Skip to content

Commit 0c6d5f5

Browse files
committed
Parity: NSCoding stragglers: NSIndexPath
Implement Darwin-compatible coding and add fixture tests.
1 parent 7207b89 commit 0c6d5f5

7 files changed

+238
-99
lines changed

Foundation/NSIndexPath.swift

+93-3
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,80 @@ open class NSIndexPath : NSObject, NSCopying, NSSecureCoding {
4040
self.init(indexes: [index])
4141
}
4242

43+
fileprivate enum NSCodingKeys {
44+
static let lengthKey = "NSIndexPathLength"
45+
static let singleValueKey = "NSIndexPathValue"
46+
static let dataKey = "NSIndexPathData"
47+
}
48+
4349
open func encode(with aCoder: NSCoder) {
44-
NSUnimplemented()
50+
guard aCoder.allowsKeyedCoding else {
51+
aCoder.failWithError(NSError(domain: NSCocoaErrorDomain, code: NSCoderReadCorruptError, userInfo: [NSLocalizedDescriptionKey: "Cannot be serialized with a coder that does not support keyed archives"]))
52+
return
53+
}
54+
55+
let length = self.length
56+
aCoder.encode(length, forKey: NSCodingKeys.lengthKey)
57+
switch length {
58+
case 0:
59+
break
60+
case 1:
61+
aCoder.encode(index(atPosition: 0), forKey: NSCodingKeys.singleValueKey)
62+
default:
63+
var sequence = PackedUIntSequence(data: Data(capacity: length * 2 + 16))
64+
for position in 0 ..< length {
65+
sequence.append(UInt(index(atPosition: position)))
66+
}
67+
aCoder.encode(sequence.data, forKey: NSCodingKeys.dataKey)
68+
}
4569
}
4670

47-
public required init?(coder aDecoder: NSCoder) {
48-
NSUnimplemented()
71+
public required convenience init?(coder aDecoder: NSCoder) {
72+
guard aDecoder.allowsKeyedCoding else {
73+
aDecoder.failWithError(NSError(domain: NSCocoaErrorDomain, code: NSCoderReadCorruptError, userInfo: [NSLocalizedDescriptionKey: "Cannot be deserialized with a coder that does not support keyed archives"]))
74+
return nil
75+
}
76+
77+
guard aDecoder.containsValue(forKey: NSCodingKeys.lengthKey) else {
78+
aDecoder.failWithError(NSError(domain: NSCocoaErrorDomain, code: NSCoderReadCorruptError, userInfo: [NSLocalizedDescriptionKey: "Decoder did not provide a length value for the indexPath."]))
79+
return nil
80+
}
81+
82+
let len = aDecoder.decodeInteger(forKey: NSCodingKeys.lengthKey)
83+
guard len > 0 else {
84+
self.init()
85+
return
86+
}
87+
88+
switch len {
89+
case 0:
90+
self.init()
91+
return
92+
93+
case 1:
94+
guard aDecoder.containsValue(forKey: NSCodingKeys.singleValueKey) else {
95+
aDecoder.failWithError(NSError(domain: NSCocoaErrorDomain, code: NSCoderReadCorruptError, userInfo: [NSLocalizedDescriptionKey: "Decoder did not provide indexPath data."]))
96+
return nil
97+
}
98+
99+
let index = aDecoder.decodeInteger(forKey: NSCodingKeys.singleValueKey)
100+
self.init(index: index)
101+
return
102+
103+
default:
104+
guard let bytes = aDecoder.decodeObject(of: NSData.self, forKey: NSCodingKeys.dataKey) else {
105+
aDecoder.failWithError(NSError(domain: NSCocoaErrorDomain, code: NSCoderReadCorruptError, userInfo: [NSLocalizedDescriptionKey: "Range data missing."]))
106+
return nil
107+
}
108+
109+
let sequence = PackedUIntSequence(data: bytes._swiftObject)
110+
guard sequence.count == len else {
111+
aDecoder.failWithError(NSError(domain: NSCocoaErrorDomain, code: NSCoderReadCorruptError, userInfo: [NSLocalizedDescriptionKey: "Range data did not match expected length."]))
112+
return nil
113+
}
114+
115+
self.init(indexes: sequence.integers)
116+
}
49117
}
50118

51119
public static var supportsSecureCoding: Bool { return true }
@@ -110,6 +178,28 @@ open class NSIndexPath : NSObject, NSCopying, NSSecureCoding {
110178
}
111179
return .orderedSame
112180
}
181+
182+
open override var hash: Int {
183+
var hasher = Hasher()
184+
for i in 0 ..< length {
185+
hasher.combine(index(atPosition: i))
186+
}
187+
return hasher.finalize()
188+
}
189+
190+
open override func isEqual(_ object: Any?) -> Bool {
191+
guard let indexPath = object as? NSIndexPath,
192+
indexPath.length == self.length else { return false }
193+
194+
let length = self.length
195+
for i in 0 ..< length {
196+
if index(atPosition: i) != indexPath.index(atPosition: i) {
197+
return false
198+
}
199+
}
200+
201+
return true
202+
}
113203
}
114204

