├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Combine-Realm.podspec
├── CombineRealm.png
├── Example
├── Example.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
└── Example
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ ├── Models.swift
│ ├── SceneDelegate.swift
│ └── ViewController.swift
├── LICENSE.md
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
└── CombineRealm
│ ├── Add.swift
│ ├── Delete.swift
│ └── Publishers.swift
└── Tests
├── CombineRealmTests
├── CombineRealmLinkingObjectTests.swift
├── CombineRealmListTests.swift
├── CombineRealmObjectTests.swift
├── CombineRealmResultsTests.swift
├── CombineRealmTests.swift
├── CombineTests.swift
├── Models.swift
├── Utilities.swift
└── XCTestManifests.swift
└── LinuxMain.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 | # Pods/
50 |
51 | # Carthage
52 | #
53 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
54 | # Carthage/Checkouts
55 |
56 | Carthage/Build
57 |
58 | # fastlane
59 | #
60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
61 | # screenshots whenever they are needed.
62 | # For more information about the recommended setup visit:
63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
64 |
65 | fastlane/report.xml
66 | fastlane/Preview.html
67 | fastlane/screenshots/**/*.png
68 | fastlane/test_output
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Combine-Realm.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 |
3 | s.name = "Combine-Realm"
4 | s.version = "2.0.1"
5 | s.summary = "A Combine wrapper of Realm's notifications and write bindings"
6 |
7 | s.description = <<-DESC
8 | This is a Combine extension that provides an easy to use way to use Realm's natively reactive collection types as a Publishers
9 | DESC
10 |
11 | s.homepage = "https://github.com/CombineCommunity/CombineRealm.git"
12 | s.license = 'MIT'
13 | s.author = { "Istvan Kreisz" => "kreiszdev@gmail.com", "Combine Community" => "cocoapods@combine.community" }
14 | s.source = { :git => "https://github.com/CombineCommunity/CombineRealm.git", :tag => s.version.to_s }
15 | s.source_files = 'Sources/CombineRealm/*'
16 |
17 | s.requires_arc = true
18 |
19 | s.ios.deployment_target = '13.0'
20 | s.osx.deployment_target = '10.15'
21 | s.tvos.deployment_target = '13.0'
22 | s.watchos.deployment_target = '6.0'
23 |
24 | s.frameworks = 'Combine'
25 |
26 | s.swift_version = "5.1"
27 |
28 | s.dependency 'RealmSwift', '~> 5'
29 |
30 | end
31 |
--------------------------------------------------------------------------------
/CombineRealm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CombineCommunity/CombineRealm/8812f3472790c7a0467c4f917179ab5ae02212ce/CombineRealm.png
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 5D450BDB24F2A0C0004A2648 /* CombineRealm in Frameworks */ = {isa = PBXBuildFile; productRef = 5D450BDA24F2A0C0004A2648 /* CombineRealm */; };
11 | 5D6C5C4423ECA6BD00853061 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6C5C4323ECA6BD00853061 /* AppDelegate.swift */; };
12 | 5D6C5C4623ECA6BD00853061 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6C5C4523ECA6BD00853061 /* SceneDelegate.swift */; };
13 | 5D6C5C4823ECA6BD00853061 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6C5C4723ECA6BD00853061 /* ViewController.swift */; };
14 | 5D6C5C4B23ECA6BD00853061 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5D6C5C4923ECA6BD00853061 /* Main.storyboard */; };
15 | 5D6C5C4D23ECA6BE00853061 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5D6C5C4C23ECA6BE00853061 /* Assets.xcassets */; };
16 | 5D6C5C5023ECA6BE00853061 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5D6C5C4E23ECA6BE00853061 /* LaunchScreen.storyboard */; };
17 | 5D6C5C6723ECD1A800853061 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6C5C6623ECD1A800853061 /* Models.swift */; };
18 | /* End PBXBuildFile section */
19 |
20 | /* Begin PBXFileReference section */
21 | 5D02515823F217C000CB304A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
22 | 5D02515E23F217F400CB304A /* CombineRealmTest-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CombineRealmTest-Bridging-Header.h"; sourceTree = ""; };
23 | 5D02515F23F217F500CB304A /* CombineRealmResultsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CombineRealmResultsTests.swift; path = ../../Tests/CombineRealmTests/CombineRealmResultsTests.swift; sourceTree = ""; };
24 | 5D02516023F217F500CB304A /* CombineRealmObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CombineRealmObjectTests.swift; path = ../../Tests/CombineRealmTests/CombineRealmObjectTests.swift; sourceTree = ""; };
25 | 5D02516123F217F500CB304A /* CombineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CombineTests.swift; path = ../../Tests/CombineRealmTests/CombineTests.swift; sourceTree = ""; };
26 | 5D02516223F217F500CB304A /* Models.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Models.swift; path = ../../Tests/CombineRealmTests/Models.swift; sourceTree = ""; };
27 | 5D02516323F217F500CB304A /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Utilities.swift; path = ../../Tests/CombineRealmTests/Utilities.swift; sourceTree = ""; };
28 | 5D02516423F217F500CB304A /* CombineRealmLinkingObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CombineRealmLinkingObjectTests.swift; path = ../../Tests/CombineRealmTests/CombineRealmLinkingObjectTests.swift; sourceTree = ""; };
29 | 5D02516523F217F500CB304A /* CombineRealmTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CombineRealmTests.swift; path = ../../Tests/CombineRealmTests/CombineRealmTests.swift; sourceTree = ""; };
30 | 5D02516623F217F500CB304A /* CombineRealmListTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CombineRealmListTests.swift; path = ../../Tests/CombineRealmTests/CombineRealmListTests.swift; sourceTree = ""; };
31 | 5D6C5C4023ECA6BD00853061 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
32 | 5D6C5C4323ECA6BD00853061 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
33 | 5D6C5C4523ECA6BD00853061 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
34 | 5D6C5C4723ECA6BD00853061 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
35 | 5D6C5C4A23ECA6BD00853061 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
36 | 5D6C5C4C23ECA6BE00853061 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
37 | 5D6C5C4F23ECA6BE00853061 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
38 | 5D6C5C5123ECA6BE00853061 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
39 | 5D6C5C6623ECD1A800853061 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; };
40 | /* End PBXFileReference section */
41 |
42 | /* Begin PBXFrameworksBuildPhase section */
43 | 5D6C5C3D23ECA6BD00853061 /* Frameworks */ = {
44 | isa = PBXFrameworksBuildPhase;
45 | buildActionMask = 2147483647;
46 | files = (
47 | 5D450BDB24F2A0C0004A2648 /* CombineRealm in Frameworks */,
48 | );
49 | runOnlyForDeploymentPostprocessing = 0;
50 | };
51 | /* End PBXFrameworksBuildPhase section */
52 |
53 | /* Begin PBXGroup section */
54 | 5D02515523F217C000CB304A /* CombineRealmTest */ = {
55 | isa = PBXGroup;
56 | children = (
57 | 5D02516423F217F500CB304A /* CombineRealmLinkingObjectTests.swift */,
58 | 5D02516623F217F500CB304A /* CombineRealmListTests.swift */,
59 | 5D02516023F217F500CB304A /* CombineRealmObjectTests.swift */,
60 | 5D02515F23F217F500CB304A /* CombineRealmResultsTests.swift */,
61 | 5D02516523F217F500CB304A /* CombineRealmTests.swift */,
62 | 5D02516123F217F500CB304A /* CombineTests.swift */,
63 | 5D02516223F217F500CB304A /* Models.swift */,
64 | 5D02516323F217F500CB304A /* Utilities.swift */,
65 | 5D02515823F217C000CB304A /* Info.plist */,
66 | 5D02515E23F217F400CB304A /* CombineRealmTest-Bridging-Header.h */,
67 | );
68 | path = CombineRealmTest;
69 | sourceTree = "";
70 | };
71 | 5D6C5C3723ECA6BD00853061 = {
72 | isa = PBXGroup;
73 | children = (
74 | 5D6C5C4223ECA6BD00853061 /* Example */,
75 | 5D02515523F217C000CB304A /* CombineRealmTest */,
76 | 5D6C5C4123ECA6BD00853061 /* Products */,
77 | );
78 | sourceTree = "";
79 | };
80 | 5D6C5C4123ECA6BD00853061 /* Products */ = {
81 | isa = PBXGroup;
82 | children = (
83 | 5D6C5C4023ECA6BD00853061 /* Example.app */,
84 | );
85 | name = Products;
86 | sourceTree = "";
87 | };
88 | 5D6C5C4223ECA6BD00853061 /* Example */ = {
89 | isa = PBXGroup;
90 | children = (
91 | 5D6C5C4323ECA6BD00853061 /* AppDelegate.swift */,
92 | 5D6C5C4523ECA6BD00853061 /* SceneDelegate.swift */,
93 | 5D6C5C4723ECA6BD00853061 /* ViewController.swift */,
94 | 5D6C5C6623ECD1A800853061 /* Models.swift */,
95 | 5D6C5C4923ECA6BD00853061 /* Main.storyboard */,
96 | 5D6C5C4C23ECA6BE00853061 /* Assets.xcassets */,
97 | 5D6C5C4E23ECA6BE00853061 /* LaunchScreen.storyboard */,
98 | 5D6C5C5123ECA6BE00853061 /* Info.plist */,
99 | );
100 | path = Example;
101 | sourceTree = "";
102 | };
103 | /* End PBXGroup section */
104 |
105 | /* Begin PBXNativeTarget section */
106 | 5D6C5C3F23ECA6BD00853061 /* Example */ = {
107 | isa = PBXNativeTarget;
108 | buildConfigurationList = 5D6C5C5423ECA6BE00853061 /* Build configuration list for PBXNativeTarget "Example" */;
109 | buildPhases = (
110 | 5D6C5C3C23ECA6BD00853061 /* Sources */,
111 | 5D6C5C3D23ECA6BD00853061 /* Frameworks */,
112 | 5D6C5C3E23ECA6BD00853061 /* Resources */,
113 | );
114 | buildRules = (
115 | );
116 | dependencies = (
117 | );
118 | name = Example;
119 | packageProductDependencies = (
120 | 5D450BDA24F2A0C0004A2648 /* CombineRealm */,
121 | );
122 | productName = Example;
123 | productReference = 5D6C5C4023ECA6BD00853061 /* Example.app */;
124 | productType = "com.apple.product-type.application";
125 | };
126 | /* End PBXNativeTarget section */
127 |
128 | /* Begin PBXProject section */
129 | 5D6C5C3823ECA6BD00853061 /* Project object */ = {
130 | isa = PBXProject;
131 | attributes = {
132 | LastSwiftUpdateCheck = 1130;
133 | LastUpgradeCheck = 1130;
134 | ORGANIZATIONNAME = "István Kreisz";
135 | TargetAttributes = {
136 | 5D6C5C3F23ECA6BD00853061 = {
137 | CreatedOnToolsVersion = 11.3.1;
138 | };
139 | };
140 | };
141 | buildConfigurationList = 5D6C5C3B23ECA6BD00853061 /* Build configuration list for PBXProject "Example" */;
142 | compatibilityVersion = "Xcode 9.3";
143 | developmentRegion = en;
144 | hasScannedForEncodings = 0;
145 | knownRegions = (
146 | en,
147 | Base,
148 | );
149 | mainGroup = 5D6C5C3723ECA6BD00853061;
150 | packageReferences = (
151 | 5D450BD924F2A0C0004A2648 /* XCRemoteSwiftPackageReference "CombineRealm" */,
152 | );
153 | productRefGroup = 5D6C5C4123ECA6BD00853061 /* Products */;
154 | projectDirPath = "";
155 | projectRoot = "";
156 | targets = (
157 | 5D6C5C3F23ECA6BD00853061 /* Example */,
158 | );
159 | };
160 | /* End PBXProject section */
161 |
162 | /* Begin PBXResourcesBuildPhase section */
163 | 5D6C5C3E23ECA6BD00853061 /* Resources */ = {
164 | isa = PBXResourcesBuildPhase;
165 | buildActionMask = 2147483647;
166 | files = (
167 | 5D6C5C5023ECA6BE00853061 /* LaunchScreen.storyboard in Resources */,
168 | 5D6C5C4D23ECA6BE00853061 /* Assets.xcassets in Resources */,
169 | 5D6C5C4B23ECA6BD00853061 /* Main.storyboard in Resources */,
170 | );
171 | runOnlyForDeploymentPostprocessing = 0;
172 | };
173 | /* End PBXResourcesBuildPhase section */
174 |
175 | /* Begin PBXSourcesBuildPhase section */
176 | 5D6C5C3C23ECA6BD00853061 /* Sources */ = {
177 | isa = PBXSourcesBuildPhase;
178 | buildActionMask = 2147483647;
179 | files = (
180 | 5D6C5C4823ECA6BD00853061 /* ViewController.swift in Sources */,
181 | 5D6C5C4423ECA6BD00853061 /* AppDelegate.swift in Sources */,
182 | 5D6C5C4623ECA6BD00853061 /* SceneDelegate.swift in Sources */,
183 | 5D6C5C6723ECD1A800853061 /* Models.swift in Sources */,
184 | );
185 | runOnlyForDeploymentPostprocessing = 0;
186 | };
187 | /* End PBXSourcesBuildPhase section */
188 |
189 | /* Begin PBXVariantGroup section */
190 | 5D6C5C4923ECA6BD00853061 /* Main.storyboard */ = {
191 | isa = PBXVariantGroup;
192 | children = (
193 | 5D6C5C4A23ECA6BD00853061 /* Base */,
194 | );
195 | name = Main.storyboard;
196 | sourceTree = "";
197 | };
198 | 5D6C5C4E23ECA6BE00853061 /* LaunchScreen.storyboard */ = {
199 | isa = PBXVariantGroup;
200 | children = (
201 | 5D6C5C4F23ECA6BE00853061 /* Base */,
202 | );
203 | name = LaunchScreen.storyboard;
204 | sourceTree = "";
205 | };
206 | /* End PBXVariantGroup section */
207 |
208 | /* Begin XCBuildConfiguration section */
209 | 5D6C5C5223ECA6BE00853061 /* Debug */ = {
210 | isa = XCBuildConfiguration;
211 | buildSettings = {
212 | ALWAYS_SEARCH_USER_PATHS = NO;
213 | CLANG_ANALYZER_NONNULL = YES;
214 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
215 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
216 | CLANG_CXX_LIBRARY = "libc++";
217 | CLANG_ENABLE_MODULES = YES;
218 | CLANG_ENABLE_OBJC_ARC = YES;
219 | CLANG_ENABLE_OBJC_WEAK = YES;
220 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
221 | CLANG_WARN_BOOL_CONVERSION = YES;
222 | CLANG_WARN_COMMA = YES;
223 | CLANG_WARN_CONSTANT_CONVERSION = YES;
224 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
225 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
226 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
227 | CLANG_WARN_EMPTY_BODY = YES;
228 | CLANG_WARN_ENUM_CONVERSION = YES;
229 | CLANG_WARN_INFINITE_RECURSION = YES;
230 | CLANG_WARN_INT_CONVERSION = YES;
231 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
232 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
233 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
234 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
235 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
236 | CLANG_WARN_STRICT_PROTOTYPES = YES;
237 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
238 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
239 | CLANG_WARN_UNREACHABLE_CODE = YES;
240 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
241 | COPY_PHASE_STRIP = NO;
242 | DEBUG_INFORMATION_FORMAT = dwarf;
243 | ENABLE_STRICT_OBJC_MSGSEND = YES;
244 | ENABLE_TESTABILITY = YES;
245 | GCC_C_LANGUAGE_STANDARD = gnu11;
246 | GCC_DYNAMIC_NO_PIC = NO;
247 | GCC_NO_COMMON_BLOCKS = YES;
248 | GCC_OPTIMIZATION_LEVEL = 0;
249 | GCC_PREPROCESSOR_DEFINITIONS = (
250 | "DEBUG=1",
251 | "$(inherited)",
252 | );
253 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
254 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
255 | GCC_WARN_UNDECLARED_SELECTOR = YES;
256 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
257 | GCC_WARN_UNUSED_FUNCTION = YES;
258 | GCC_WARN_UNUSED_VARIABLE = YES;
259 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
260 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
261 | MTL_FAST_MATH = YES;
262 | ONLY_ACTIVE_ARCH = YES;
263 | SDKROOT = iphoneos;
264 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
265 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
266 | };
267 | name = Debug;
268 | };
269 | 5D6C5C5323ECA6BE00853061 /* Release */ = {
270 | isa = XCBuildConfiguration;
271 | buildSettings = {
272 | ALWAYS_SEARCH_USER_PATHS = NO;
273 | CLANG_ANALYZER_NONNULL = YES;
274 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
275 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
276 | CLANG_CXX_LIBRARY = "libc++";
277 | CLANG_ENABLE_MODULES = YES;
278 | CLANG_ENABLE_OBJC_ARC = YES;
279 | CLANG_ENABLE_OBJC_WEAK = YES;
280 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
281 | CLANG_WARN_BOOL_CONVERSION = YES;
282 | CLANG_WARN_COMMA = YES;
283 | CLANG_WARN_CONSTANT_CONVERSION = YES;
284 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
285 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
286 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
287 | CLANG_WARN_EMPTY_BODY = YES;
288 | CLANG_WARN_ENUM_CONVERSION = YES;
289 | CLANG_WARN_INFINITE_RECURSION = YES;
290 | CLANG_WARN_INT_CONVERSION = YES;
291 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
292 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
293 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
294 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
295 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
296 | CLANG_WARN_STRICT_PROTOTYPES = YES;
297 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
298 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
299 | CLANG_WARN_UNREACHABLE_CODE = YES;
300 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
301 | COPY_PHASE_STRIP = NO;
302 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
303 | ENABLE_NS_ASSERTIONS = NO;
304 | ENABLE_STRICT_OBJC_MSGSEND = YES;
305 | GCC_C_LANGUAGE_STANDARD = gnu11;
306 | GCC_NO_COMMON_BLOCKS = YES;
307 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
308 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
309 | GCC_WARN_UNDECLARED_SELECTOR = YES;
310 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
311 | GCC_WARN_UNUSED_FUNCTION = YES;
312 | GCC_WARN_UNUSED_VARIABLE = YES;
313 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
314 | MTL_ENABLE_DEBUG_INFO = NO;
315 | MTL_FAST_MATH = YES;
316 | SDKROOT = iphoneos;
317 | SWIFT_COMPILATION_MODE = wholemodule;
318 | SWIFT_OPTIMIZATION_LEVEL = "-O";
319 | VALIDATE_PRODUCT = YES;
320 | };
321 | name = Release;
322 | };
323 | 5D6C5C5523ECA6BE00853061 /* Debug */ = {
324 | isa = XCBuildConfiguration;
325 | buildSettings = {
326 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
327 | CODE_SIGN_STYLE = Automatic;
328 | DEVELOPMENT_TEAM = TA366CU42K;
329 | INFOPLIST_FILE = Example/Info.plist;
330 | LD_RUNPATH_SEARCH_PATHS = (
331 | "$(inherited)",
332 | "@executable_path/Frameworks",
333 | );
334 | PRODUCT_BUNDLE_IDENTIFIER = istvankreisz.Example;
335 | PRODUCT_NAME = "$(TARGET_NAME)";
336 | SWIFT_VERSION = 5.0;
337 | TARGETED_DEVICE_FAMILY = "1,2";
338 | };
339 | name = Debug;
340 | };
341 | 5D6C5C5623ECA6BE00853061 /* Release */ = {
342 | isa = XCBuildConfiguration;
343 | buildSettings = {
344 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
345 | CODE_SIGN_STYLE = Automatic;
346 | DEVELOPMENT_TEAM = TA366CU42K;
347 | INFOPLIST_FILE = Example/Info.plist;
348 | LD_RUNPATH_SEARCH_PATHS = (
349 | "$(inherited)",
350 | "@executable_path/Frameworks",
351 | );
352 | PRODUCT_BUNDLE_IDENTIFIER = istvankreisz.Example;
353 | PRODUCT_NAME = "$(TARGET_NAME)";
354 | SWIFT_VERSION = 5.0;
355 | TARGETED_DEVICE_FAMILY = "1,2";
356 | };
357 | name = Release;
358 | };
359 | /* End XCBuildConfiguration section */
360 |
361 | /* Begin XCConfigurationList section */
362 | 5D6C5C3B23ECA6BD00853061 /* Build configuration list for PBXProject "Example" */ = {
363 | isa = XCConfigurationList;
364 | buildConfigurations = (
365 | 5D6C5C5223ECA6BE00853061 /* Debug */,
366 | 5D6C5C5323ECA6BE00853061 /* Release */,
367 | );
368 | defaultConfigurationIsVisible = 0;
369 | defaultConfigurationName = Release;
370 | };
371 | 5D6C5C5423ECA6BE00853061 /* Build configuration list for PBXNativeTarget "Example" */ = {
372 | isa = XCConfigurationList;
373 | buildConfigurations = (
374 | 5D6C5C5523ECA6BE00853061 /* Debug */,
375 | 5D6C5C5623ECA6BE00853061 /* Release */,
376 | );
377 | defaultConfigurationIsVisible = 0;
378 | defaultConfigurationName = Release;
379 | };
380 | /* End XCConfigurationList section */
381 |
382 | /* Begin XCRemoteSwiftPackageReference section */
383 | 5D450BD924F2A0C0004A2648 /* XCRemoteSwiftPackageReference "CombineRealm" */ = {
384 | isa = XCRemoteSwiftPackageReference;
385 | repositoryURL = "https://github.com/CombineCommunity/CombineRealm.git";
386 | requirement = {
387 | kind = upToNextMajorVersion;
388 | minimumVersion = 2.0.0;
389 | };
390 | };
391 | /* End XCRemoteSwiftPackageReference section */
392 |
393 | /* Begin XCSwiftPackageProductDependency section */
394 | 5D450BDA24F2A0C0004A2648 /* CombineRealm */ = {
395 | isa = XCSwiftPackageProductDependency;
396 | package = 5D450BD924F2A0C0004A2648 /* XCRemoteSwiftPackageReference "CombineRealm" */;
397 | productName = CombineRealm;
398 | };
399 | /* End XCSwiftPackageProductDependency section */
400 | };
401 | rootObject = 5D6C5C3823ECA6BD00853061 /* Project object */;
402 | }
403 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "CombineRealm",
6 | "repositoryURL": "https://github.com/CombineCommunity/CombineRealm.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "9c8f4478f0d603afba2706882ca3212432b5aaee",
10 | "version": "2.0.0"
11 | }
12 | },
13 | {
14 | "package": "Realm",
15 | "repositoryURL": "https://github.com/realm/realm-cocoa.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "99c7d89791dfe8730c145461d59969eb4c56c557",
19 | "version": "5.3.5"
20 | }
21 | },
22 | {
23 | "package": "RealmCore",
24 | "repositoryURL": "https://github.com/realm/realm-core",
25 | "state": {
26 | "branch": null,
27 | "revision": "b7c49bb107cfc933016792e78fc5daa2aca71a14",
28 | "version": "6.0.19"
29 | }
30 | }
31 | ]
32 | },
33 | "version": 1
34 | }
35 |
--------------------------------------------------------------------------------
/Example/Example/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Example
4 | //
5 | // Created by István Kreisz on 06/02/2020.
6 | // Copyright (c) Combine Community. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import RealmSwift
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | let realm = try! Realm(configuration: Realm.Configuration(deleteRealmIfMigrationNeeded: true))
17 | try! realm.write {
18 | realm.deleteAll()
19 | }
20 | return true
21 | }
22 |
23 | // MARK: UISceneSession Lifecycle
24 |
25 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
27 | }
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Example/Example/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/Example/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Example/Example/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/Example/Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 | UISceneStoryboardFile
37 | Main
38 |
39 |
40 |
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIMainStoryboardFile
45 | Main
46 | UIRequiredDeviceCapabilities
47 |
48 | armv7
49 |
50 | UISupportedInterfaceOrientations
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 | UISupportedInterfaceOrientations~ipad
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationPortraitUpsideDown
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Example/Example/Models.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Models.swift
3 | // Example
4 | //
5 | // Created by István Kreisz on 06/02/2020.
6 | // Copyright (c) Combine Community. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import RealmSwift
11 |
12 | let formatter: DateFormatter = {
13 | let f = DateFormatter()
14 | f.timeStyle = .long
15 | return f
16 | }()
17 |
18 | class Color: Object {
19 | @objc dynamic var time: TimeInterval = Date().timeIntervalSinceReferenceDate
20 | @objc dynamic var colorR = Double.random(in: 0...1.0)
21 | @objc dynamic var colorG = Double.random(in: 0...1.0)
22 | @objc dynamic var colorB = Double.random(in: 0...1.0)
23 |
24 | var color: UIColor {
25 | return UIColor(red: CGFloat(colorR), green: CGFloat(colorG), blue: CGFloat(colorB), alpha: 1.0)
26 | }
27 | }
28 |
29 | class TickCounter: Object {
30 | @objc dynamic var id = UUID().uuidString
31 | @objc dynamic var ticks: Int = 0
32 | override static func primaryKey() -> String? { return "id" }
33 | }
34 |
--------------------------------------------------------------------------------
/Example/Example/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // Example
4 | //
5 | // Created by István Kreisz on 06/02/2020.
6 | // Copyright (c) Combine Community. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
12 |
13 | var window: UIWindow?
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | guard let _ = (scene as? UIWindowScene) else { return }
17 | }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/Example/Example/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Example
4 | //
5 | // Created by István Kreisz on 06/02/2020.
6 | // Copyright (c) Combine Community. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import RealmSwift
11 | import Combine
12 | import CombineRealm
13 |
14 | class MainViewController: UITableViewController {
15 |
16 | var subscriptions = Set()
17 |
18 | var colors: Results!
19 |
20 | let addedColors = PassthroughSubject()
21 | let deletedColors = PassthroughSubject()
22 |
23 | let tickUpdated = PassthroughSubject()
24 |
25 | let footer: UIStackView = {
26 | let label1 = UILabel()
27 | let label2 = UILabel()
28 | label1.textAlignment = .center
29 | label2.textAlignment = .center
30 | let stackView = UIStackView(arrangedSubviews: [label1, label2])
31 | stackView.distribution = .fillEqually
32 | stackView.axis = .horizontal
33 | return stackView
34 | }()
35 |
36 | lazy var ticker: TickCounter = {
37 | let realm = try! Realm()
38 | let ticker = TickCounter()
39 | try! realm.write {
40 | realm.add(ticker)
41 | }
42 | return ticker
43 | }()
44 |
45 | @IBAction func addTapped(_ sender: Any) {
46 | addedColors.send(Color())
47 | }
48 |
49 | @IBAction func tickTapped(_ sender: Any) {
50 | tickUpdated.send()
51 | }
52 |
53 | override func viewDidLoad() {
54 | super.viewDidLoad()
55 |
56 | let realm = try! Realm()
57 | colors = realm.objects(Color.self).sorted(byKeyPath: "time", ascending: false)
58 |
59 | tickUpdated
60 | .sink { [unowned self] in
61 | let realm = try! Realm()
62 | try! realm.write {
63 | self.ticker.ticks += 1
64 | }
65 | }
66 | .store(in: &subscriptions)
67 |
68 | // Observing collection changes
69 | RealmPublishers.collection(from: colors)
70 | .map { results in "colors: \(results.count)" }
71 | .sink(receiveCompletion: { _ in
72 | print("Completed")
73 | }, receiveValue: { results in
74 | self.title = results
75 | })
76 | .store(in: &subscriptions)
77 |
78 | // Observing changesets
79 | RealmPublishers.changeset(from: colors)
80 | .sink(receiveCompletion: { _ in
81 | print("Completed")
82 | }, receiveValue: { [unowned self] _, changes in
83 | if let changes = changes {
84 | self.tableView.applyChangeset(changes)
85 | } else {
86 | self.tableView.reloadData()
87 | }
88 | })
89 | .store(in: &subscriptions)
90 |
91 | // Adding to realm
92 | addedColors
93 | .addToRealm()
94 | .store(in: &subscriptions)
95 |
96 | // Deleting from realm
97 | deletedColors
98 | .deleteFromRealm()
99 | .store(in: &subscriptions)
100 |
101 | // Observing a single object
102 | RealmPublishers.propertyChanges(object: ticker)
103 | .filter { $0.name == "ticks" }
104 | .map { "\($0.newValue!) ticks" }
105 | .sink(receiveCompletion: { _ in },
106 | receiveValue: { [unowned self] ticks in
107 | (self.footer.arrangedSubviews[0] as! UILabel).text = ticks
108 | })
109 | .store(in: &subscriptions)
110 |
111 | // Observing all database changes
112 | RealmPublishers.from(realm: realm)
113 | .map { _ in }
114 | .scan(0, { result, _ in
115 | return result + 1
116 | })
117 | .map { "\($0) changes" }
118 | .sink(receiveCompletion: { _ in },
119 | receiveValue: { count in
120 | (self.footer.arrangedSubviews[1] as! UILabel).text = count
121 | })
122 | .store(in: &subscriptions)
123 | }
124 | }
125 |
126 | extension MainViewController {
127 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
128 | return colors.count
129 | }
130 |
131 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
132 | let color = colors[indexPath.row]
133 |
134 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
135 | cell.textLabel?.text = formatter.string(from: Date(timeIntervalSinceReferenceDate: color.time))
136 | cell.backgroundColor = color.color
137 | return cell
138 | }
139 |
140 | override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
141 | return "Delete objects by tapping them"
142 | }
143 | }
144 |
145 | extension MainViewController {
146 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
147 | deletedColors.send(colors[indexPath.row])
148 | }
149 |
150 | override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
151 | return footer
152 | }
153 | }
154 |
155 | extension UITableView {
156 | func applyChangeset(_ changes: RealmChangeset) {
157 | beginUpdates()
158 | deleteRows(at: changes.deleted.map { IndexPath(row: $0, section: 0) }, with: .automatic)
159 | insertRows(at: changes.inserted.map { IndexPath(row: $0, section: 0) }, with: .automatic)
160 | reloadRows(at: changes.updated.map { IndexPath(row: $0, section: 0) }, with: .automatic)
161 | endUpdates()
162 | }
163 | }
164 |
165 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Combine Open Source
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "Realm",
6 | "repositoryURL": "https://github.com/realm/realm-cocoa.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "99c7d89791dfe8730c145461d59969eb4c56c557",
10 | "version": "5.3.5"
11 | }
12 | },
13 | {
14 | "package": "RealmCore",
15 | "repositoryURL": "https://github.com/realm/realm-core",
16 | "state": {
17 | "branch": null,
18 | "revision": "b7c49bb107cfc933016792e78fc5daa2aca71a14",
19 | "version": "6.0.19"
20 | }
21 | }
22 | ]
23 | },
24 | "version": 1
25 | }
26 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "CombineRealm",
8 | platforms: [
9 | .macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6)
10 | ],
11 | products: [
12 | .library(
13 | name: "CombineRealm",
14 | targets: ["CombineRealm"]),
15 | ],
16 | dependencies: [
17 | .package(url: "https://github.com/realm/realm-cocoa.git", .upToNextMajor(from: "5.0.0"))
18 | ],
19 | targets: [
20 | .target(
21 | name: "CombineRealm",
22 | dependencies: ["Realm", "RealmSwift"],
23 | path: "Sources"
24 | ),
25 | .testTarget(
26 | name: "CombineRealmTests",
27 | dependencies: ["CombineRealm"],
28 | path: "Tests"
29 | )
30 | ],
31 | swiftLanguageVersions: [.v5]
32 | )
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CombineRealm
2 |
3 | 
4 |
5 | [](http://cocoapods.org/pods/Combine-Realm)
6 | [](http://cocoapods.org/pods/Combine-Realm)
7 | 
8 |
9 | This library is a thin wrapper around __RealmSwift__ ([Realm Docs](https://realm.io/docs/swift/latest/)), inspired by the RxSwift Community's [RxRealm](https://github.com/RxSwiftCommunity/RxRealm) library.
10 |
11 | ## Usage
12 |
13 | **Table of contents:**
14 |
15 | 1. [Observing object collections](https://github.com/istvan-kreisz/CombineRealm#observing-object-collections)
16 | 2. [Observing a single object](https://github.com/istvan-kreisz/CombineRealm#observing-a-single-object)
17 | 3. [Observing a realm instance](https://github.com/istvan-kreisz/CombineRealm#observing-a-realm-instance)
18 | 4. [Write transactions](https://github.com/istvan-kreisz/CombineRealm#write-transactions)
19 | 5. [Delete transactions](https://github.com/istvan-kreisz/CombineRealm#delete-transactions)
20 | 6. [Example app](https://github.com/istvan-kreisz/CombineRealm#example-app)
21 |
22 | ### Observing object collections
23 |
24 | CombineRealm can be used to create `Publisher`s from objects of type `Results`, `List`, `LinkingObjects` or `AnyRealmCollection`. These types are typically used to load and observe object collections from the Realm Mobile Database.
25 |
26 | #### `RealmPublishers.collection(from:synchronousStart:)`
27 |
28 | Emits an event each time the collection changes:
29 |
30 | ```swift
31 | let realm = try! Realm()
32 | let colors = realm.objects(Color.self)
33 |
34 | RealmPublishers.collection(from: colors)
35 | .map { colors in "colors: \(colors.count)" }
36 | .sink(receiveCompletion: { _ in
37 | print("Completed")
38 | }, receiveValue: { result in
39 | print(result)
40 | })
41 | ```
42 |
43 | The above prints out "colors: X" each time a `Color` instance is added or removed from the database. If you set `synchronousStart` to `true` (the default value), the first element will be emitted synchronously - e.g. when you're binding UI it might not be possible for an asynchronous notification to come through.
44 |
45 | #### `RealmPublishers.array(from:synchronousStart:)`
46 | Upon each change fetches a snapshot of the Realm collection and converts it to an array value (for example if you want to use array methods on the collection):
47 |
48 | ```swift
49 | let realm = try! Realm()
50 | let colors = realm.objects(Color.self)
51 |
52 | RealmPublishers.array(from: colors)
53 | .map { colors in colors.prefix(3) }
54 | .sink(receiveCompletion: { _ in
55 | print("Completed")
56 | }, receiveValue: { colors in
57 | print(colors)
58 | })
59 | ```
60 |
61 | #### `RealmPublishers.changeset(from:synchronousStart:)`
62 | Emits every time the collection changes and provides the exact indexes that have been deleted, inserted or updated along with the appropriate `AnyRealmCollection` value:
63 |
64 | ```swift
65 | let realm = try! Realm()
66 | let colors = realm.objects(Color.self)
67 |
68 | RealmPublishers.changeset(from: colors)
69 | .sink(receiveCompletion: { _ in
70 | print("Completed")
71 | }, receiveValue: { results, changes in
72 | if let changes = changes {
73 | // it's an update
74 | print(results)
75 | print("deleted: \(changes.deleted)")
76 | print("inserted: \(changes.inserted)")
77 | print("updated: \(changes.updated)")
78 | } else {
79 | // it's the initial data
80 | print(results)
81 | }
82 | })
83 | ```
84 |
85 | #### `RealmPublishers.arrayWithChangeset(from:synchronousStart:)`
86 |
87 | Emits every time the collection changes and provides the exact indexes that have been deleted, inserted or updated along with the `Array` value:
88 |
89 | ```swift
90 | let realm = try! Realm()
91 | let colors = realm.objects(Color.self))
92 |
93 | RealmPublishers.arrayWithChangeset(from: colors)
94 | .sink(receiveCompletion: { _ in
95 | print("Completed")
96 | }, receiveValue: { array, changes in
97 | if let changes = changes {
98 | // it's an update
99 | print(array)
100 | print("deleted: \(changes.deleted)")
101 | print("inserted: \(changes.inserted)")
102 | print("updated: \(changes.updated)")
103 | } else {
104 | // it's the initial data
105 | print(array)
106 | }
107 | })
108 | ```
109 |
110 | ### Observing a single object
111 |
112 | #### `RealmPublishers.from(object:emitInitialValue:properties:)`
113 |
114 | Emits every time any of the properties of the observed object change.
115 |
116 | It will by default emit the object's initial state as its first value. You can disable this behavior by using the `emitInitialValue` parameter and setting it to `false`.
117 |
118 | ```swift
119 | RealmPublishers.from(object: color)
120 | .sink(receiveCompletion: { _ in
121 | print("Completed")
122 | }) { color in
123 | print(color)
124 | }
125 | ```
126 |
127 | You can set which property changes you'd like to observe:
128 |
129 | ```swift
130 | Observable.from(object: ticker, properties: ["red", "green", "blue"])
131 | ```
132 |
133 | ### Observing a realm instance
134 |
135 | #### `RealmPublishers.from(realm:)`
136 |
137 | Emits every time the realm changes: any create & update & delete operation happens in it. It provides the realm instance along with the realm change notification.
138 |
139 | ```swift
140 | let realm = try! Realm()
141 |
142 | RealmPublishers.from(realm: realm)
143 | .sink(receiveCompletion: { _ in
144 | print("Completed")
145 | }) { realm, notification in
146 | print("Something happened!")
147 | }
148 | ```
149 |
150 | ### Write transactions
151 |
152 | #### `addToRealm()`
153 |
154 | Writes object(s) to the default Realm: `Realm(configuration: .defaultConfiguration)`.
155 |
156 | ```swift
157 | let realm = try! Realm()
158 | let colors = realm.objects(Color.self))
159 |
160 | RealmPublishers.array(from: colors)
161 | .addToRealm()
162 | ```
163 |
164 | #### `addToRealm(configuration:updatePolicy:onError:)`
165 |
166 | Writes object(s) to a **custom** Realm. If you want to switch threads and not use the default Realm, provide a `Realm.Configuration`. You an also provide an error handler for the observer to be called if either creating the realm reference or the write transaction raise an error:
167 |
168 | NOTE: All 3 arguments are optional, check the function definition for the default values
169 |
170 | ```swift
171 | let realm = try! Realm()
172 | let colors = realm.objects(Color.self))
173 |
174 | RealmPublishers.array(from: colors)
175 | .addToRealm(configuration: .defaultCOnfiguration, updatePolicy: .error, onError: {
176 | print($0)
177 | })
178 | ```
179 |
180 | ### Delete transactions
181 |
182 | #### `deleteFromRealm()`
183 |
184 | Deletes object(s) from the object(s)'s realm:
185 |
186 | ```swift
187 | let realm = try! Realm()
188 | let colors = realm.objects(Color.self))
189 |
190 | RealmPublishers.array(from: colors)
191 | .deleteFromRealm()
192 | ```
193 |
194 | #### `deleteFromRealm(onError:)`
195 |
196 | Deletes object(s) from the object(s)'s realm. You an also provide an error handler for the observer to be called if either creating the realm reference or the write transaction raise an error:
197 |
198 | ```swift
199 | let realm = try! Realm()
200 | let colors = realm.objects(Color.self))
201 |
202 | RealmPublishers.array(from: colors)
203 | .deleteFromRealm(onError: {
204 | print($0)
205 | })
206 | ```
207 |
208 | ### Example app
209 |
210 | To run the example project, clone the repo, navigate to the __Example__ folder and open the `Example.xcodeproj` file.
211 |
212 | To ensure that you're using the latest version of CombineRealm, in Xcode select `Update to Latest Package Versions` in the `File/Swift Packages/Add Package Dependency...` menu.
213 |
214 | The app uses CombineRealm to observe changes in and write to Realm.
215 |
216 | ## Testing
217 |
218 | To inspect the library's Unit tests, check out the files in `Tests/CombineRealmTests`.
219 | To run the tests, go to the root directory of the repo and run the command:
220 |
221 | ```swift
222 | swift test
223 | ```
224 |
225 | ## Installation
226 |
227 | ### CocoaPods
228 |
229 | Add the following line to your Podfile and run `pod install`:
230 |
231 | ```ruby
232 | pod 'Combine-Realm'
233 | ```
234 | Since import statements in Xcode can't contain dashes, the correct way to import the library is:
235 |
236 | ```swift
237 | import Combine_Realm
238 | ```
239 |
240 | ### Swift Package Manager
241 |
242 | - In Xcode select `File/Swift Packages/Add Package Dependency...`
243 | - Paste `https://github.com/istvan-kreisz/CombineRealm.git` into the repository URL textfield.
244 |
245 | ### Future ideas
246 |
247 | - Add CI tests
248 | - Add Carthage support
249 | - Your ideas?
250 |
251 | ## Author
252 |
253 | __Istvan Kreisz__
254 |
255 | [kreiszdev@gmail.com](mailto:kreiszdev@gmail.com)
256 |
257 | [@IKreisz](https://twitter.com/IKreisz)
258 |
259 | ## License
260 |
261 | CombineReachability is available under the MIT license. See the LICENSE file for more info.
262 |
--------------------------------------------------------------------------------
/Sources/CombineRealm/Add.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Add.swift
3 | // CombineRealm
4 | //
5 | // Created by István Kreisz on 05/02/2020.
6 | // Copyright (c) Combine Community. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Combine
11 | import RealmSwift
12 |
13 |
14 | class Add: Subscriber, Cancellable {
15 |
16 | public let combineIdentifier = CombineIdentifier()
17 |
18 | private let configuration: Realm.Configuration
19 |
20 | private let updatePolicy: Realm.UpdatePolicy
21 | private let onError: ((Swift.Error) -> Void)?
22 | private var subscription: Subscription?
23 |
24 | init(configuration: Realm.Configuration, updatePolicy: Realm.UpdatePolicy, onError: ((Swift.Error) -> Void)?) {
25 | self.configuration = configuration
26 | self.updatePolicy = updatePolicy
27 | self.onError = onError
28 | }
29 |
30 | func receive(subscription: Subscription) {
31 | self.subscription = subscription
32 | subscription.request(.unlimited)
33 | }
34 |
35 | func receive(_ input: Input) -> Subscribers.Demand {
36 | do {
37 | let realm = try Realm(configuration: configuration)
38 | try realm.write {
39 | addToRealm(realm, input: input, updatePolicy: updatePolicy)
40 | }
41 | } catch let error {
42 | onError?(error)
43 | }
44 | return .unlimited
45 | }
46 |
47 | func addToRealm(_ realm: Realm, input: Input, updatePolicy: Realm.UpdatePolicy) {
48 | preconditionFailure("Subclasses must override this method")
49 | }
50 |
51 | func receive(completion: Subscribers.Completion) {
52 | subscription = nil
53 | }
54 |
55 | func cancel() {
56 | subscription?.cancel()
57 | subscription = nil
58 | }
59 | }
60 |
61 | final class AddOne: Add {
62 | override func addToRealm(_ realm: Realm, input: Input, updatePolicy: Realm.UpdatePolicy) {
63 | realm.add(input, update: updatePolicy)
64 | }
65 | }
66 |
67 | final class AddMany: Add where Input.Iterator.Element: Object {
68 | override func addToRealm(_ realm: Realm, input: Input, updatePolicy: Realm.UpdatePolicy) {
69 | realm.add(input, update: updatePolicy)
70 | }
71 | }
72 |
73 | public extension Publisher where Output: Object, Failure: Error {
74 |
75 | /**
76 | Subscribes publisher to subscriber which adds objects to a Realm. The objects are added to the default realm instance `Realm()`.
77 |
78 | - returns: `AnyCancellable`
79 | */
80 | func addToRealm() -> AnyCancellable {
81 | return addToRealm(configuration: .defaultConfiguration)
82 | }
83 |
84 | /**
85 | Subscribes publisher to subscriber which adds objects to a Realm.
86 |
87 | - parameter configuration (by default uses `Realm.Configuration.defaultConfiguration`) to use to get a Realm for the write operations
88 | - returns: `AnyCancellable`
89 | */
90 | func addToRealm(configuration: Realm.Configuration = .defaultConfiguration) -> AnyCancellable {
91 | return addToRealm(configuration: configuration, updatePolicy: .error)
92 | }
93 |
94 | /**
95 | Subscribes publisher to subscriber which adds objects to a Realm.
96 |
97 | - parameter configuration (by default uses `Realm.Configuration.defaultConfiguration`) to use to get a Realm for the write operations
98 | - parameter updatePolicy - update according to `Realm.UpdatePolicy`
99 | - parameter onError - closure to implement custom error handling
100 | - returns: `AnyCancellable`
101 | */
102 | func addToRealm(configuration: Realm.Configuration = .defaultConfiguration, updatePolicy: Realm.UpdatePolicy = .error, onError: ((Swift.Error) -> Void)? = nil) -> AnyCancellable {
103 | let subscriber = AddOne