Skip to content

Commit 29baf48

Browse files
committed
Add RangeReplaceableCollection conformance to struct Data.
This also addresses a TODO in the code, now that we can access the base of a slice.
1 parent e08255e commit 29baf48

File tree

2 files changed

+127
-43
lines changed

2 files changed

+127
-43
lines changed

stdlib/public/SDK/Foundation/Data.swift

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ internal final class _SwiftNSData : _SwiftNativeNSData, _SwiftNativeFoundationTy
6666

6767
`Data` can be initialized with an `UnsafePointer` and count, an array of `UInt8` (the primitive byte type), or an `UnsafeBufferPointer`. The buffer-oriented functions provide an extra measure of safety by automatically performing the size calculation, as the type is known at compile time.
6868
*/
69-
public struct Data : ReferenceConvertible, CustomStringConvertible, Equatable, Hashable, RandomAccessCollection, MutableCollection, _MutablePairBoxing {
69+
public struct Data : ReferenceConvertible, CustomStringConvertible, Equatable, Hashable, RandomAccessCollection, MutableCollection, RangeReplaceableCollection, _MutablePairBoxing {
7070
/// The Objective-C bridged type of `Data`.
7171
public typealias ReferenceType = NSData
7272

@@ -163,29 +163,29 @@ public struct Data : ReferenceConvertible, CustomStringConvertible, Equatable, H
163163

164164
/// Initialize a `Data` with the specified size.
165165
///
166-
/// This initializer doesn't 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.
166+
/// This initializer doesn't necessarily allocate the requested memory right away. `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.
167167
///
168168
/// This method sets the `count` of the data to 0.
169169
///
170170
/// 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.
171171
///
172172
/// - parameter capacity: The size of the data.
173-
public init?(capacity: Int) {
173+
public init(capacity: Int) {
174174
if let d = NSMutableData(capacity: capacity) {
175175
_wrapped = _SwiftNSData(mutableObject: d)
176176
} else {
177-
return nil
177+
fatalError("Unable to allocate data of the requested capacity")
178178
}
179179
}
180180

181181
/// Initialize a `Data` with the specified count of zeroed bytes.
182182
///
183183
/// - parameter count: The number of bytes the data initially contains.
184-
public init?(count: Int) {
184+
public init(count: Int) {
185185
if let d = NSMutableData(length: count) {
186186
_wrapped = _SwiftNSData(mutableObject: d)
187187
} else {
188-
return nil
188+
fatalError("Unable to allocate data of the requested count")
189189
}
190190
}
191191

@@ -249,10 +249,10 @@ public struct Data : ReferenceConvertible, CustomStringConvertible, Equatable, H
249249
/// 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.
250250
///
251251
/// - parameter reference: The instance of `NSData` that you wish to wrap. This instance will be copied by `struct Data`.
252-
public init(reference: NSData) {
252+
public init(referencing reference: NSData) {
253253
_wrapped = _SwiftNSData(immutableObject: reference.copy())
254254
}
255-
255+
256256
// -----------------------------------
257257
// MARK: - Properties and Functions
258258

@@ -461,11 +461,11 @@ public struct Data : ReferenceConvertible, CustomStringConvertible, Equatable, H
461461
///
462462
/// This will resize the data if required, to fit the entire contents of `data`.
463463
///
464-
/// - precondition: `range` must be within the range of the data.
465-
/// - parameter range: The range in the data to replace.
464+
/// - precondition: The bounds of `subrange` must be valid indicies of the collection.
465+
/// - parameter subrange: The range in the data to replace. If `subrange.lowerBound == data.count && subrange.count == 0` then this operation is an append.
466466
/// - parameter data: The replacement data.
467-
public mutating func replaceBytes(in range: Range<Index>, with data: Data) {
468-
let nsRange = NSMakeRange(range.lowerBound, range.upperBound - range.lowerBound)
467+
public mutating func replaceSubrange(_ subrange: Range<Index>, with data: Data) {
468+
let nsRange = NSMakeRange(subrange.lowerBound, subrange.upperBound - subrange.lowerBound)
469469
let cnt = data.count
470470
let bytes = data._getUnsafeBytesPointer()
471471

@@ -478,10 +478,11 @@ public struct Data : ReferenceConvertible, CustomStringConvertible, Equatable, H
478478
///
479479
/// This will resize the data if required, to fit the entire contents of `buffer`.
480480
///
481-
/// - precondition: `range` must be within the range of the data.
481+
/// - precondition: The bounds of `subrange` must be valid indicies of the collection.
482+
/// - parameter subrange: The range in the data to replace.
482483
/// - parameter buffer: The replacement bytes.
483-
public mutating func replaceBytes<SourceType>(in range: Range<Index>, with buffer: UnsafeBufferPointer<SourceType>) {
484-
let nsRange = NSMakeRange(range.lowerBound, range.upperBound - range.lowerBound)
484+
public mutating func replaceSubrange<SourceType>(_ subrange: Range<Index>, with buffer: UnsafeBufferPointer<SourceType>) {
485+
let nsRange = NSMakeRange(subrange.lowerBound, subrange.upperBound - subrange.lowerBound)
485486
let bufferCount = buffer.count * strideof(SourceType.self)
486487

487488
_applyUnmanagedMutation {
@@ -490,6 +491,52 @@ public struct Data : ReferenceConvertible, CustomStringConvertible, Equatable, H
490491

491492
}
492493

494+
/// Replace a region of bytes in the data with new bytes from a collection.
495+
///
496+
/// This will resize the data if required, to fit the entire contents of `newElements`.
497+
///
498+
/// - precondition: The bounds of `subrange` must be valid indicies of the collection.
499+
/// - parameter subrange: The range in the data to replace.
500+
/// - parameter newElements: The replacement bytes.
501+
public mutating func replaceSubrange<ByteCollection : Collection where ByteCollection.Iterator.Element == Data.Iterator.Element>(_ subrange: Range<Index>, with newElements: ByteCollection) {
502+
503+
// Calculate this once, it may not be O(1)
504+
let replacementCount : Int = numericCast(newElements.count)
505+
let currentCount = self.count
506+
let subrangeCount = subrange.count
507+
508+
if currentCount < subrange.lowerBound + subrangeCount {
509+
if subrangeCount == 0 {
510+
preconditionFailure("location \(subrange.lowerBound) exceeds data count \(currentCount)")
511+
} else {
512+
preconditionFailure("range \(subrange) exceeds data count \(currentCount)")
513+
}
514+
}
515+
516+
let resultCount = currentCount - subrangeCount + replacementCount
517+
if resultCount != currentCount {
518+
// This may realloc.
519+
// In the future, if we keep the malloced pointer and count inside this struct/ref instead of deferring to NSData, we may be able to do this more efficiently.
520+
self.count = resultCount
521+
}
522+
523+
let shift = resultCount - currentCount
524+
let start = subrange.lowerBound
525+
526+
self.withUnsafeMutableBytes { (bytes : UnsafeMutablePointer<UInt8>) -> () in
527+
if shift != 0 {
528+
let destination = bytes + start + replacementCount
529+
let source = bytes + start + subrangeCount
530+
memmove(destination, source, currentCount - start - subrangeCount)
531+
}
532+
533+
if replacementCount != 0 {
534+
newElements._copyContents(initializing: bytes + start)
535+
}
536+
}
537+
}
538+
539+
493540
/// Return a new copy of the data in a specified range.
494541
///
495542
/// - parameter range: The range to copy.
@@ -561,15 +608,7 @@ public struct Data : ReferenceConvertible, CustomStringConvertible, Equatable, H
561608
return MutableRandomAccessSlice(base: self, bounds: bounds)
562609
}
563610
set {
564-
// Ideally this would be:
565-
// replaceBytes(in: bounds, with: newValue._base)
566-
// but we do not have access to _base due to 'internal' protection
567-
// TODO: Use a custom Slice type so we have access to the underlying data
568-
let arrayOfBytes = newValue.map { $0 }
569-
arrayOfBytes.withUnsafeBufferPointer {
570-
let otherData = Data(buffer: $0)
571-
replaceBytes(in: bounds, with: otherData)
572-
}
611+
replaceSubrange(bounds, with: newValue.base)
573612
}
574613
}
575614

@@ -619,12 +658,12 @@ extension Data : _ObjectiveCBridgeable {
619658

620659
public static func _forceBridgeFromObjectiveC(_ input: NSData, result: inout Data?) {
621660
// We must copy the input because it might be mutable; just like storing a value type in ObjC
622-
result = Data(reference: input)
661+
result = Data(referencing: input)
623662
}
624663

625664
public static func _conditionallyBridgeFromObjectiveC(_ input: NSData, result: inout Data?) -> Bool {
626665
// We must copy the input because it might be mutable; just like storing a value type in ObjC
627-
result = Data(reference: input)
666+
result = Data(referencing: input)
628667
return true
629668
}
630669

test/1_stdlib/TestData.swift

Lines changed: 62 additions & 17 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 = Data(reference: AllOnesData(length: length))
193+
let allOnesData = Data(referencing: 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 = Data(reference: allOnes)
249+
let data = Data(referencing: 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 testReplaceBytes() {
294+
func testReplaceSubrange() {
295295
var hello = dataFrom("Hello")
296296
let world = dataFrom("World")
297297

@@ -302,11 +302,11 @@ class TestData : TestDataSuper {
302302
let goodbye = dataFrom("Goodbye")
303303
let expected = dataFrom("Goodbye World")
304304

305-
goodbyeWorld.replaceBytes(in: 0..<5, with: goodbye)
305+
goodbyeWorld.replaceSubrange(0..<5, with: goodbye)
306306
expectEqual(goodbyeWorld, expected)
307307
}
308308

309-
func testReplaceBytes2() {
309+
func testReplaceSubrange2() {
310310
let hello = dataFrom("Hello")
311311
let world = dataFrom(" World")
312312
let goodbye = dataFrom("Goodbye")
@@ -316,12 +316,12 @@ class TestData : TestDataSuper {
316316
mutateMe.append(world)
317317

318318
if let found = mutateMe.range(of: hello) {
319-
mutateMe.replaceBytes(in: found, with: goodbye)
319+
mutateMe.replaceSubrange(found, with: goodbye)
320320
}
321321
expectEqual(mutateMe, expected)
322322
}
323323

324-
func testReplaceBytes3() {
324+
func testReplaceSubrange3() {
325325
// The expected result
326326
let expectedBytes : [UInt8] = [1, 2, 9, 10, 11, 12, 13]
327327
let expected = expectedBytes.withUnsafeBufferPointer {
@@ -337,10 +337,45 @@ class TestData : TestDataSuper {
337337
// The bytes we'll insert
338338
let b : [UInt8] = [9, 10, 11, 12, 13]
339339
b.withUnsafeBufferPointer {
340-
a.replaceBytes(in: 2..<5, with: $0)
340+
a.replaceSubrange(2..<5, with: $0)
341341
}
342342
expectEqual(expected, a)
343343
}
344+
345+
func testReplaceSubrange4() {
346+
let expectedBytes : [UInt8] = [1, 2, 9, 10, 11, 12, 13]
347+
let expected = Data(bytes: expectedBytes)
348+
349+
// The data we'll mutate
350+
let someBytes : [UInt8] = [1, 2, 3, 4, 5]
351+
var a = Data(bytes: someBytes)
352+
353+
// The bytes we'll insert
354+
let b : [UInt8] = [9, 10, 11, 12, 13]
355+
a.replaceSubrange(2..<5, with: b)
356+
expectEqual(expected, a)
357+
}
358+
359+
func testReplaceSubrange5() {
360+
var d = Data(bytes: [1, 2, 3])
361+
d.replaceSubrange(0..<0, with: [4])
362+
expectEqual(Data(bytes: [4, 1, 2, 3]), d)
363+
364+
d.replaceSubrange(0..<4, with: [9])
365+
expectEqual(Data(bytes: [9]), d)
366+
367+
d.replaceSubrange(0..<d.count, with: [])
368+
expectEqual(Data(), d)
369+
370+
d.replaceSubrange(0..<0, with: [1, 2, 3, 4])
371+
expectEqual(Data(bytes: [1, 2, 3, 4]), d)
372+
373+
d.replaceSubrange(1..<3, with: [9, 8])
374+
expectEqual(Data(bytes: [1, 9, 8, 4]), d)
375+
376+
d.replaceSubrange(d.count..<d.count, with: [5])
377+
expectEqual(Data(bytes: [1, 9, 8, 4, 5]), d)
378+
}
344379

345380
func testRange() {
346381
let helloWorld = dataFrom("Hello World")
@@ -369,8 +404,8 @@ class TestData : TestDataSuper {
369404
let expected = dataFrom("Hello World")
370405
var helloWorld = dataFrom("")
371406

372-
helloWorld.replaceBytes(in: 0..<0, with: world)
373-
helloWorld.replaceBytes(in: 0..<0, with: hello)
407+
helloWorld.replaceSubrange(0..<0, with: world)
408+
helloWorld.replaceSubrange(0..<0, with: hello)
374409

375410
expectEqual(helloWorld, expected)
376411
}
@@ -424,7 +459,7 @@ class TestData : TestDataSuper {
424459
buffer[0] = 0
425460
buffer[1] = 0
426461

427-
var data = Data(capacity: c * strideof(UInt16.self))!
462+
var data = Data(capacity: c * strideof(UInt16.self))
428463
data.resetBytes(in: 0..<c * strideof(UInt16.self))
429464
data[0] = 0xFF
430465
data[1] = 0xFF
@@ -795,9 +830,11 @@ DataTests.test("testBridgingMutable") { TestData().testBridgingMutable() }
795830
DataTests.test("testBridgingCustom") { TestData().testBridgingCustom() }
796831
DataTests.test("testEquality") { TestData().testEquality() }
797832
DataTests.test("testDataInSet") { TestData().testDataInSet() }
798-
DataTests.test("testReplaceBytes") { TestData().testReplaceBytes() }
799-
DataTests.test("testReplaceBytes2") { TestData().testReplaceBytes2() }
800-
DataTests.test("testReplaceBytes3") { TestData().testReplaceBytes3() }
833+
DataTests.test("testReplaceSubrange") { TestData().testReplaceSubrange() }
834+
DataTests.test("testReplaceSubrange2") { TestData().testReplaceSubrange2() }
835+
DataTests.test("testReplaceSubrange3") { TestData().testReplaceSubrange3() }
836+
DataTests.test("testReplaceSubrange4") { TestData().testReplaceSubrange4() }
837+
DataTests.test("testReplaceSubrange5") { TestData().testReplaceSubrange5() }
801838
DataTests.test("testRange") { TestData().testRange() }
802839
DataTests.test("testInsertData") { TestData().testInsertData() }
803840
DataTests.test("testLoops") { TestData().testLoops() }
@@ -830,7 +867,7 @@ DataTests.test("bounding failure subdata") {
830867
DataTests.test("bounding failure replace") {
831868
var data = "Hello World".data(using: .utf8)!
832869
expectCrashLater()
833-
data.replaceBytes(in: 5..<200, with: Data())
870+
data.replaceSubrange(5..<200, with: Data())
834871
}
835872

836873
DataTests.test("bounding failure replace2") {
@@ -839,7 +876,7 @@ DataTests.test("bounding failure replace2") {
839876
expectCrashLater()
840877
bytes.withUnsafeBufferPointer {
841878
// lowerBound ok, upperBound after end of data
842-
data.replaceBytes(in: 0..<2, with: $0)
879+
data.replaceSubrange(0..<2, with: $0)
843880
}
844881
}
845882

@@ -849,10 +886,18 @@ DataTests.test("bounding failure replace3") {
849886
expectCrashLater()
850887
bytes.withUnsafeBufferPointer {
851888
// lowerBound is > length
852-
data.replaceBytes(in: 2..<4, with: $0)
889+
data.replaceSubrange(2..<4, with: $0)
853890
}
854891
}
855892

893+
DataTests.test("bounding failure replace4") {
894+
var data = "a".data(using: .utf8)!
895+
var bytes : [UInt8] = [1, 2, 3]
896+
expectCrashLater()
897+
// lowerBound is > length
898+
data.replaceSubrange(2..<4, with: bytes)
899+
}
900+
856901
DataTests.test("bounding failure reset range") {
857902
var data = "Hello World".data(using: .utf8)!
858903
expectCrashLater()

0 commit comments

Comments
 (0)