115205

Foundation/NSIndexSet.swift

+51-42
Original file line numberDiff line numberDiff line change
@@ -858,52 +858,61 @@ extension NSIndexSet : _StructTypeBridgeable, _SwiftBridgeable {
858858

859859
// MARK: NSCoding
860860

861-
extension NSIndexSet {
862-
struct PackedUIntSequence {
863-
var data: Data
864-
init(data: Data) {
865-
self.data = data
866-
}
867-
868-
var count: Int {
869-
var result = 0
870-
for byte in data {
871-
if byte < 128 {
872-
result += 1
873-
}
861+
internal struct PackedUIntSequence {
862+
var data: Data
863+
init(data: Data = Data()) {
864+
self.data = data
865+
}
866+
867+
var count: Int {
868+
var result = 0
869+
for byte in data {
870+
if byte < 128 {
871+
result += 1
874872
}
875-
return result
876873
}
877-
878-
var unsignedIntegers: [UInt] {
879-
var result: [UInt] = []
880-
var index = data.startIndex
881-
while index < data.endIndex {
882-
let fromIndex = unsignedInt(atIndex: index)
883-
result.append(fromIndex.value)
884-
index = fromIndex.nextIndex
885-
}
886-
return result
874+
return result
875+
}
876+
877+
var unsignedIntegers: [UInt] {
878+
var result: [UInt] = []
879+
var index = data.startIndex
880+
while index < data.endIndex {
881+
let fromIndex = unsignedInt(atIndex: index)
882+
result.append(fromIndex.value)
883+
index = fromIndex.nextIndex
887884
}
888-
889-
private func unsignedInt(atIndex index: Int) -> (value: UInt, nextIndex: Int) {
890-
var result = UInt(data[index])
891-
if result < 128 {
892-
return (value: result, nextIndex: index + 1)
893-
} else {
894-
let fromNext = unsignedInt(atIndex: index + 1)
895-
result = (result - 128) + 128 * fromNext.value
896-
return (value: result, nextIndex: fromNext.nextIndex)
897-
}
885+
return result
886+
}
887+
888+
var integers: [Int] {
889+
var result: [Int] = []
890+
var index = data.startIndex
891+
while index < data.endIndex {
892+
let fromIndex = unsignedInt(atIndex: index)
893+
result.append(Int(fromIndex.value))
894+
index = fromIndex.nextIndex
898895
}
899-
900-
mutating func append(_ value: UInt) {
901-
if value > 127 {
902-
data.append(UInt8(128 + (value % 128)))
903-
append(value / 128)
904-
} else {
905-
data.append(UInt8(value))
906-
}
896+
return result
897+
}
898+
899+
private func unsignedInt(atIndex index: Int) -> (value: UInt, nextIndex: Int) {
900+
var result = UInt(data[index])
901+
if result < 128 {
902+
return (value: result, nextIndex: index + 1)
903+
} else {
904+
let fromNext = unsignedInt(atIndex: index + 1)
905+
result = (result - 128) + 128 * fromNext.value
906+
return (value: result, nextIndex: fromNext.nextIndex)
907+
}
908+
}
909+
910+
mutating func append(_ value: UInt) {
911+
if value > 127 {
912+
data.append(UInt8(128 + (value % 128)))
913+
append(value / 128)
914+
} else {
915+
data.append(UInt8(value))
907916
}
908917
}
909918
}

TestFoundation/FixtureValues.swift

+19
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,22 @@ enum Fixtures {
200200
return (try Fixtures.indexSetManyRanges.make()).mutableCopy() as! NSMutableIndexSet
201201
}
202202

203+
// ===== NSIndexPath =====
204+
205+
static let indexPathEmpty = TypedFixture<NSIndexPath>("NSIndexPath-Empty") {
206+
return NSIndexPath()
207+
}
208+
209+
static let indexPathOneIndex = TypedFixture<NSIndexPath>("NSIndexPath-OneIndex") {
210+
return NSIndexPath(index: 52)
211+
}
212+
213+
static let indexPathManyIndices = TypedFixture<NSIndexPath>("NSIndexPath-ManyIndices") {
214+
var indexPath = IndexPath()
215+
indexPath.append([4, 8, 15, 16, 23, 42])
216+
return indexPath as NSIndexPath
217+
}
218+
203219
// ===== NSSet, NSMutableSet =====
204220

205221
static let setOfNumbers = TypedFixture<NSSet>("NSSet-Numbers") {
@@ -284,6 +300,9 @@ enum Fixtures {
284300
AnyFixture(Fixtures.mutableIndexSetEmpty),
285301
AnyFixture(Fixtures.mutableIndexSetOneRange),
286302
AnyFixture(Fixtures.mutableIndexSetManyRanges),
303+
AnyFixture(Fixtures.indexPathEmpty),
304+
AnyFixture(Fixtures.indexPathOneIndex),
305+
AnyFixture(Fixtures.indexPathManyIndices),
287306
AnyFixture(Fixtures.setOfNumbers),
288307
AnyFixture(Fixtures.setEmpty),
289308
AnyFixture(Fixtures.mutableSetOfNumbers),
Binary file not shown.
Binary file not shown.
Binary file not shown.

TestFoundation/TestIndexPath.swift

+75-54
Original file line numberDiff line numberDiff line change
@@ -9,60 +9,6 @@
99

1010
class TestIndexPath: XCTestCase {
1111

12-
static var allTests: [(String, (TestIndexPath) -> () throws -> Void)] {
13-
return [
14-
("testEmpty", testEmpty),
15-
("testSingleIndex", testSingleIndex),
16-
("testTwoIndexes", testTwoIndexes),
17-
("testManyIndexes", testManyIndexes),
18-
("testCreateFromSequence", testCreateFromSequence),
19-
("testCreateFromLiteral", testCreateFromLiteral),
20-
("testDropLast", testDropLast),
21-
("testDropLastFromEmpty", testDropLastFromEmpty),
22-
("testDropLastFromSingle", testDropLastFromSingle),
23-
("testDropLastFromPair", testDropLastFromPair),
24-
("testDropLastFromTriple", testDropLastFromTriple),
25-
("testStartEndIndex", testStartEndIndex),
26-
("testIterator", testIterator),
27-
("testIndexing", testIndexing),
28-
("testCompare", testCompare),
29-
("testHashing", testHashing),
30-
("testEquality", testEquality),
31-
("testSubscripting", testSubscripting),
32-
("testAppending", testAppending),
33-
("testAppendEmpty", testAppendEmpty),
34-
("testAppendEmptyIndexPath", testAppendEmptyIndexPath),
35-
("testAppendManyIndexPath", testAppendManyIndexPath),
36-
("testAppendEmptyIndexPathToSingle", testAppendEmptyIndexPathToSingle),
37-
("testAppendSingleIndexPath", testAppendSingleIndexPath),
38-
("testAppendSingleIndexPathToSingle", testAppendSingleIndexPathToSingle),
39-
("testAppendPairIndexPath", testAppendPairIndexPath),
40-
("testAppendManyIndexPathToEmpty", testAppendManyIndexPathToEmpty),
41-
("testAppendByOperator", testAppendByOperator),
42-
("testAppendArray", testAppendArray),
43-
("testRanges", testRanges),
44-
("testRangeFromEmpty", testRangeFromEmpty),
45-
("testRangeFromSingle", testRangeFromSingle),
46-
("testRangeFromPair", testRangeFromPair),
47-
("testRangeFromMany", testRangeFromMany),
48-
("testRangeReplacementSingle", testRangeReplacementSingle),
49-
("testRangeReplacementPair", testRangeReplacementPair),
50-
("testMoreRanges", testMoreRanges),
51-
("testIteration", testIteration),
52-
("testDescription", testDescription),
53-
("testBridgeToObjC", testBridgeToObjC),
54-
("testForceBridgeFromObjC", testForceBridgeFromObjC),
55-
("testConditionalBridgeFromObjC", testConditionalBridgeFromObjC),
56-
("testUnconditionalBridgeFromObjC", testUnconditionalBridgeFromObjC),
57-
("testObjcBridgeType", testObjcBridgeType),
58-
("test_AnyHashableContainingIndexPath", test_AnyHashableContainingIndexPath),
59-
("test_AnyHashableCreatedFromNSIndexPath", test_AnyHashableCreatedFromNSIndexPath),
60-
("test_unconditionallyBridgeFromObjectiveC", test_unconditionallyBridgeFromObjectiveC),
61-
("test_slice_1ary", test_slice_1ary),
62-
("test_copy", test_copy),
63-
]
64-
}
65-
6612
func testEmpty() {
6713
let ip = IndexPath()
6814
XCTAssertEqual(ip.count, 0)
@@ -790,4 +736,79 @@ class TestIndexPath: XCTestCase {
790736
XCTAssertEqual(nip2.length, 3)
791737
XCTAssertEqual(nip1, nip2)
792738
}
739+
740+
let fixtures: [TypedFixture<NSIndexPath>] = [
741+
Fixtures.indexPathEmpty,
742+
Fixtures.indexPathOneIndex,
743+
Fixtures.indexPathManyIndices,
744+
]
745+
746+
func testCodingRoundtrip() throws {
747+
for fixture in fixtures {
748+
try fixture.assertValueRoundtripsInCoder()
749+
}
750+
}
751+
752+
func testLoadedValuesMatch() throws {
753+
for fixture in fixtures {
754+
try fixture.assertLoadedValuesMatch()
755+
}
756+
}
757+
758+
static var allTests: [(String, (TestIndexPath) -> () throws -> Void)] {
759+
return [
760+
("testEmpty", testEmpty),
761+
("testSingleIndex", testSingleIndex),
762+
("testTwoIndexes", testTwoIndexes),
763+
("testManyIndexes", testManyIndexes),
764+
("testCreateFromSequence", testCreateFromSequence),
765+
("testCreateFromLiteral", testCreateFromLiteral),
766+
("testDropLast", testDropLast),
767+
("testDropLastFromEmpty", testDropLastFromEmpty),
768+
("testDropLastFromSingle", testDropLastFromSingle),
769+
("testDropLastFromPair", testDropLastFromPair),
770+
("testDropLastFromTriple", testDropLastFromTriple),
771+
("testStartEndIndex", testStartEndIndex),
772+
("testIterator", testIterator),
773+
("testIndexing", testIndexing),
774+
("testCompare", testCompare),
775+
("testHashing", testHashing),
776+
("testEquality", testEquality),
777+
("testSubscripting", testSubscripting),
778+
("testAppending", testAppending),
779+
("testAppendEmpty", testAppendEmpty),
780+
("testAppendEmptyIndexPath", testAppendEmptyIndexPath),
781+
("testAppendManyIndexPath", testAppendManyIndexPath),
782+
("testAppendEmptyIndexPathToSingle", testAppendEmptyIndexPathToSingle),
783+
("testAppendSingleIndexPath", testAppendSingleIndexPath),
784+
("testAppendSingleIndexPathToSingle", testAppendSingleIndexPathToSingle),
785+
("testAppendPairIndexPath", testAppendPairIndexPath),
786+
("testAppendManyIndexPathToEmpty", testAppendManyIndexPathToEmpty),
787+
("testAppendByOperator", testAppendByOperator),
788+
("testAppendArray", testAppendArray),
789+
("testRanges", testRanges),
790+
("testRangeFromEmpty", testRangeFromEmpty),
791+
("testRangeFromSingle", testRangeFromSingle),
792+
("testRangeFromPair", testRangeFromPair),
793+
("testRangeFromMany", testRangeFromMany),
794+
("testRangeReplacementSingle", testRangeReplacementSingle),
795+
("testRangeReplacementPair", testRangeReplacementPair),
796+
("testMoreRanges", testMoreRanges),
797+
("testIteration", testIteration),
798+
("testDescription", testDescription),
799+
("testBridgeToObjC", testBridgeToObjC),
800+
("testForceBridgeFromObjC", testForceBridgeFromObjC),
801+
("testConditionalBridgeFromObjC", testConditionalBridgeFromObjC),
802+
("testUnconditionalBridgeFromObjC", testUnconditionalBridgeFromObjC),
803+
("testObjcBridgeType", testObjcBridgeType),
804+
("test_AnyHashableContainingIndexPath", test_AnyHashableContainingIndexPath),
805+
("test_AnyHashableCreatedFromNSIndexPath", test_AnyHashableCreatedFromNSIndexPath),
806+
("test_unconditionallyBridgeFromObjectiveC", test_unconditionallyBridgeFromObjectiveC),
807+
("test_slice_1ary", test_slice_1ary),
808+
("test_copy", test_copy),
809+
("testCodingRoundtrip", testCodingRoundtrip),
810+
("testLoadedValuesMatch", testLoadedValuesMatch),
811+
]
812+
}
813+
793814
}

0 commit comments

Comments
 (0)