Skip to content

Commit ec4087a

Browse files
committed
Add notification based context observation
1 parent 4ae5b55 commit ec4087a

14 files changed

Lines changed: 627 additions & 110 deletions

Example/RxCoreData.xcodeproj/project.pbxproj

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
3F313EA6210C94FB00D9D0F8 /* NSManagedObjectContext+ObserveObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F9F75E4210C948B00E834AD /* NSManagedObjectContext+ObserveObjectTests.swift */; };
11+
3F8A3384210C5BCD00250BCB /* Contacts.xcdatamodel in Sources */ = {isa = PBXBuildFile; fileRef = 3F8A3383210C5BCD00250BCB /* Contacts.xcdatamodel */; };
12+
3F8A3385210C5CBE00250BCB /* Bundle+Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8A337C210C5AD900250BCB /* Bundle+Test.swift */; };
13+
3F8A3386210C5CC100250BCB /* NSManagedObject+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8A337E210C5AFB00250BCB /* NSManagedObject+Extensions.swift */; };
14+
3F8A3387210C5CC500250BCB /* NSManagedObjectContext+Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8A3380210C5B1000250BCB /* NSManagedObjectContext+Test.swift */; };
15+
3F9F75E3210C939800E834AD /* NSManagedObjectContext+ObserveContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F9F75E1210C935900E834AD /* NSManagedObjectContext+ObserveContextTests.swift */; };
1016
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; };
1117
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; };
1218
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; };
1319
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; };
14-
607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; };
1520
75696D6E47EF4301137E83D3 /* Pods_RxCoreData_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D9B39454206D1A0C6433F6A /* Pods_RxCoreData_Tests.framework */; };
1621
CF5023D00F002ADEF58941A0 /* Pods_RxCoreData_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6C2097B9C8F7CECFD35D972D /* Pods_RxCoreData_Example.framework */; };
1722
D2AE78A51CF32FBA00D8411E /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2AE78A31CF32FBA00D8411E /* Event.swift */; };
@@ -33,6 +38,12 @@
3338
/* Begin PBXFileReference section */
3439
1312AE6406B7B9929CF4EE12 /* Pods-RxCoreData_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxCoreData_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-RxCoreData_Example/Pods-RxCoreData_Example.release.xcconfig"; sourceTree = "<group>"; };
3540
3D9B39454206D1A0C6433F6A /* Pods_RxCoreData_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RxCoreData_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
41+
3F8A337C210C5AD900250BCB /* Bundle+Test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Test.swift"; sourceTree = "<group>"; };
42+
3F8A337E210C5AFB00250BCB /* NSManagedObject+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Extensions.swift"; sourceTree = "<group>"; };
43+
3F8A3380210C5B1000250BCB /* NSManagedObjectContext+Test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Test.swift"; sourceTree = "<group>"; };
44+
3F8A3383210C5BCD00250BCB /* Contacts.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Contacts.xcdatamodel; sourceTree = "<group>"; };
45+
3F9F75E1210C935900E834AD /* NSManagedObjectContext+ObserveContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+ObserveContextTests.swift"; sourceTree = "<group>"; };
46+
3F9F75E4210C948B00E834AD /* NSManagedObjectContext+ObserveObjectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+ObserveObjectTests.swift"; sourceTree = "<group>"; };
3647
607FACD01AFB9204008FA782 /* RxCoreData_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RxCoreData_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
3748
607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
3849
607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@@ -41,7 +52,6 @@
4152
607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
4253
607FACE51AFB9204008FA782 /* RxCoreData_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RxCoreData_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4354
607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
44-
607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = "<group>"; };
4555
609891BD0E152693BAEB65CA /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
4656
694D2F970D4D6C2D5B237BCA /* LICENSE.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = LICENSE.md; path = ../LICENSE.md; sourceTree = "<group>"; };
4757
6C2097B9C8F7CECFD35D972D /* Pods_RxCoreData_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RxCoreData_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -75,6 +85,24 @@
7585
/* End PBXFrameworksBuildPhase section */
7686

