Skip to content
This repository was archived by the owner on May 26, 2020. It is now read-only.

Commit acae769

Browse files
dmcrodriguesandersio
authored andcommitted
Introduce Change to represent inserts/removes and Update for replaces
1 parent aa6d405 commit acae769

File tree

4 files changed

+299
-224
lines changed

4 files changed

+299
-224
lines changed

ReactiveCollections.xcodeproj/project.pbxproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
7DE06DDF1DFADD9B003303AB /* ReactiveCollections.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7DE06DBD1DFADCAE003303AB /* ReactiveCollections.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4545
7DE06DE01DFADDA0003303AB /* ReactiveSwift.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7DE06DD41DFADCE4003303AB /* ReactiveSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4646
7DE06DE11DFADDA0003303AB /* Result.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7DE06DD51DFADCE4003303AB /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
47+
7DF60EED1E007DEF0096283B /* Changeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DF60EEC1E007DEF0096283B /* Changeset.swift */; };
48+
7DF60EEE1E007DEF0096283B /* Changeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DF60EEC1E007DEF0096283B /* Changeset.swift */; };
49+
7DF60EEF1E007DEF0096283B /* Changeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DF60EEC1E007DEF0096283B /* Changeset.swift */; };
50+
7DF60EF01E007DEF0096283B /* Changeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DF60EEC1E007DEF0096283B /* Changeset.swift */; };
4751
/* End PBXBuildFile section */
4852

4953
/* Begin PBXContainerItemProxy section */
@@ -130,6 +134,7 @@
130134
7DE06DC51DFADCAF003303AB /* ReactiveCollectionsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactiveCollectionsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
131135
7DE06DD41DFADCE4003303AB /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = Carthage/Build/tvOS/ReactiveSwift.framework; sourceTree = "<group>"; };
132136
7DE06DD51DFADCE4003303AB /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/tvOS/Result.framework; sourceTree = "<group>"; };
137+
7DF60EEC1E007DEF0096283B /* Changeset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Changeset.swift; sourceTree = "<group>"; };
133138
/* End PBXFileReference section */
134139

135140
/* Begin PBXFrameworksBuildPhase section */
@@ -247,6 +252,7 @@
247252
children = (
248253
7D69AAE01DF9CEE500FCB568 /* ReactiveCollections.h */,
249254
7D69AAF71DF9D07800FCB568 /* ReactiveArray.swift */,
255+
7DF60EEC1E007DEF0096283B /* Changeset.swift */,
250256
7D3D8BE41DF9EAE000E90921 /* Supporting Files */,
251257
);
252258
name = ReactiveCollections;
@@ -600,6 +606,7 @@
600606
buildActionMask = 2147483647;
601607
files = (
602608
7D69AAF81DF9D07800FCB568 /* ReactiveArray.swift in Sources */,
609+
7DF60EED1E007DEF0096283B /* Changeset.swift in Sources */,
603610
);
604611
runOnlyForDeploymentPostprocessing = 0;
605612
};
@@ -616,6 +623,7 @@
616623
buildActionMask = 2147483647;
617624
files = (
618625
7DC1E2DA1DFADF9B00A61745 /* ReactiveArray.swift in Sources */,
626+
7DF60EF01E007DEF0096283B /* Changeset.swift in Sources */,
619627
);
620628
runOnlyForDeploymentPostprocessing = 0;
621629
};
@@ -624,6 +632,7 @@
624632
buildActionMask = 2147483647;
625633
files = (
626634
7DE06DAB1DFADB0F003303AB /* ReactiveArray.swift in Sources */,
635+
7DF60EEE1E007DEF0096283B /* Changeset.swift in Sources */,
627636
);
628637
runOnlyForDeploymentPostprocessing = 0;
629638
};
@@ -640,6 +649,7 @@
640649
buildActionMask = 2147483647;
641650
files = (
642651
7DE06DDA1DFADD69003303AB /* ReactiveArray.swift in Sources */,
652+
7DF60EEF1E007DEF0096283B /* Changeset.swift in Sources */,
643653
);
644654
runOnlyForDeploymentPostprocessing = 0;
645655
};

