diff --git a/README.md b/README.md index f7d0cf4..877ef97 100644 --- a/README.md +++ b/README.md @@ -1143,14 +1143,16 @@ Take special care when implementing `CoreStoreLogger`'s `assert(...)` and `abort - `abort(...)`: This method is *the* last-chance for your app to *synchronously* log a fatal error within CoreStore. The app will be terminated right after this function is called (CoreStore calls `fatalError()` internally) Starting CoreStore 2.0, all CoreStore types now have very useful (and pretty formatted!) `print(...)` outputs. -A couples of examples; `ListMonitor`: + +A couple of examples, `ListMonitor`: screen shot 2016-07-10 at 22 56 44 -`CoreStoreError.MappingModelNotFoundError` +`CoreStoreError.MappingModelNotFoundError`: MappingModelNotFoundError +These are all implemented with `CustomDebugStringConvertible.debugDescription`, so they work with lldb's `po` command as well. ## Observing changes and notifications (unavailable on macOS) CoreStore provides type-safe wrappers for observing managed objects: @@ -1285,12 +1287,139 @@ let person2 = self.monitor[1, 2] ``` ## Objective-C support - +CoreStore 2.0 was a big move to address the large number of apps starting to convert from Objective-C to Swift. The basic problem is this: The cost of converting all code base to Swift is very big, so most apps are forced to do undergo a *transitional* ObjC-Swift hybrid phase. This used to mean that these apps could not use the Swifty-est libraries out there yet, or that they may have to write their own bridging methods just to make new code usable in their old Objective-C code. + +With 2.0, all CoreStore types are still written in pure Swift, but they now have Objective-C "bridging classes" that are visible to Objective-C code. To show a couple of usage examples: + + + + + + + + + + + +
SwiftObjective-C
+try CoreStore.addStorageAndWait(SQLiteStore)
+
+NSError *error
+[CSCoreStore addSQLiteStorageAndWait:[CSSQLiteStore new] error:&error]
+
+CoreStore.beginAsynchronous { (transaction) in
+    // ...
+    transaction.commit { (result) in
+        switch result {
+        case .Success(let hasChanges): print(hasChanges)
+        case .Failure(let error): print(error)
+        }
+    }
+}
+
+[CSCoreStore beginAsynchronous:^(CSAsynchronousDataTransaction *transaction) {
+    // ...
+    [transaction commitWithCompletion:^(CSSaveResult *result) {
+        if (result.isSuccess) {
+            NSLog(@"hasChanges: %d", result.hasChanges);
+        }
+        else if (result.isFailure) {
+            NSLog(@"error: %@", result.error);
+        }
+    }];
+}];
+
+ +All of these `CS`-prefixed bridging classes have very similar usage to the existing CoreStore APIs, and ironically *none of them are written in Objective-C*. The secret is all in *CoreStoreBridge.swift*, where we see the signature of these bridging classes, the `CoreStoreObjectiveCType` protocol: +```swift +public protocol CoreStoreObjectiveCType: class, AnyObject { + associatedtype SwiftType + var bridgeToSwift: SwiftType { get } + init(_ swiftValue: SwiftType) +} +``` +Notice that these bridging classes all hold a reference to their corresponding `SwiftType`. + +Conversely, CoreStore original types implement the `CoreStoreSwiftType` protocol: +```swift +public protocol CoreStoreSwiftType { + associatedtype ObjectiveCType + var bridgeToObjectiveC: ObjectiveCType { get } +} +``` +These two protocols let CoreStore types free to bridge instances between Objective-C and Swift. + +This is very different to the common approach where apps and libraries write Objective-C APIs just to support both Objective-C and Swift. The advantage with CoreStore's approach is that your Swift codebase can already use the purely-Swift API without further changes in the future, but your "hybrid" codebase can still bridge instances back and forth from Objective-C to Swift. + +For example, you may have a new, modern Swift class that holds a `ListMonitor`: +```swift +class MyViewController: UIViewController { + let monitor = CoreStore.monitorList(From(MyEntity), ...) + // ... +} +``` +Now let's say you have a legacy Objective-C class that previously uses `NSFetchedResultsController`. It's easy to switch from `NSFetchedResultsController` to `CSListMonitor`, but converting the rest of this huge class is impractical. You end up with +```objc +@interface MYOldViewController: UIViewController +@property (nonatomic, readonly, strong) CSListMonitor* monitor; +- (instancetype)initWithMonitor:(CSListMonitor *)monitor; +@end +``` +When you need to instantiate this class from Swift, you just call `bridgeToObjectiveC`: +```swift +class MyViewController: UIViewController { + let monitor = CoreStore.monitorList(From(MyEntity), ...) + func showOldController() { + let controller = MYOldViewController(monitor: self.monitor.bridgeToObjectiveC) + self.presentViewController(controller, animated: true, completion: nil) + } +} +``` +Note that the `CSListMonitor` holds the exact same `ListMonitor` instance, which means that no copies and no extra fetching occur. + +### Objective-C syntax sugars +Objective-C tends to be verbose, so some method calls are long and unreadable. For example, fetching looks like this: +```objc +NSArray *objects = +[CSCoreStore + fetchAllFrom:[[CSFrom alloc] initWithEntityClass:[MYPerson class]] + fetchClauses:@[[[CSWhere alloc] initWithFormat:@"%K == %@", @"isHidden", @NO], + [[CSOrderBy alloc] initWithSortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"lastName" ascending:YES], + [NSSortDescriptor sortDescriptorWithKey:@"firstName" ascending:YES]]]]]; +``` +Although it works, it looks terrible. For this, CoreStore provides *CoreStoreBridge.h* where these Objective-C calls are wrapped in readable, convenient macros and global functions. The call above becomes +```objc +NSArray *objects = +[CSCoreStore + fetchAllFrom:CSFromClass([MYPerson class]) + fetchClauses:@[CSWhereFormat(@"%K == %@", @"isHidden", @NO), + CSOrderByKeys(CSSortAscending(@"lastName"), + CSSortAscending(@"firstName"), nil)]]; +``` +That's much shorter now. But we can still do better. Notice that we have strings being used as key paths. The `CSKeyPath(...)` macro gives us compile-time checking so keys that don't exist in a class will generate errors. Our key-safe code now looks like this: +```objc +NSArray *objects = +[CSCoreStore + fetchAllFrom:CSFromClass([MYPerson class]) + fetchClauses:@[CSWhereFormat(@"%K == %@", CSKeyPath(MYPerson, isHidden), @NO), + CSOrderByKeys(CSSortAscending(CSKeyPath(MYPerson, lastName)), + CSSortAscending(CSKeyPath(MYPerson, firstName)), nil)]]; +``` + +To use these syntax sugars, include *CoreStoreBridge.h* in your Objective-C source files. For projects that support iOS 7 (and thus cannot build CoreStore as a module), you will need to add +``` +SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME) +``` +to your target's `GCC_PREPROCESSOR_DEFINITIONS` build setting. + +GCC_PREPROCESSOR_DEFINITIONS # Roadmap -- Support iCloud stores -- CoreSpotlight auto-indexing (experimental) +- Built-in "singleton objects" support +- Built-in "readonly" stores +- CoreSpotlight auto-indexing (experimenting, still some roadblocks ahead) +- Synching # Installation @@ -1311,8 +1440,8 @@ This installs CoreStore as a framework. Declare `import CoreStore` in your swift ### Install with Carthage In your `Cartfile`, add ``` -github "JohnEstropia/CoreStore" >= 1.6.0 -github "JohnEstropia/GCDKit" >= 1.2.2 +github "JohnEstropia/CoreStore" >= 2.0.0 +github "JohnEstropia/GCDKit" >= 1.2.5 ``` and run ``` @@ -1332,6 +1461,18 @@ Drag and drop **CoreStore.xcodeproj** to your project. Add all *.swift* files to your project. +### Objective-C support + +To use the Objective-C syntax sugars, import *CoreStoreBridge.h* in your *.m* source files. + +For projects that support iOS 7 (and thus cannot build CoreStore as a module), you will need to add +``` +SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME) +``` +to your target's `GCC_PREPROCESSOR_DEFINITIONS` build setting: + +GCC_PREPROCESSOR_DEFINITIONS + # Changesets ### Upgrading from 1.x.x to 2.x.x