Skip to content

Commit 11e85d7

Browse files
committed
Add a new set of initializers to Data to aid efficiency and clarity when wrapping NSData.
addresses: rdar://problem/26385078 Data() really needs a init(length:) and replaceBytes(in:withBytes:) rdar://problem/26508250 Add more specific init method to Data to indicate keeping a ref type
1 parent 1bfd433 commit 11e85d7

File tree

2 files changed

+131
-17
lines changed

2 files changed

+131
-17
lines changed

stdlib/public/SDK/Foundation/Data.swift

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,29 @@ public struct Data : ReferenceConvertible, CustomStringConvertible, Equatable, H
128128
public init(bytes: UnsafePointer<UInt8>, count: Int) {
129129
_wrapped = _SwiftNSData(immutableObject: NSData(bytes: bytes, length: count))
130130
}
131-
131+
132+
/// Initialize a `Data` with copied memory content.
133+
///
134+
/// - parameter bytes: A pointer to the memory. It will be copied.
135+
/// - parameter count: The number of bytes to copy.
136+
public init(bytes: UnsafeMutablePointer<UInt8>, count: Int) {
137+
_wrapped = _SwiftNSData(immutableObject: NSData(bytes: UnsafePointer(bytes), length: count))
138+
}
139+
132140
/// Initialize a `Data` with copied memory content.
133141
///
134142
/// - parameter buffer: A buffer pointer to copy. The size is calculated from `SourceType` and `buffer.count`.
135143
public init<SourceType>(buffer: UnsafeBufferPointer<SourceType>) {
136144
_wrapped = _SwiftNSData(immutableObject: NSData(bytes: buffer.baseAddress, length: strideof(SourceType) * buffer.count))
137145
}
138-
146+
147+
/// Initialize a `Data` with copied memory content.
148+
///
149+
/// - parameter buffer: A buffer pointer to copy. The size is calculated from `SourceType` and `buffer.count`.
150+
public init<SourceType>(buffer: UnsafeMutableBufferPointer<SourceType>) {
151+
_wrapped = _SwiftNSData(immutableObject: NSData(bytes: UnsafePointer(buffer.baseAddress), length: strideof(SourceType) * buffer.count))
152+
}
153+
139154
/// Initialize a `Data` with the contents of an Array.
140155
///
141156
/// - parameter bytes: An array of bytes to copy.
@@ -156,15 +171,33 @@ public struct Data : ReferenceConvertible, CustomStringConvertible, Equatable, H
156171

157172
/// Initialize a `Data` with the specified size.
158173
///
174+
/// This initializer doesn not necessarily allocate the requested memory right away. Mutable data allocates additional memory as needed, so `capacity` simply establishes the initial capacity. When it does allocate the initial memory, though, it allocates the specified amount.
175+
///
176+
/// This method sets the `count` of the data to 0.
177+
///
178+
/// If the capacity specified in `capacity` is greater than four memory pages in size, this may round the amount of requested memory up to the nearest full page.
179+
///
159180
/// - parameter capacity: The size of the data.
160181
public init?(capacity: Int) {
161182
if let d = NSMutableData(capacity: capacity) {
162-
_wrapped = _SwiftNSData(immutableObject: d)
183+
_wrapped = _SwiftNSData(mutableObject: d)
163184
} else {
164185
return nil
165186
}
166187
}
167188

189+
/// Initialize a `Data` with the specified count of zeroed bytes.
190+
///
191+
/// - parameter count: The number of bytes the data initially contains.
192+
public init?(count: Int) {
193+
if let d = NSMutableData(length: count) {
194+
_wrapped = _SwiftNSData(mutableObject: d)
195+
} else {
196+
return nil
197+
}
198+
}
199+
200+
168201
/// Initialize an empty `Data`.
169202
public init() {
170203
_wrapped = _SwiftNSData(immutableObject: NSData(bytes: nil, length: 0))
@@ -217,9 +250,27 @@ public struct Data : ReferenceConvertible, CustomStringConvertible, Equatable, H
217250
}
218251
}
219252