Sources/Changeset.swift

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import Foundation
2+
3+
public enum Change<T> {
4+
case item(T, at: Int)
5+
case items([T], range: CountableRange<Int>)
6+
}
7+
8+
extension Change where T: Equatable {
9+
10+
public static func ==(lhs: Change<T>, rhs: Change<T>) -> Bool {
11+
switch (lhs, rhs) {
12+
case let (.item(leftItem, leftIndex), .item(rightItem, rightIndex)):
13+
return leftIndex == rightIndex
14+
&& leftItem == rightItem
15+
case let (.items(leftItems, leftRange), .items(rightItems, rightRange)):
16+
return leftRange == rightRange
17+
&& leftItems == rightItems
18+
default:
19+
return false
20+
}
21+
}
22+
23+
// TODO: Keep while we haven't `SE-0143: Conditional conformances` (expected in Swift 4)
24+
fileprivate static func equalChanges(_ lhs: [Change<T>], _ rhs: [Change<T>]) -> Bool {
25+
guard lhs.count == rhs.count else { return false }
26+
27+
return zip(lhs, rhs)
28+
.map(==)
29+
.reduce(true, { $0 && $1 })
30+
}
31+
}
32+
33+
public enum Update<T> {
34+
case item(old: T, new: T, at: Int)
35+
case items(old: [T], new: [T], range: CountableRange<Int>)
36+
}
37+
38+
extension Update where T: Equatable {
39+
40+
public static func ==(lhs: Update<T>, rhs: Update<T>) -> Bool {
41+
switch (lhs, rhs) {
42+
case let (.item(leftOldItem, leftNewItem, leftIndex), .item(rightOldItem, rightNewItem, rightIndex)):
43+
return leftIndex == rightIndex
44+
&& leftOldItem == rightOldItem
45+
&& leftNewItem == rightNewItem
46+
case let (.items(leftOldItems, leftNewItems, leftRange), .items(rightOldItems, rightNewItems, rightRange)):
47+
return leftRange == rightRange
48+
&& leftOldItems == rightOldItems
49+
&& leftNewItems == rightNewItems
50+
default:
51+
return false
52+
}
53+
}
54+
55+
// TODO: Keep while we haven't `SE-0143: Conditional conformances` (expected in Swift 4)
56+
fileprivate static func equalUpdates(_ lhs: [Update<T>], _ rhs: [Update<T>]) -> Bool {
57+
guard lhs.count == rhs.count else { return false }
58+
59+
return zip(lhs, rhs)
60+
.map(==)
61+
.reduce(true, { $0 && $1 })
62+
}
63+
}
64+
65+
public struct Changeset<T> {
66+
public let inserts: [Change<T>]
67+
public let removes: [Change<T>]
68+
public let updates: [Update<T>]
69+
}
70+
71+
extension Changeset where T: Equatable {
72+
73+
public static func ==(lhs: Changeset<T>, rhs: Changeset<T>) -> Bool {
74+
guard
75+
lhs.inserts.count == rhs.inserts.count,
76+
lhs.removes.count == rhs.removes.count,
77+
lhs.updates.count == rhs.updates.count
78+
else { return false }
79+
80+
return Change.equalChanges(lhs.inserts, rhs.inserts)
81+
&& Change.equalChanges(lhs.removes, rhs.removes)
82+
&& Update.equalUpdates(lhs.updates, rhs.updates)
83+
}
84+
}
85+
86+
extension Changeset {
87+
88+
internal static func generate<C>(
89+
insert: (items: C, range: Range<Int>)?,
90+
remove: (items: C, range: Range<Int>)?
91+
) -> Changeset<T> where C: Collection, C.Iterator.Element == T {
92+
93+
precondition(insert != nil || remove != nil)
94+
95+
return Changeset(
96+
inserts: insert.flatMap(changes) ?? [],
97+
removes: remove.flatMap(changes) ?? [],
98+
updates: []
99+
)
100+
}
101+
102+
private static func changes<C>(
103+
elements: C,
104+
at range: Range<Int>
105+
) -> [Change<T>] where C: Collection, C.Iterator.Element == T {
106+
107+
switch elements.count {
108+
case 0: return []
109+
case 1: return [.item(elements.first!, at: range.lowerBound)]
110+
default:
111+
let items = Array(elements)
112+
let range = range.count != items.count
113+
? range.lowerBound..<range.lowerBound.advanced(by: items.count)
114+
: CountableRange(range)
115+
116+
assert(items.count == range.count)
117+
118+
return [.items(items, range: range)]
119+
}
120+
}
121+
}

