Skip to content

Commit 544db73

Browse files
committed
Merge pull request #5 from RxSwiftCommunity/mt-0.1.3-changeset
[WIP]0.1.3 changeset observing
2 parents c7e7454 + 52a2e00 commit 544db73

File tree

5 files changed

+209
-35
lines changed

5 files changed

+209
-35
lines changed

Example/RxRealm_Tests/RxRealmTests.swift

+147-23
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ import RealmSwift
1111
import RxRealm
1212
import RxTests
1313

14+
func delay(delay: Double, closure: () -> Void) {
15+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))),
16+
dispatch_get_main_queue(), closure)
17+
}
18+
1419
class Message: Object, Equatable {
1520
dynamic var text = ""
1621
convenience init(_ text: String) {
@@ -25,25 +30,19 @@ func ==(lhs: Message, rhs: Message) -> Bool {
2530

2631
class RxRealm_Tests: XCTestCase {
2732

28-
var bag: DisposeBag! = DisposeBag()
29-
30-
override func setUp() {
31-
super.setUp()
32-
Realm.Configuration.defaultConfiguration.inMemoryIdentifier = "MemoryRealm"
33+
private func realmInMemory(name: String) -> Realm {
34+
var conf = Realm.Configuration()
35+
conf.inMemoryIdentifier = name
36+
return try! Realm(configuration: conf)
3337
}
3438

35-
override func tearDown() {
36-
bag = nil
37-
super.tearDown()
38-
}
39-
4039
private func clearRealm(realm: Realm) {
4140
try! realm.write {
4241
realm.deleteAll()
4342
}
4443
}
4544

46-
private func addObject(realm: Realm, text: String) {
45+
private func addMessage(realm: Realm, text: String) {
4746
try! realm.write {
4847
realm.add(Message(text))
4948
}
@@ -53,8 +52,9 @@ class RxRealm_Tests: XCTestCase {
5352
let expectation1 = expectationWithDescription("Results<Message> first")
5453
let expectation2 = expectationWithDescription("Results<Message> second")
5554

56-
let realm = try! Realm()
55+
let realm = realmInMemory(#function)
5756
clearRealm(realm)
57+
let bag = DisposeBag()
5858

5959
let scheduler = TestScheduler(initialClock: 0)
6060
let observer = scheduler.createObserver(Results<Message>)
@@ -70,27 +70,30 @@ class RxRealm_Tests: XCTestCase {
7070

7171
messages$.subscribe(observer).addDisposableTo(bag)
7272

73-
addObject(realm, text: "first")
73+
addMessage(realm, text: "first(Results)")
7474

75-
performSelector(#selector(addSecondMessage), withObject: nil, afterDelay: 0.1)
75+
delay(0.1) {
76+
self.addMessage(realm, text: "second(Results)")
77+
}
7678

7779
scheduler.start()
7880

7981
waitForExpectationsWithTimeout(0.5) {error in
8082
XCTAssertTrue(error == nil)
8183
XCTAssertEqual(observer.events.count, 2)
8284
let results = observer.events.last!.value.element!
83-
XCTAssertTrue(results.first! == Message("first"))
84-
XCTAssertTrue(results.last! == Message("second"))
85+
XCTAssertTrue(results.first! == Message("first(Results)"))
86+
XCTAssertTrue(results.last! == Message("second(Results)"))
8587
}
8688
}
8789

8890
func testEmittedArrayValues() {
8991
let expectation1 = expectationWithDescription("Array<Message> first")
9092
let expectation2 = expectationWithDescription("Array<Message> second")
9193

92-
let realm = try! Realm()
94+
let realm = realmInMemory(#function)
9395
clearRealm(realm)
96+
let bag = DisposeBag()
9497

9598
let scheduler = TestScheduler(initialClock: 0)
9699
let observer = scheduler.createObserver(Array<Message>)
@@ -106,21 +109,142 @@ class RxRealm_Tests: XCTestCase {
106109

107110
messages$.subscribe(observer).addDisposableTo(bag)
108111

109-
addObject(realm, text: "first")
112+
addMessage(realm, text: "first(Array)")
110113

111-
performSelector(#selector(addSecondMessage), withObject: nil, afterDelay: 0.1)
114+
delay(0.1) {
115+
self.addMessage(realm, text: "second(Array)")
116+
}
112117

113118
scheduler.start()
114119

115120
waitForExpectationsWithTimeout(0.5) {error in
116121
XCTAssertTrue(error == nil)
117122
XCTAssertEqual(observer.events.count, 2)
118-
XCTAssertTrue(observer.events.first!.value.element! == [Message("first")])
119-
XCTAssertTrue(observer.events.last!.value.element! == [Message("first"), Message("second")])
123+
XCTAssertTrue(observer.events.first!.value.element! == [Message("first(Array)")])
124+
XCTAssertTrue(observer.events.last!.value.element! == [Message("first(Array)"), Message("second(Array)")])
120125
}
121126
}
122127

123-
func addSecondMessage() {
124-
addObject(try! Realm(), text: "second")
128+
func testEmittedChangeset() {
129+
let expectation1 = expectationWithDescription("did emit all changeset values")
130+
131+
let realm = realmInMemory(#function)
132+
clearRealm(realm)
133+
let bag = DisposeBag()
134+
135+
let scheduler = TestScheduler(initialClock: 0)
136+
let observer = scheduler.createObserver(String)
137+
138+
//initial data
139+
addMessage(realm, text: "first(Changeset)")
140+
141+
let messages$ = realm.objects(Message).asObservableChangeset().shareReplay(1)
142+
messages$.scan(0) { count, _ in
143+
return count+1
144+
}
145+
.filter {$0 == 3}
146+
.subscribeNext {_ in expectation1.fulfill() }
147+
.addDisposableTo(bag)
148+
149+
messages$
150+
.map {result, changes in
151+
if let changes = changes {
152+
return "count:\(result.count) inserted:\(changes.inserted) deleted:\(changes.deleted) updated:\(changes.updated)"
153+
} else {
154+
return "count:\(result.count)"
155+
}
156+
}
157+
.subscribe(observer).addDisposableTo(bag)
158+
159+
//insert
160+
delay(0.25) {
161+
self.addMessage(realm, text: "second(Changeset)")
162+
}
163+
//update
164+
delay(0.5) {
165+
try! realm.write {
166+
realm.delete(realm.objects(Message).filter("text='first(Changeset)'").first!)
167+
realm.objects(Message).filter("text='second(Changeset)'").first!.text = "third(Changeset)"
168+
}
169+
}
170+
//coalesced
171+
delay(0.7) {
172+
self.addMessage(realm, text: "first(Changeset)")
173+
}
174+
delay(0.7) {
175+
try! realm.write {
176+
realm.delete(realm.objects(Message).filter("text='first(Changeset)'").first!)
177+
}
178+
}
179+
180+
waitForExpectationsWithTimeout(0.75) {error in
181+
XCTAssertTrue(error == nil)
182+
XCTAssertEqual(observer.events.count, 3)
183+
XCTAssertEqual(observer.events[0].value.element!, "count:1")
184+
XCTAssertEqual(observer.events[1].value.element!, "count:2 inserted:[1] deleted:[] updated:[]")
185+
XCTAssertEqual(observer.events[2].value.element!, "count:1 inserted:[] deleted:[0] updated:[1]")
186+
}
187+
}
188+
189+
func testEmittedArrayChangeset() {
190+
let expectation1 = expectationWithDescription("did emit all array changeset values")
191+
192+
let realm = realmInMemory(#function)
193+
clearRealm(realm)
194+
let bag = DisposeBag()
195+
196+
let scheduler = TestScheduler(initialClock: 0)
197+
let observer = scheduler.createObserver(String)
198+
199+
//initial data
200+
addMessage(realm, text: "first(ArrayChangeset)")
201+
202+
let messages$ = realm.objects(Message).asObservableArrayChangeset().shareReplay(1)
203+
messages$.scan(0) { count, _ in
204+
return count+1
205+
}
206+
.filter {$0 == 3}
207+
.subscribeNext {_ in expectation1.fulfill() }
208+
.addDisposableTo(bag)
209+
210+
messages$
211+
.map {result, changes in
212+
if let changes = changes {
213+
return "count:\(result.count) inserted:\(changes.inserted) deleted:\(changes.deleted) updated:\(changes.updated)"
214+
} else {
215+
return "count:\(result.count)"
216+
}
217+
}
218+
.subscribe(observer).addDisposableTo(bag)
219+
220+
//insert
221+
delay(0.25) {
222+
self.addMessage(realm, text: "second(ArrayChangeset)")
223+
}
224+
//update
225+
delay(0.5) {
226+
try! realm.write {
227+
realm.delete(realm.objects(Message).filter("text='first(ArrayChangeset)'").first!)
228+
realm.objects(Message).filter("text='second(ArrayChangeset)'").first!.text = "third(ArrayChangeset)"
229+
}
230+
}
231+
//coalesced
232+
delay(0.7) {
233+
self.addMessage(realm, text: "first(ArrayChangeset)")
234+
}
235+
delay(0.7) {
236+
try! realm.write {
237+
realm.delete(realm.objects(Message).filter("text='first(ArrayChangeset)'").first!)
238+
}
239+
}
240+
241+
waitForExpectationsWithTimeout(0.75) {error in
242+
XCTAssertTrue(error == nil)
243+
XCTAssertEqual(observer.events.count, 3)
244+
XCTAssertEqual(observer.events[0].value.element!, "count:1")
245+
XCTAssertEqual(observer.events[1].value.element!, "count:2 inserted:[1] deleted:[] updated:[]")
246+
XCTAssertEqual(observer.events[2].value.element!, "count:1 inserted:[] deleted:[0] updated:[1]")
247+
}
125248
}
249+
126250
}

Pod/Classes/RxRealm.swift

+31
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ extension List: NotificationEmitter {}
1616
extension AnyRealmCollection: NotificationEmitter {}
1717
extension Results: NotificationEmitter {}
1818

19+
public struct RealmChangeset {
20+
public let deleted: [Int]
21+
public let inserted: [Int]
22+
public let updated: [Int]
23+
}
24+
1925
public extension NotificationEmitter where Self: RealmCollectionType {
2026

2127
public func asObservable() -> Observable<Self> {
@@ -48,4 +54,29 @@ public extension NotificationEmitter where Self: RealmCollectionType {
4854
public func asObservableArray() -> Observable<Array<Self.Generator.Element>> {
4955
return asObservable().map { Array($0) }
5056
}
57+
58+
public func asObservableChangeset() -> Observable<(Self, RealmChangeset?)> {
59+
return Observable.create {observer in
60+
let token = self.addNotificationBlock {changeset in
61+
62+
switch changeset {
63+
case .Initial(let value):
64+
observer.onNext((value, nil))
65+
case .Update(let value, let deletes, let inserts, let updates):
66+
observer.onNext((value, RealmChangeset(deleted: deletes, inserted: inserts, updated: updates)))
67+
case .Error(let error):
68+
observer.onError(error)
69+
return
70+
}
71+
}
72+
73+
return AnonymousDisposable {
74+
token.stop()
75+
}
76+
}
77+
}
78+
79+
public func asObservableArrayChangeset() -> Observable<(Array<Self.Generator.Element>, RealmChangeset?)> {
80+
return asObservableChangeset().map { (Array($0), $1) }
81+
}
5182
}

README.md

+30-10
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88

99
This library is a very thin wrapper around the reactive collection types __RealmSwift__ provides: `Results`, `List` and `AnyRealmCollection`.
1010

11-
The extension adds two methods to all of the above:
11+
The extension adds these methods to all of the above:
1212

13-
### asObservable()
13+
#### asObservable()
1414
`asObservable()` - emits every time the collection changes:
1515

1616
```swift
@@ -22,7 +22,7 @@ realm.objects(Lap).asObservable()
2222
}
2323
```
2424

25-
### asObservableArray()
25+
#### asObservableArray()
2626
`asObservableArray()` - fetches the a snapshot of a Realm collection and converts it to an array value (for example if you want to use array methods on the collection):
2727

2828
```swift
@@ -36,29 +36,48 @@ realm.objects(Lap).asObservableArray()
3636
}
3737
```
3838

39+
#### asObservableChangeset()
40+
`asObservableChangeset()` - emits every time the collection changes and provides the exact indexes that has been deleted, inserted or updated:
3941

40-
## Example app
42+
```swift
43+
let realm = try! Realm()
44+
realm.objects(Lap).asObservableChangeset()
45+
.subscribeNext {result, changes in
46+
if let changes = changes {
47+
//it's an update
48+
print(result)
49+
print("deleted: \(changes.deleted) inserted: \(changes.inserted) updated: \(changes.updated)")
50+
} else {
51+
//it's the initial data
52+
print(result)
53+
}
54+
}
55+
```
4156

42-
To run the example project, clone the repo, and run `pod install` from the Example directory first. The app uses RxSwift, RxCocoa using RealmSwift, RxRealm to observe Results from Realm.
57+
#### asObservableArrayChangeset()
58+
59+
`asObservableArrayChangeset()` combines the result of `asObservableArray()` and `asObservableChangeset()` returning an `Observable<Array<T>, RealmChangeset?>`.
4360

44-
## Requirements
61+
#### Example app
4562

46-
This library depends on both __RxSwift__ and __RealmSwift__.
63+
To run the example project, clone the repo, and run `pod install` from the Example directory first. The app uses RxSwift, RxCocoa using RealmSwift, RxRealm to observe Results from Realm.
4764

4865
## Installation
4966

50-
### CocoaPods
67+
This library depends on both __RxSwift__ and __RealmSwift__ 0.99+.
68+
69+
#### CocoaPods
5170
RxRealm is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your Podfile:
5271

5372
```ruby
5473
pod "RxRealm"
5574
```
5675

57-
### Carthage
76+
#### Carthage
5877

5978
Feel free to send a PR
6079

61-
### As Source
80+
#### As Source
6281

6382
You can grab the __RxRealm.swift__ file from this repo and include it in your project.
6483

@@ -71,6 +90,7 @@ This library belongs to _RxSwiftCommunity_ and is based on the work of [@fpillet
7190
* Carthage
7291
* Add `asObservable()` to the Realm class
7392
* Test add platforms and add compatibility for the pod
93+
* Document the source code
7494

7595
## License
7696

RxRealm.podspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Pod::Spec.new do |s|
22

33
s.name = "RxRealm"
4-
s.version = "0.1.2"
4+
s.version = "0.1.3"
55
s.summary = "An Rx wrapper of Realm's collection type"
66

77
s.description = <<-DESC

_Pods.xcodeproj

-1
This file was deleted.

0 commit comments

Comments
 (0)