220-
private init(_bridged data: NSData) {
221-
// We must copy the input because it might be mutable; just like storing a value type in ObjC
222-
_wrapped = _SwiftNSData(immutableObject: data.copy())
253+
/// Initialize a `Data` by adopting a reference type.
254+
///
255+
/// You can use this initializer to create a `struct Data` that wraps a `class NSData`. `struct Data` will use the `class NSData` for all operations. Other initializers (including casting using `as Data`) may choose to hold a reference or not, based on a what is the most efficient representation.
256+
///
257+
/// If the resulting value is mutated, then `Data` will invoke the `mutableCopy()` function on the reference to copy the contents. You may customize the behavior of that function if you wish to return a specialized mutable subclass.
258+
///
259+
/// - parameter reference: The instance of `NSData` that you wish to wrap. This instance will be copied by `struct Data`.
260+
public init(reference: NSData) {
261+
_wrapped = _SwiftNSData(immutableObject: reference.copy())
262+
}
263+
264+
/// Initialize a `Data` by adopting a mutable reference type.
265+
///
266+
/// You can use this initializer to create a `struct Data` that wraps a `class NSMutableData`. `struct Data` will use the `class NSMutableData` for all operations. Other initializers (including casting using `as Data`) may choose to hold a reference or not, based on a what is the most efficient representation.
267+
///
268+
/// If the resulting value is mutated, then `Data` will invoke the `mutableCopy()` function on the reference to copy the contents. You may customize the behavior of that function if you wish to return a specialized mutable subclass.
269+
///
270+
/// - warning: For performance reasons, this method does not copy the reference on initialization. It assumes that the reference is uniquely held by the struct. If you continue to mutate the reference after invoking this initializer, you may invalidate the copy-on-write and value semantics of `struct Data`. It is recommended that you do not keep the reference after initializing a `Data` with it.
271+
/// - parameter mutableReference: The instance of `NSMutableData` that you wish to wrap.
272+
public init(mutableReference: NSMutableData) {
273+
_wrapped = _SwiftNSData(mutableObject: mutableReference)
223274
}
224275

225276
// -----------------------------------
@@ -428,6 +479,9 @@ public struct Data : ReferenceConvertible, CustomStringConvertible, Equatable, H
428479

429480
/// Replace a region of bytes in the data with new data.
430481
///
482+
/// This will resize the data if required, to fit the entire contents of `data`.
483+
///
484+
/// - precondition: `range` must be within the range of the data.
431485
/// - parameter range: The range in the data to replace.
432486
/// - parameter data: The replacement data.
433487
public mutating func replaceBytes(in range: Range<Index>, with data: Data) {
@@ -439,7 +493,23 @@ public struct Data : ReferenceConvertible, CustomStringConvertible, Equatable, H
439493
$0.replaceBytes(in: nsRange, withBytes: bytes, length: cnt)
440494
}
441495
}
496+
497+
/// Replace a region of bytes in the data with new bytes from a buffer.
498+
///
499+
/// This will resize the data if required, to fit the entire contents of `buffer`.
500+
///
501+
/// - precondition: `range` must be within the range of the data.
502+
/// - parameter buffer: The replacement bytes.
503+
public mutating func replaceBytes<SourceType>(in range: Range<Index>, with buffer: UnsafeBufferPointer<SourceType>) {
504+
let nsRange = NSMakeRange(range.lowerBound, range.upperBound - range.lowerBound)
505+
let bufferCount = buffer.count * strideof(SourceType)
506+
507+
_applyUnmanagedMutation {
508+
$0.replaceBytes(in: nsRange, withBytes: buffer.baseAddress, length: bufferCount)
509+
}
442510

511+
}
512+
443513
/// Return a new copy of the data in a specified range.
444514
///
445515
/// - parameter range: The range to copy.
@@ -506,7 +576,7 @@ public struct Data : ReferenceConvertible, CustomStringConvertible, Equatable, H
506576
}
507577
}
508578