Sources/ReactiveArray.swift

Lines changed: 11 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,6 @@ import Foundation
22
import ReactiveSwift
33
import Result
44

5-
// MARK: - Changeset
6-
7-
public struct Insert<Element> {
8-
public let element: Element
9-
public let index: Int
10-
11-
public init(element: Element, at index: Int) {
12-
self.element = element
13-
self.index = index
14-
}
15-
}
16-
17-
public struct Remove<Element> {
18-
public let element: Element
19-
public let index: Int
20-
21-
public init(element: Element, at index: Int) {
22-
self.element = element
23-
self.index = index
24-
}
25-
}
26-
27-
public struct Changeset<Element> {
28-
public let deletions: [Remove<Element>]
29-
public let insertions: [Insert<Element>]
30-
31-
public init(deletions: [Remove<Element>] = [], insertions: [Insert<Element>] = []) {
32-
self.deletions = deletions
33-
self.insertions = insertions
34-
}
35-
}
36-
37-
// MARK: - ReactiveArray
38-
395
public final class ReactiveArray<Element> {
406

417
fileprivate var elements: ContiguousArray<Element>
@@ -49,8 +15,9 @@ public final class ReactiveArray<Element> {
4915
guard let `self` = self else { return .failure(NSError()) }
5016

5117
return .success(
52-
Changeset(insertions:
53-
self.changes(inserting: self[self.indices], at: Range(self.indices))
18+
Changeset.generate(
19+
insert: (items: self[self.indices], range: Range(self.indices)),
20+
remove: nil
5421
)
5522
)
5623

@@ -156,9 +123,12 @@ extension ReactiveArray: RangeReplaceableCollection {
156123

157124
public func removeAll(keepingCapacity keepCapacity: Bool = false) {
158125
if keepCapacity {
159-
removeSubrange(startIndex..<endIndex)
126+
removeSubrange(indices)
160127
} else {
161-
let changeset = Changeset(deletions: changes(removing: elements, at: Range(indices)))
128+
let changeset = Changeset.generate(
129+
insert: nil,
130+
remove: (items: self[indices], range: Range(indices))
131+
)
162132

163133
elements.removeAll()
164134

@@ -218,9 +188,9 @@ extension ReactiveArray: RangeReplaceableCollection {
218188

219189
public func replaceSubrange<C>(_ subrange: Range<Int>, with newElements: C) where C: Collection, C.Iterator.Element == Element {
220190

221-
let changeset = Changeset(
222-
deletions: changes(removing: elements[subrange], at: subrange),
223-
insertions: changes(inserting: newElements, at: subrange)
191+
let changeset = Changeset.generate(
192+
insert: (items: ArraySlice(newElements), range: subrange),
193+
remove: (items: elements[subrange], range: subrange)
224194
)
225195

226196
elements.replaceSubrange(subrange, with: newElements)
@@ -231,28 +201,4 @@ extension ReactiveArray: RangeReplaceableCollection {
231201
public func reserveCapacity(_ n: Int) {
232202
elements.reserveCapacity(n)
233203
}
234-
235-
}
236-
237-
// MARK: - Helpers
238-
239-
extension ReactiveArray {
240-
241-
fileprivate func changes<C>(removing elements: C, at subrange: Range<Int>) -> [Remove<Element>] where C: Collection, C.Iterator.Element == Element {
242-
return elements
243-
.enumerated()
244-
.map { ($0.advanced(by: subrange.lowerBound), $1) }
245-
.map(flip(Remove.init))
246-
}
247-
248-
fileprivate func changes<C>(inserting elements: C, at subrange: Range<Int>) -> [Insert<Element>] where C: Collection, C.Iterator.Element == Element {
249-
return elements
250-
.enumerated()
251-
.map { ($0.advanced(by: subrange.lowerBound), $1) }
252-
.map(flip(Insert.init))
253-
}
254-
}
255-
256-
private func flip<T, U, V>(_ function: @escaping (T, U) -> V) -> (U, T) -> V {
257-
return { function($1, $0) }
258204
}

0 commit comments

Comments
 (0)