7787
/* Begin PBXGroup section */
88+
3F8A337B210C5AC000250BCB /* Utils */ = {
89+
isa = PBXGroup;
90+
children = (
91+
3F8A337C210C5AD900250BCB /* Bundle+Test.swift */,
92+
3F8A337E210C5AFB00250BCB /* NSManagedObject+Extensions.swift */,
93+
3F8A3380210C5B1000250BCB /* NSManagedObjectContext+Test.swift */,
94+
);
95+
name = Utils;
96+
sourceTree = "<group>";
97+
};
98+
3F8A3382210C5BB100250BCB /* Resources */ = {
99+
isa = PBXGroup;
100+
children = (
101+
3F8A3383210C5BCD00250BCB /* Contacts.xcdatamodel */,
102+
);
103+
name = Resources;
104+
sourceTree = "<group>";
105+
};
78106
607FACC71AFB9204008FA782 = {
79107
isa = PBXGroup;
80108
children = (
@@ -124,8 +152,11 @@
124152
607FACE81AFB9204008FA782 /* Tests */ = {
125153
isa = PBXGroup;
126154
children = (
127-
607FACEB1AFB9204008FA782 /* Tests.swift */,
155+
3F8A3382210C5BB100250BCB /* Resources */,
156+
3F8A337B210C5AC000250BCB /* Utils */,
128157
607FACE91AFB9204008FA782 /* Supporting Files */,
158+
3F9F75E1210C935900E834AD /* NSManagedObjectContext+ObserveContextTests.swift */,
159+
3F9F75E4210C948B00E834AD /* NSManagedObjectContext+ObserveObjectTests.swift */,
129160
);
130161
path = Tests;
131162
sourceTree = "<group>";
@@ -500,7 +531,12 @@
500531
isa = PBXSourcesBuildPhase;
501532
buildActionMask = 2147483647;
502533
files = (
503-
607FACEC1AFB9204008FA782 /* Tests.swift in Sources */,
534+
3F8A3384210C5BCD00250BCB /* Contacts.xcdatamodel in Sources */,
535+
3F9F75E3210C939800E834AD /* NSManagedObjectContext+ObserveContextTests.swift in Sources */,
536+
3F8A3386210C5CC100250BCB /* NSManagedObject+Extensions.swift in Sources */,
537+
3F313EA6210C94FB00D9D0F8 /* NSManagedObjectContext+ObserveObjectTests.swift in Sources */,
538+
3F8A3385210C5CBE00250BCB /* Bundle+Test.swift in Sources */,
539+
3F8A3387210C5CC500250BCB /* NSManagedObjectContext+Test.swift in Sources */,
504540
);
505541
runOnlyForDeploymentPostprocessing = 0;
506542
};

Example/Tests/Bundle+Test.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// Bundle+Test.swift
3+
// RxCoreData_Example
4+
//
5+
// Created by Evghenii Nicolaev on 7/28/18.
6+
// Copyright © 2018 CocoaPods. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
private class Test {}
12+
13+
extension Bundle {
14+
static var test: Bundle {
15+
return Bundle(for: Test.self)
16+
}
17+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14133" systemVersion="17E199" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
3+
<entity name="Contact" representedClassName="Contact" syncable="YES" codeGenerationType="class">
4+
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
5+
<relationship name="phoneNumbers" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="PhoneNumber" syncable="YES"/>
6+
</entity>
7+
<entity name="Group" representedClassName="Group" syncable="YES" codeGenerationType="class">
8+
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
9+
<relationship name="contacts" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Contact" syncable="YES"/>
10+
</entity>
11+
<entity name="PhoneNumber" representedClassName="PhoneNumber" syncable="YES" codeGenerationType="class">
12+
<attribute name="phoneNumber" optional="YES" attributeType="String" syncable="YES"/>
13+
<attribute name="title" optional="YES" attributeType="String" syncable="YES"/>
14+
</entity>
15+
<elements>
16+
<element name="Contact" positionX="-54" positionY="-9" width="128" height="75"/>
17+
<element name="Group" positionX="-63" positionY="-18" width="128" height="75"/>
18+
<element name="PhoneNumber" positionX="-54" positionY="9" width="128" height="75"/>
19+
</elements>
20+
</model>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// NSManagedObject+Extensions.swift
3+
// RxCoreData_Example
4+
//
5+
// Created by Evghenii Nicolaev on 7/28/18.
6+
// Copyright © 2018 CocoaPods. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import CoreData
11+
12+
extension NSManagedObject {
13+
14+
class func new(in managedObjectContext: NSManagedObjectContext) -> Self {
15+
return generateObject(type: self, in: managedObjectContext)
16+
}
17+
18+
private class func generateObject<T>(type: T.Type, in managedObjectContext: NSManagedObjectContext) -> T {
19+
return NSEntityDescription.insertNewObject(forEntityName: String(describing: self), into: managedObjectContext) as! T
20+
}
21+
22+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
//
2+
// NSManagedObjectContext+ObserveContextTests.swift
3+
// RxCoreData_Example
4+
//
5+
// Created by Evghenii Nicolaev on 7/28/18.
6+
// Copyright © 2018 CocoaPods. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import XCTest
11+
import RxSwift
12+
import RxCoreData
13+
import CoreData
14+
15+
class NSManagedObjectContext_ObserveContextTests: XCTestCase {
16+
17+
var testMOC: NSManagedObjectContext!
18+
var disposeBag: DisposeBag!
19+
20+
// MARK: - Setup
21+
22+
override func setUp() {
23+
super.setUp()
24+
disposeBag = DisposeBag()
25+
testMOC = NSManagedObjectContext.test
26+
}
27+
28+
override func tearDown() {
29+
testMOC = nil
30+
disposeBag = nil
31+
super.tearDown()
32+
}
33+
34+
// MARK: - Tests
35+
36+
func testObserveContext_insertion() {
37+
let insertionExpectation = expectation(description: "Expect to get insert event")
38+
39+
testMOC.rx.changes().take(1).subscribe(onNext: { changeEvent in
40+
XCTAssertEqual(changeEvent.deleted.count, 0)
41+
XCTAssertEqual(changeEvent.updated.count, 0)
42+
XCTAssertEqual(changeEvent.refreshed.count, 0)
43+
44+
XCTAssertEqual(changeEvent.inserted.count, 1)
45+
let insertedGroup = changeEvent.inserted.first as? Group
46+
XCTAssertEqual(insertedGroup?.name, "Test group")
47+
48+
insertionExpectation.fulfill()
49+
}).disposed(by: disposeBag)
50+
51+
let group = Group.new(in: testMOC)
52+
group.name = "Test group"
53+
54+
waitForExpectations(timeout: 1.0, handler: nil)
55+
}
56+
57+
func testObserveContext_update() {
58+
let insertionExpectation = expectation(description: "Expect to get update event")
59+
60+
let group = Group.new(in: testMOC)
61+
group.name = "Test group"
62+
try! testMOC.save()
63+
64+
testMOC.rx.changes().take(1).subscribe(onNext: { changeEvent in
65+
XCTAssertEqual(changeEvent.inserted.count, 0)
66+
XCTAssertEqual(changeEvent.deleted.count, 0)
67+
XCTAssertEqual(changeEvent.refreshed.count, 0)
68+
69+
XCTAssertEqual(changeEvent.updated.count, 1)
70+
let updatedGroup = changeEvent.updated.first as? Group
71+
XCTAssertEqual(updatedGroup?.name, "Updated test group")
72+
73+
insertionExpectation.fulfill()
74+
}).disposed(by: disposeBag)
75+
76+
group.name = "Updated test group"
77+
78+
waitForExpectations(timeout: 1.0, handler: nil)
79+
}
80+
81+
func testObserveContext_delete() {
82+
let insertionExpectation = expectation(description: "Expect to get delete event")
83+
84+
let group = Group.new(in: testMOC)
85+
group.name = "Test group"
86+
try! testMOC.save()
87+
88+
testMOC.rx.changes().take(1).subscribe(onNext: { changeEvent in
89+
XCTAssertEqual(changeEvent.inserted.count, 0)
90+
XCTAssertEqual(changeEvent.updated.count, 0)
91+
XCTAssertEqual(changeEvent.refreshed.count, 0)
92+
93+
XCTAssertEqual(changeEvent.deleted.count, 1)
94+
let deletedGroup = changeEvent.deleted.first as? Group
95+
XCTAssertEqual(deletedGroup?.name, "Test group")
96+
97+
insertionExpectation.fulfill()
98+
}).disposed(by: disposeBag)
99+
100+
testMOC.delete(group)
101+
102+
waitForExpectations(timeout: 1.0, handler: nil)
103+
}
104+
105+
func testObserveContext_refresh() {
106+
let insertionExpectation = expectation(description: "Expect to get refresh event")
107+
108+
let group = Group.new(in: testMOC)
109+
group.name = "Test group"
110+
try! testMOC.save()
111+
112+
testMOC.rx.changes().take(1).subscribe(onNext: { changeEvent in
113+
XCTAssertEqual(changeEvent.inserted.count, 0)
114+
XCTAssertEqual(changeEvent.updated.count, 0)
115+
XCTAssertEqual(changeEvent.deleted.count, 0)
116+
117+
XCTAssertEqual(changeEvent.refreshed.count, 1)
118+
let refreshedGroup = changeEvent.refreshed.first as? Group
119+
XCTAssertEqual(refreshedGroup?.name, "Test group")
120+
121+
insertionExpectation.fulfill()
122+
}).disposed(by: disposeBag)
123+
124+
testMOC.refreshAllObjects()
125+
126+
waitForExpectations(timeout: 1.0, handler: nil)
127+
}
128+
129+
}

0 commit comments

Comments
 (0)