509-
public subscript(bounds: Range<Int>) -> MutableRandomAccessSlice<Data> {
579+
public subscript(bounds: Range<Index>) -> MutableRandomAccessSlice<Data> {
510580
get {
511581
return MutableRandomAccessSlice(base: self, bounds: bounds)
512582
}
@@ -568,11 +638,13 @@ extension Data : _ObjectiveCBridgeable {
568638
}
569639

570640
public static func _forceBridgeFromObjectiveC(_ input: NSData, result: inout Data?) {
571-
result = Data(_bridged: input)
641+
// We must copy the input because it might be mutable; just like storing a value type in ObjC
642+
result = Data(reference: input)
572643
}
573644

574645
public static func _conditionallyBridgeFromObjectiveC(_ input: NSData, result: inout Data?) -> Bool {
575-
result = Data(_bridged: input)
646+
// We must copy the input because it might be mutable; just like storing a value type in ObjC
647+
result = Data(reference: input)
576648
return true
577649
}
578650

test/1_stdlib/TestData.swift

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ class TestData : TestDataSuper {
190190

191191
func testCustomData() {
192192
let length = 5
193-
let allOnesData = AllOnesData(length: length) as Data
193+
let allOnesData = Data(reference: AllOnesData(length: length))
194194
expectEqual(1, allOnesData[0], "First byte of all 1s data should be 1")
195195

196196
// Double the length
@@ -246,7 +246,7 @@ class TestData : TestDataSuper {
246246
let allOnes = AllOnesData(length: 64)
247247

248248
// Type-erased
249-
let data = allOnes as Data
249+
let data = Data(reference: allOnes)
250250

251251
// Create a home for our test data
252252
let dirPath = (NSTemporaryDirectory() as NSString).appendingPathComponent(NSUUID().uuidString)
@@ -291,7 +291,7 @@ class TestData : TestDataSuper {
291291
expectEqual(s.count, 2, "Expected only two entries in the Set")
292292
}
293293

294-
func testSubsequences() {
294+
func testReplaceBytes() {
295295
var hello = dataFrom("Hello")
296296
let world = dataFrom("World")
297297

@@ -306,7 +306,7 @@ class TestData : TestDataSuper {
306306
expectEqual(goodbyeWorld, expected)
307307
}
308308

309-
func testSubsequences2() {
309+
func testReplaceBytes2() {
310310
let hello = dataFrom("Hello")
311311
let world = dataFrom(" World")
312312
let goodbye = dataFrom("Goodbye")
@@ -320,8 +320,29 @@ class TestData : TestDataSuper {
320320
}
321321
expectEqual(mutateMe, expected)
322322
}
323+
324+
func testReplaceBytes3() {
325+
// The expected result
326+
let expectedBytes : [UInt8] = [1, 2, 9, 10, 11, 12, 13]
327+
let expected = expectedBytes.withUnsafeBufferPointer {
328+
return Data(buffer: $0)
329+
}
330+
331+
// The data we'll mutate
332+
let someBytes : [UInt8] = [1, 2, 3, 4, 5]
333+
var a = someBytes.withUnsafeBufferPointer {
334+
return Data(buffer: $0)
335+
}
336+
337+
// The bytes we'll insert
338+
let b : [UInt8] = [9, 10, 11, 12, 13]
339+
b.withUnsafeBufferPointer {
340+
a.replaceBytes(in: 2..<5, with: $0)
341+
}
342+
expectEqual(expected, a)
343+
}
323344

324-
func testSubsequences3() {
345+
func testRange() {
325346
let helloWorld = dataFrom("Hello World")
326347
let goodbye = dataFrom("Goodbye")
327348
let hello = dataFrom("Hello")
@@ -774,9 +795,10 @@ DataTests.test("testBridgingMutable") { TestData().testBridgingMutable() }
774795
DataTests.test("testBridgingCustom") { TestData().testBridgingCustom() }
775796
DataTests.test("testEquality") { TestData().testEquality() }
776797
DataTests.test("testDataInSet") { TestData().testDataInSet() }
777-
DataTests.test("testSubsequences") { TestData().testSubsequences() }
778-
DataTests.test("testSubsequences2") { TestData().testSubsequences2() }
779-
DataTests.test("testSubsequences3") { TestData().testSubsequences3() }
798+
DataTests.test("testReplaceBytes") { TestData().testReplaceBytes() }
799+
DataTests.test("testReplaceBytes2") { TestData().testReplaceBytes2() }
800+
DataTests.test("testReplaceBytes3") { TestData().testReplaceBytes3() }
801+
DataTests.test("testRange") { TestData().testRange() }
780802
DataTests.test("testInsertData") { TestData().testInsertData() }
781803
DataTests.test("testLoops") { TestData().testLoops() }
782804
DataTests.test("testGenericAlgorithms") { TestData().testGenericAlgorithms() }
@@ -811,6 +833,26 @@ DataTests.test("bounding failure replace") {
811833
data.replaceBytes(in: 5..<200, with: Data())
812834
}
813835

836+
DataTests.test("bounding failure replace2") {
837+
var data = "a".data(using: .utf8)!
838+
var bytes : [UInt8] = [1, 2, 3]
839+
expectCrashLater()
840+
bytes.withUnsafeBufferPointer {
841+
// lowerBound ok, upperBound after end of data
842+
data.replaceBytes(in: 0..<2, with: $0)
843+
}
844+
}
845+
846+
DataTests.test("bounding failure replace3") {
847+
var data = "a".data(using: .utf8)!
848+
var bytes : [UInt8] = [1, 2, 3]
849+
expectCrashLater()
850+
bytes.withUnsafeBufferPointer {
851+
// lowerBound is > length
852+
data.replaceBytes(in: 2..<4, with: $0)
853+
}
854+
}
855+
814856
DataTests.test("bounding failure reset range") {
815857
var data = "Hello World".data(using: .utf8)!
816858
expectCrashLater()

0 commit comments

Comments
 (0)