Skip to content
This repository has been archived by the owner on Dec 17, 2018. It is now read-only.

Expose CloudCore to Objective C #30

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
## Build generated
build/
DerivedData/
.DS_Store

## Various settings
*.pbxuser
Expand Down
11 changes: 8 additions & 3 deletions CloudCore.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0910;
LastUpgradeCheck = 0900;
LastUpgradeCheck = 1010;
ORGANIZATIONNAME = "Vasily Ulianov";
TargetAttributes = {
D5B2E89E1C3A780C00C0327D = {
Expand Down Expand Up @@ -634,6 +634,7 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
Base,
);
Expand Down Expand Up @@ -816,12 +817,14 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
Expand Down Expand Up @@ -855,7 +858,7 @@
ONLY_ACTIVE_ARCH = YES;
SUPPORTED_PLATFORMS = "macosx watchsimulator watchos appletvsimulator appletvos iphonesimulator iphoneos";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2,3,4";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
Expand All @@ -874,12 +877,14 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
Expand All @@ -906,7 +911,7 @@
MTL_ENABLE_DEBUG_INFO = NO;
SUPPORTED_PLATFORMS = "macosx watchsimulator watchos appletvsimulator appletvos iphonesimulator iphoneos";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 3.0;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2,3,4";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
8 changes: 3 additions & 5 deletions CloudCore.xcodeproj/xcshareddata/xcschemes/CloudCore.xcscheme
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0900"
LastUpgradeVersion = "1010"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand All @@ -26,9 +26,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
Expand Down Expand Up @@ -57,7 +56,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
Expand Down
2 changes: 1 addition & 1 deletion Example/Sources/Class/NotificationsObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class CloudCoreDelegateHandler: CloudCoreDelegate {
os_log("✅ Finished saving to iCloud", log: OSLog.default, type: .debug)
}

func error(error: Error, module: Module?) {
func error(error: Error, module: Module) {
print("⚠️ CloudCore error detected in module \(String(describing: module)): \(error)")
}

Expand Down
13 changes: 9 additions & 4 deletions Source/Classes/CloudCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import CloudKit

You can also check for updated data at CloudKit **manually** (e.g. push notifications are not working). Use for that `CloudCore.fetchAndSave(to:error:completion:)`
*/
open class CloudCore {
@objc @objcMembers open class CloudCore: NSObject {

// MARK: - Properties

Expand Down Expand Up @@ -93,7 +93,7 @@ open class CloudCore {
// Fetch updated data (e.g. push notifications weren't received)
let updateFromCloudOperation = FetchAndSaveOperation(persistentContainer: container)
updateFromCloudOperation.errorBlock = {
self.delegate?.error(error: $0, module: .some(.fetchFromCloud))
self.delegate?.error(error: $0, module: .fetchFromCloud)
}

#if !os(watchOS)
Expand All @@ -112,6 +112,11 @@ open class CloudCore {

// FIXME: unsubscribe
}

/// Set Database Version
public static func setDatabaseVersion(_ version: Int) {
config.databaseVersion = version
}

// MARK: Fetchers

Expand Down Expand Up @@ -185,7 +190,7 @@ open class CloudCore {

static private func handle(subscriptionError: Error, container: NSPersistentContainer) {
guard let cloudError = subscriptionError as? CKError, let partialErrorValues = cloudError.partialErrorsByItemID?.values else {
delegate?.error(error: subscriptionError, module: nil)
delegate?.error(error: subscriptionError, module: .subscribeToCloud)
return
}

Expand All @@ -203,7 +208,7 @@ open class CloudCore {
}
}

delegate?.error(error: subscriptionError, module: nil)
delegate?.error(error: subscriptionError, module: .subscribeToCloud)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,12 @@ class FetchRecordZoneChangesOperation: Operation {
self.recordWithIDWasDeletedBlock?(recordID)
}
fetchOperation.recordZoneFetchCompletionBlock = { zoneId, serverChangeToken, clientChangeTokenData, isMore, error in
self.tokens.tokensByRecordZoneID[zoneId] = serverChangeToken

if let error = error {
self.errorBlock?(zoneId, error)
}
} else {
self.tokens.tokensByRecordZoneID[zoneId] = serverChangeToken
}

if isMore {
let moreOperation = self.makeFetchOperation(optionsByRecordZoneID: optionsByRecordZoneID)
Expand Down
20 changes: 14 additions & 6 deletions Source/Classes/Fetch/SubOperations/RecordToCoreDataOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,13 @@ class RecordToCoreDataOperation: AsynchronousOperation {
guard let serviceAttributes = NSEntityDescription.entity(forEntityName: entityName, in: context)?.serviceAttributeNames else {
throw CloudCoreError.missingServiceAttributes(entityName: entityName)
}

if let recordVersion = record.value(forKey: ServiceAttributeNames.recordVersion) as? Int {
guard recordVersion <= CloudCore.config.databaseVersion else {
CloudCore.tokens.canSaveToken = false
throw CloudCoreError.incompatibleVersion(recordVersion)
}
}

// Try to find existing objects
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
fetchRequest.predicate = NSPredicate(format: serviceAttributes.recordID + " == %@", record.recordID.encodedString)
Expand All @@ -74,11 +80,13 @@ class RecordToCoreDataOperation: AsynchronousOperation {
/// - recordDataAttributeName: attribute name containing recordData
private func fill(object: NSManagedObject, entityName: String, serviceAttributeNames: ServiceAttributeNames, context: NSManagedObjectContext) throws {
for key in record.allKeys() {
let recordValue = record.value(forKey: key)

let attribute = CloudKitAttribute(value: recordValue, fieldName: key, entityName: entityName, serviceAttributes: serviceAttributeNames, context: context)
let coreDataValue = try attribute.makeCoreDataValue()
object.setValue(coreDataValue, forKey: key)
if key != ServiceAttributeNames.recordVersion {
let recordValue = record.value(forKey: key)

let attribute = CloudKitAttribute(value: recordValue, fieldName: key, entityName: entityName, serviceAttributes: serviceAttributeNames, context: context)
let coreDataValue = try attribute.makeCoreDataValue()
object.setValue(coreDataValue, forKey: key)
}
}

// Set system headers
Expand Down
14 changes: 7 additions & 7 deletions Source/Classes/Save/CoreDataListener.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class CoreDataListener {
public init(container: NSPersistentContainer) {
self.container = container
converter.errorBlock = { [weak self] in
self?.delegate?.error(error: $0, module: .some(.saveToCloud))
self?.delegate?.error(error: $0, module: .saveToCloud)
}
}

Expand Down Expand Up @@ -82,7 +82,7 @@ class CoreDataListener {
try backgroundContext.save()
}
} catch {
listener.delegate?.error(error: error, module: .some(.saveToCloud))
listener.delegate?.error(error: error, module: .saveToCloud)
}

CloudCore.delegate?.didSyncToCloud()
Expand All @@ -91,7 +91,7 @@ class CoreDataListener {

private func handle(error: Error, parentContext: NSManagedObjectContext) {
guard let cloudError = error as? CKError else {
delegate?.error(error: error, module: .some(.saveToCloud))
delegate?.error(error: error, module: .saveToCloud)
return
}

Expand All @@ -103,25 +103,25 @@ class CoreDataListener {
// Create CloudCore Zone
let createZoneOperation = CreateCloudCoreZoneOperation()
createZoneOperation.errorBlock = {
self.delegate?.error(error: $0, module: .some(.saveToCloud))
self.delegate?.error(error: $0, module: .saveToCloud)
self.cloudSaveOperationQueue.cancelAllOperations()
}

// Subscribe operation
#if !os(watchOS)
let subscribeOperation = SubscribeOperation()
subscribeOperation.errorBlock = { self.delegate?.error(error: $0, module: .some(.saveToCloud)) }
subscribeOperation.errorBlock = { self.delegate?.error(error: $0, module: .saveToCloud) }
subscribeOperation.addDependency(createZoneOperation)
cloudSaveOperationQueue.addOperation(subscribeOperation)
#endif

// Upload all local data
let uploadOperation = UploadAllLocalDataOperation(parentContext: parentContext, managedObjectModel: container.managedObjectModel)
uploadOperation.errorBlock = { self.delegate?.error(error: $0, module: .some(.saveToCloud)) }
uploadOperation.errorBlock = { self.delegate?.error(error: $0, module: .saveToCloud) }

cloudSaveOperationQueue.addOperations([createZoneOperation, uploadOperation], waitUntilFinished: true)
case .operationCancelled: return
default: delegate?.error(error: cloudError, module: .some(.saveToCloud))
default: delegate?.error(error: cloudError, module: .saveToCloud)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ class ObjectToRecordConverter {
} else {
recordWithSystemFields = try object.setRecordInformation()
}

// Update record version
recordWithSystemFields.setValue(CloudCore.config.databaseVersion, forKey: ServiceAttributeNames.recordVersion)

var changedAttributes: [String]?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class ObjectToRecordOperation: Operation {
throw CloudCoreError.coreData("Unable to find managed object for record: \(record)")
}

let changedValues = managedObject.committedValues(forKeys: changedAttributes)
let changedValues = managedObject.committedValues(forKeys: nil)

for (attributeName, value) in changedValues {
if attributeName == serviceAttributeNames.recordData || attributeName == serviceAttributeNames.recordID { continue }
Expand Down
7 changes: 5 additions & 2 deletions Source/Enum/CloudCoreError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ public enum CloudCoreError: Error, CustomStringConvertible {

/// Custom error, description is placed inside associated value
case custom(String)



/// Incompatible record version error
case incompatibleVersion(Int)

/// CloudCore doesn't support relationships with `NSOrderedSet` type
case orderedSetRelationshipIsNotSupported(NSRelationshipDescription)

Expand All @@ -36,6 +38,7 @@ public enum CloudCoreError: Error, CustomStringConvertible {
case .cloudKit(let text): return "iCloud error: \(text)"
case .coreData(let text): return "Core Data error: \(text)"
case .custom(let error): return error
case .incompatibleVersion(let version): return "Record version \(version) / Database version \(CloudCore.config.databaseVersion)"
case .orderedSetRelationshipIsNotSupported(let relationship): return "Relationships with NSOrderedSet type are not supported. Error occured in: \(relationship)"
}
}
Expand Down
16 changes: 9 additions & 7 deletions Source/Enum/Module.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
import Foundation

/// Enumeration with module name that issued an error in `CloudCoreErrorDelegate`
public enum Module {

/// Save to CloudKit module
case saveToCloud

/// Fetch from CloudKit module
case fetchFromCloud
@objc public enum Module: Int {

/// Save to CloudKit module
case saveToCloud

/// Fetch from CloudKit module
case fetchFromCloud

/// No CloudKit module
case subscribeToCloud
}
5 changes: 5 additions & 0 deletions Source/Model/CloudCoreConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ public struct CloudCoreConfig {

/// subscriptionID's prefix for custom CKSubscription in public databases
var publicSubscriptionIDPrefix = "CloudCore-"

/// Database version to handle incompatible record version
///
/// Default value is 1
public var databaseVersion = 1

// MARK: Core Data
let contextName = "CloudCoreFetchAndSave"
Expand Down
1 change: 1 addition & 0 deletions Source/Model/ServiceAttributeName.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct ServiceAttributeNames {

static let valueRecordData = "recordData"
static let valueRecordID = "recordID"
static let recordVersion: String = "recordVersion"

let entityName: String
let recordData: String
Expand Down
9 changes: 7 additions & 2 deletions Source/Model/Tokens.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ import CloudKit
}
```
*/
open class Tokens: NSObject, NSCoding {
@objc @objcMembers open class Tokens: NSObject, NSCoding {

var tokensByRecordZoneID = [CKRecordZoneID: CKServerChangeToken]()
var canSaveToken = true

private struct ArchiverKey {
static let tokensByRecordZoneID = "tokensByRecordZoneID"
Expand All @@ -45,7 +46,7 @@ open class Tokens: NSObject, NSCoding {
/// Load saved Tokens from UserDefaults. Key is used from `CloudCoreConfig.userDefaultsKeyTokens`
///
/// - Returns: previously saved `Token` object, if tokens weren't saved before newly initialized `Tokens` object will be returned
open static func loadFromUserDefaults() -> Tokens {
public static func loadFromUserDefaults() -> Tokens {
guard let tokensData = UserDefaults.standard.data(forKey: CloudCore.config.userDefaultsKeyTokens),
let tokens = NSKeyedUnarchiver.unarchiveObject(with: tokensData) as? Tokens else {
return Tokens()
Expand All @@ -56,6 +57,10 @@ open class Tokens: NSObject, NSCoding {

/// Save tokens to UserDefaults and synchronize. Key is used from `CloudCoreConfig.userDefaultsKeyTokens`
open func saveToUserDefaults() {
guard canSaveToken else {
NSLog("CloudCore will not save tokens as incompatible version error occured")
return
}
let tokensData = NSKeyedArchiver.archivedData(withRootObject: self)
UserDefaults.standard.set(tokensData, forKey: CloudCore.config.userDefaultsKeyTokens)
UserDefaults.standard.synchronize()
Expand Down
Loading