Simplified access to Apple's CloudKit

Overview

EVCloudKitDao

Build Status Issues Stars Documentation Version Awesome

Language Platform Support License

Git Twitter LinkedIn Website eMail

Discuss EVCloudKitDao : Join the chat at https://gitter.im/evermeer/EVCloudKitDao

What is this

With Apple CloudKit, you can focus on your client-side app development and let iCloud eliminate the need to write server-side application logic. CloudKit provides you with Authentication, private and public database, structured and asset storage services - all for free with very high limits. For more information see Apple CloudKit documentation

This is a library to simplify the access to Apple's CloudKit data and notifications (see a more detailed description below)

  • EVCloudKitDao.swift for if you want control over your in app data and notifications.
  • EVCloudData.swift will let you handle CloudKit data as easy as possible.
  • EVglobal.swift for a couple of easy to use global bridging functions (EVLog and EVtry)

There is a dependency with EVReflection This will automatically be setup if you are using cocoapods.

  • EVReflection for if you want easy to use reflection methods. (not only for CloudKit)

See the Quick Help info for method descriptions or the documentation at cocoadocs.org

The AppMessage demo is a complete functional messaging app based on CloudKit:

  • News items are fully functional. Just try adding, deleting and updating newsitems from the CloudKit dashboard.
  • The Contacts list based on your phone contacts that also have installed the app.
  • Chat with someone using text messages, pictures and sending your location
  • A search window (async autocomplete) where you can search all chat messages using a tokenized or begiswith query
  • It also has TestViewController.swift for an overview of the functionality

I'm looking for feedback. Please let me know if you want something changed or added to the library or the demo.

A picture says more than 1000 words

Here are screenshots of the included demo app chat functionality:

Screenshot0 Screenshot1

Screenshot2 Screenshot3

Documentation

Documentation is now available at cocoadocs.org

Main features of EVCloudKitDao:

  • simple singleton access to your public or private database and containers (default and named)
  • Object mapping: You do not have to parse from and to CKRecord (mapping is based on reflection, including system fields)
  • Generic and simplified query handling
  • Error handling (separate completionHandler and errorHandler code blocks)
  • Storing CKReference objects
  • Storing CKAsset objects
  • Optionally auto continue reading from cursor (batch query)
  • Organising subscription
  • Handling incoming notifications
  • (Re)setting the badge

Main features of EVCloudData:

  • Use just one predicate for a query, subscription and processing incoming notifications.
  • it's only one method call with a couple of callback events (optional which to use)
  • it will store the fetched data collection in memory.
  • notifications will update the data collections and call the appropriate events.
  • local updates will also update the data collection and call the appropriate events
  • since all data is processed all callback events will be executed on the mainQueue
  • caching of the results to a file for speedy app restart. (You can set the caching strategy)
  • Internal app notifications using NSNotificationCenter

Main features of EVglobal

  • EVLog as a replacement for NSLog which will also output the file, function and line number.

Known issues (Swift limitations)

  • If you add a property to your object of type CKReference, then also add a property of type String for the RecordID.recordName. You could add a setter for populating both properties. Then if you query this using a NSPredicate, then query the string field and not the CKReference field. You have to do this because a NSPredicate works difrently for NSCloudkit than for an object. The EVCloudData class needs them to function in the same way. For a sample, see the Message class.

  • Optional objects properties can now be used. Optional type properties not. Swift is not able to do a .setValue forKey on an optional like Int? or Double? As a workaround for this you could use a NSNumber? This limitation is part of EVReflection

External components for the demo

The AppMessage demo is using the following components which can be installed using CocoaPods. See instructions below. Because of dependency compatibility the AppMessage demo requires Xcode 6.2 or later.

  • EVReflection - Swift helper library with reflection functions
  • SSASideMenu - A Swift implementation of RESideMenu
  • JSQMessagesViewController - An elegant messages UI library
  • JSQSystemSoundPlayer - A fancy Obj-C wrapper for iOS System Sound Services
  • CRToast - A modern iOS toast view that can fit your notification needs
  • UIImage-Resize - Category to add some resizing methods to the UIImage class, to resize it to a given CGSize — or fit in a CGSize keeping aspect ratio
  • SwiftLocation - iOS CoreLocation Wrapper made in Swift
  • UzysAssetsPickerController - Alternative UIImagePickerController , You can take a picture with camera and choose multiple photos and videos
  • VIPhotoView - View a photo with simple and basic interactive gesture
  • Async Syntactic sugar in Swift for asynchronous dispatches in Grand Central Dispatch
  • PermissionScope - Intelligent iOS permissions UI and unified API

Besides these the dependency to EVCloudKitDao has been skipped by just using the classes directly

Using EVCloudKitDao or EVCloudData in your own App

'EVCloudKitDao' is now available through the dependency manager CocoaPods. You do have to use cocoapods version 0.36. At this moment this can be installed by executing:

[sudo] gem install cocoapods

If you have installed cocoapods version 0.36 or later, then you can just add EVCloudKitDao to your workspace by adding the folowing 2 lines to your Podfile:

use_frameworks!
pod "EVCloudKitDao"

I have now moved on to Swift 2. If you want to use EVCloudKitDao with Swift 1.2, then get that version by using the podfile command:

use_frameworks!
pod 'EVReflection', :git => 'https://github.com/evermeer/EVReflection.git', :branch => 'Swift1.2'
pod 'SwiftTryCatch'
pod 'EVCloudKitDao', '~> 2.6'

Version 0.36 of cocoapods will make a dynamic framework of all the pods that you use. Because of that it's only supported in iOS 8.0 or later. When using a framework, you also have to add an import at the top of your swift file like this:

import EVCloudKitDao

If you want support for older versions than iOS 8.0, then you can also just copy the Cloudkit folder containing the 5 classes EVCloudKitDao, EVCloudData, EVReflection, EVCloudData and EVglobal to your app.

When you have added EVCloudKitDao to your project, then have a look at the AppMessage code for how to implement push notifications and how to connect to CloudKit data (see AppDelegate.swift and LeftMenuViewController.swift) For contacts see the RightMenuViewController.swift and for other usage see the TestsViewController.swift

Building the AppMessage demo

  1. Clone the repo to a working directory

  2. CocoaPods is used to manage dependencies. Pods are setup easily and are distributed via a ruby gem. Follow the simple instructions on the website to setup. After setup, run the following command from the toplevel directory of AppMessage to download the dependencies for AppMessage:

pod install
  1. Open the AppMessage.xcworkspace in Xcode.

  2. Go to AppMessage target settings and update the:

  • bundle name (usually your own reversed domain)
  • Change the team settings (your own certificate that is enabled for push notifications)
  • fix the iCloud capabilities. (check key-value store and CloudKit with a default container)
  • fix the capabilities for Background Modes 'Background fetch' and 'Remote notifications'
  1. Build and Run the app. In the AppDelegate there is a call to initiate all objects (createRecordTypes). All required CloudKit objects will be created.

  2. Open the CloudKit dashboard, select all recordtypes and enable all 'Metadata Indexes'

  3. Disable the call to .createRecordTypes in AppDelegate and run the app again.

  4. Make sure you run the app on 2 devices, each using a diverent iCloud account and each device having the other account in it's contact list.

and you are ready to go!

How to use the EVCloudData

Below is all the code you need to setup a news feed including push notification handling for any changes.

// Just enherit from CKDataObject so that you have access to the CloudKit metadata
class News : CKDataObject {
    var Subject : String = ""
    var Text : String = ""
}

class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
        // Make sure we receive subscription notifications
        application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: .Alert | .Badge | .Sound, categories: nil))
        application.registerForRemoteNotifications()
        return true
    }

    func application(application: UIApplication!, didReceiveRemoteNotification userInfo: [NSObject : NSObject]!) {
        EVLog("Push received")
        EVCloudData.publicDB.didReceiveRemoteNotification(userInfo, {
            EVLog("Not a CloudKit Query notification.")            
        })
    }

    func applicationDidEnterBackground(application: UIApplication) {
        // If you do a backup then this backup will be reloaded after app restart.
        EVCloudData.publicDB.backupData()        
    }
}


class LeftMenuViewController: UIViewController {
    var newsController: NewsViewController!

    override func viewDidLoad() {
        super.viewDidLoad()
        connectToNews()
        // Only already setup CloudKit connect's will receive these notifications (like the News above)
        EVCloudData.publicDB.fetchChangeNotifications()        
    }

    deinit {
        EVCloudData.publicDB.disconnect("News_All")
    }

    func connectToNews() {
        EVCloudData.publicDB.connect(News()
        , predicate: NSPredicate(value: true)
        , filterId: "News_All"
        , configureNotificationInfo: { notificationInfo in
            notificationInfo.alertBody = "New news item"
            notificationInfo.shouldSendContentAvailable = true }
        , completionHandler: { results in
            EVLog("There are \(results.count) existing news items")
            self.newsController.tableView.reloadData()
            return results.count < 200 // Continue reading if we have less than 200 records and if there are more.
        }, insertedHandler: {item in
            Helper.showStatus("New News item: '\(item.Subject)'")
            self.newsController.tableView.reloadData()
        }, updatedHandler: {item in
            Helper.showStatus("Updated News item:'\(item.Subject)'")
            self.newsController.tableView.reloadData()
        }, deletedHandler: {recordId in
            Helper.showStatus("News item was removed")
            self.newsController.tableView.reloadData()
        }, dataChangedHandler : {
            EVLog("Some News data was changed")
        }, errorHandler: {error in
            Helper.showError("Could not load news: \(error.description)")
        })
    }
}


class NewsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate, RESideMenuDelegate {
	...
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cell:UITableViewCell! = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? UITableViewCell
        ...
        //This line all you need to get the correct data for the cell
        var news:News = EVCloudData.publicDB.data["News_All"]![indexPath.row] as News

        cell.textLabel?.text = news.Subject
        cell.detailTextLabel?.text = news.Body
        return cell;
    }
}

How to use the EVCloudKitDao

// Just enherit from CKDataObject so that you have access to the CloudKit metadata
class Message : CKDataObject {
    var From : String = ""
    var To : String = ""
    var Text : String = ""
}

let dao: EVCloudKitDao = EVCloudKitDao.publicDB
let dao2 = EVCloudKitDao.publicDBForContainer("iCloud.nl.evict.myapp")

var message = Message()
message.From = "[email protected]"
message.To = "[email protected]"
message.Text = "This is the message text"

dao.saveItem(message, completionHandler: {record in
        createdId = record.recordID.recordName;
        EVLog("saveItem : \(createdId)");
    }, errorHandler: {error in
        EVLog("<--- ERROR saveItem");
    })

dao.query(Message()
    , completionHandler: { results in
        EVLog("query : result count = \(results.count)")
    }, errorHandler: { error in
        EVLog("<--- ERROR query Message")
    })

All you need for a keyword search (async autocomplete)

var queryRunning:Int = 0
var data:[Message] = []

func searchDisplayController(controller: UISearchDisplayController!, shouldReloadTableForSearchString searchString: String!) -> Bool {
    self.filterContentForSearchText(searchString)
    return false
}

func searchDisplayController(controller: UISearchDisplayController!, shouldReloadTableForSearchScope searchOption: Int) -> Bool {
    self.filterContentForSearchText(self.searchDisplayController!.searchBar.text)
    return false
}

func filterContentForSearchText(searchText: String) {
    EVLog("Filter for \(searchText)")
    networkSpinner(1)
    EVCloudKitDao.publicDB.query(Message(), tokens: searchText, completionHandler: { results in
        EVLog("query for tokens '\(searchText)' result count = \(results.count)")
        self.data = results
        NSOperationQueue.mainQueue().addOperationWithBlock {
            self.searchDisplayController!.searchResultsTableView.reloadData()
            self.tableView.reloadData()
            self.networkSpinner(-1)
        }
    }, errorHandler: { error in
        EVLog("ERROR: query Message for words \(searchText)")
        self.networkSpinner(-1)
    })
}

func networkSpinner(adjust: Int) {
    self.queryRunning = self.queryRunning + adjust
    UIApplication.sharedApplication().networkActivityIndicatorVisible = self.queryRunning > 0
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return data.count
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cellIdentifier = "Folowin_Search_Cell";
    var cell:UITableViewCell! = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? UITableViewCell
    if cell == nil {
        cell = UITableViewCell(style: .Subtitle, reuseIdentifier: cellIdentifier)
    }

    var item:Message = data[indexPath.row]
    cell.textLabel?.text = item.Text
    return cell;
}

Error handling

All cloudkit function have an errorHandler codeblock. You should handle the error appropriate. There is a helper function for getting a functional error status. In most cases you would get something like the code below. When you are doing data manupilations you should also handle the .RecoverableError

func initializeCommunication(retryCount: Double = 1) {
    ...
    }, errorHandler: { error in
        switch EVCloudKitDao.handleCloudKitErrorAs(error, retryAttempt: retryCount) {
            case .Retry(let timeToWait):
                Async.background(after: timeToWait) {
                    self.initializeCommunication(retryCount: retryCount + 1)
                }
            case .Fail:
                if error.code == .limitExceeded {
                    //TODO: try again with a smaller load?
                }            
                Helper.showError("Could not load messages: \(error.localizedDescription)")
            default: // For here there is no need to handle the .Success, and .RecoverableError
                break
        }
    })
}

License

EVCloudKitDao is available under the MIT license. See the LICENSE file for more info.

My other libraries:

Also see my other open source iOS libraries:

  • EVReflection - Reflection based (Dictionary, CKRecord, JSON and XML) object mapping with extensions for Alamofire and Moya with RxSwift or ReactiveSwift
  • EVCloudKitDao - Simplified access to Apple's CloudKit
  • EVFaceTracker - Calculate the distance and angle of your device with regards to your face in order to simulate a 3D effect
  • EVURLCache - a NSURLCache subclass for handling all web requests that use NSURLReques
  • AlamofireOauth2 - A swift implementation of OAuth2 using Alamofire
  • EVWordPressAPI - Swift Implementation of the WordPress (Jetpack) API using AlamofireOauth2, AlomofireJsonToObjects and EVReflection (work in progress)
  • PassportScanner - Scan the MRZ code of a passport and extract the firstname, lastname, passport number, nationality, date of birth, expiration date and personal numer.
  • AttributedTextView - Easiest way to create an attributed UITextView with support for multiple links (url, hashtags, mentions).

Evolution of EVReflection (Gource Visualization)

Evolution of EVReflection (Gource Visualization)

Comments
  • Can a single record type be defined by multiple child classes?

    Can a single record type be defined by multiple child classes?

    I was hoping to create a single private CKRecord type that contains fields that are defined by multiple child classes. For example, an ApplicationOptions record type that contains all options, but having those options divided up into child classes, like the example below. I thought a way to do this might be to have the child classes inherit from EVObject, like so:

    public class ApplicationOptions: EVCloudKitDataObject {
        public var audio = AudioOptions()
        public var video = VideoOptions()
        public var playback = PlaybackOptions()
    }
    
    public class AudioOptions: EVObject {
        public audioPreferenceOne: Bool = false
    }
    
    public class VideoOptions: EVObject {
        public videoPreferenceOne: Bool = false
    }
    
    public class PlaybackOptions: EVObject {
        public playbackPreferenceOne: Bool = false
    }
    

    The desired result would be an ApplicationOptions CKRecord that contains fields named something like audio_audioPreferenceOne, video_videoPreferenceOne and playback_playbackPreferenceOne. In this case, there will only ever be a single record instance for application options, and it would be overkill imho to also create separate record types for each of the child classes. They are also expected to only result in a single record instance per user.

    When I attempt something like the above example, I get this unhandled exception:

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Objects of class __NSDictionaryM cannot be set on CKRecord'
    

    Is there any way to accomplish this type of thing, or is this beyond the scope of what is supposed to be possible? Thanks...

    enhancement fixed? 
    opened by tbaggett 41
  • Sub-objects get reinitialized after parent objects during transfer of data from CloudKit

    Sub-objects get reinitialized after parent objects during transfer of data from CloudKit

    I have found the root cause of some of my problems with property values not being their expected values when retrieving data saved in CloudKit using EVCloudKitDao. I'm not sure if this can be easily or at all fixed though. Please review my observations on this and provide your thoughts.

    Consider a class structure like the following:

    public class A : EVCloudKitDataObject {
        public var b = B()
    
        public required init() {
            super.init()
            b.intProp = 9
        }
    }
    
    public class B: EVCloudKitDataObject {
        public required init() {
            super.init()
            intProp = 5
        }
    
        public var intProp = 0 {
            didSet {
                EVLog("intProp set to \(intProp)")
            }
        }
    
        // NOT storing the intProp property value in CloudKit is key to this issue
        override public func propertyMapping() -> [(String?, String?)] {
            return [("intProp", nil)]
        }
    }
    

    If you set breakpoints in Class A's assignment of 9 to the b.intProp property and the EVLog("intProp set to...") statements, you will see the following behavior in action.

    If an instance of class A is saved to CloudKit using EVCloudKitDao, then retrieved, the value of the "b" variable in the class A instance will be 5, not 9 as expected. This is due to a second instance of class B being directly instantiated by EVCloudKitDao and assigned to the class A instance's "b" property after it has already been instantiated from an instance of Class A. This also means sub-objects are instantiated multiple times, one per level of however deep they are nested in the root EVCloudKitDataObject being saved.

    Is it possible to assign stored values to the already-instantiated sub-objects instead of instantiating new instances? If not, I believe this means I will have to remove any objects that I was expecting EVCloudKitDao to ignore using the propertyMapping override.

    fixed? 
    opened by tbaggett 17
  • Completion Handlers, iCloud Status Monitoring, Per-User Local Cache Changes FOR REVIEW

    Completion Handlers, iCloud Status Monitoring, Per-User Local Cache Changes FOR REVIEW

    Hi Edwin,

    These changes ended up being pretty substantial. As I mentioned before, my experience with iOS/tvOS is limited, and my app, along with your project, is the first Swift coding I've done. I have no doubt that these changes could be implemented differently, and could probably be implemented in better, simpler, and/or smarter ways. I welcome your review and comments. I have already learned a lot from your project, and fully expect to learn more from us discussing these changes. I hope your schedule will allow you to do so.

    Here's some notes on my thinking behind the changes:

    • IMHO, adding the completion handlers while maintaining the publicDB, privateDB and ...forContainer DB constants required keeping the two fully separate. I also thought that being able to assign multiple completion handlers was a reasonable expectation. My changes support that behavior.
    • I also included support for automatically calling completion handlers that are added after a constant DB entity is initialized. The new handlers are called the next time the constant entity is referenced. All assigned handlers are called again in the event of a DB reinitialization.
    • A wrapper class was needed to facilitate easy removal of handlers if they were no longer needed. The wrapper class is returned as a token that the calling code is expected to keep up with, then call the wrapper's releaseToken method when the associated handler should no longer be called. I also created a manager class that I use in my app to keep up with the tokens and remove all of them when it goes out of scope. I can include that if you'd like.
    • Per-user caching now uses the iCloud account's user recordId as part of the file name. This change required some semaphore usage to wait on the recordId to be retrieved the first time. Also, the local cache file name is constructed the same as before if no user recordId is returned. This may need to be changed.
    • I had to change the iCloud status monitoring code from the sample code I originally provided in the feature request. I found that code was broken in varying ways depending on the OS version being used. It now uses fetchUserRecordIDWithCompletionHandler, which is also used to construct the local cache file names.
    • Currently, an EVCloudKitDao DB instance has its activeUser and (new) activeUserId properties set to nil, followed by the initializeDataBase method being called, for all public and private DB instances when an iCloud status change is detected. I'm not sure if anything else needs to be reset or not (subscriptions maybe?). Please let me know or make the needed changes if other things are needed.
    • EVCloudData adds a completion handler that calls disconnectAll() to each EVCloudKitDao instance that its constant entities depend on. This completion handler is given a higher priority than app-provided ones. It should always be called first, avoiding the possibility of disconnecting everything after the app-provided handlers added their needed connections.
    • Being a Swift newb, I am still a little fuzzy on when closures can cause a circular reference that prevents them and their enclosing code from being released from memory. I've read about things like specifying [weak self] and [unowned self] in closures. However I'm hesitant to apply them since they can also cause premature releasing of things before everything finishes executing. I will eventually get around to using the profiler and getting a clearer understanding of when they're needed. In the meantime, discussion of this area is definitely welcomed.
    • While the sample AppMessage app compiles successfully (albeit with the same warnings it exhibited prior to my changes), I didn't make any modifications to it to take advantage of my changes or to kickstart the iCloud account status monitoring. I made those changes in the tvOS app that I'm working and tested the changes from my app. We can discuss what needs to be done to integrate and reasonably exercise my changes in the sample app.
    • I didn't make any documentation changes. I fully expect further changes to be needed, so I didn't see the point until everything is finalized.
    • Finally, I didn't change the pod version number as I wasn't sure what you'd want it changed to with this many changes.
    opened by tbaggett 15
  • Swift 4?

    Swift 4?

    Hey guys.

    Do you have any news about EVCloudKitDAO on Swift4? I've tried to fix the errors I got, but I'm not advanced programmer and I couldn't be able to find out the solution.

    These two lines contains the same error. image

    And this error is reported too. image

    I would appreciate if you guys could help me on that.

    Thanks in advance.

    fixed? 
    opened by alissonbarauna 14
  • EVCloudKitDao not connecting to app's default container if explicitly specified

    EVCloudKitDao not connecting to app's default container if explicitly specified

    I need to connect to the same container from both my tvOS app and its TVTopShelfProvider extension. Thus far during the development of my app, I've been using its default container and EVCloudKit's publicDB and privateDB accessors. According to the Apple iCloud documentation (quoted below), I should be able to connect to one app's default container from another app that is developed using the same developer team account.

    I attempted to do this by changing references to publicDB and privateDB to publicDBForContainer and privateDBForContainer and passing my app's bundle identifier name as the container name. After doing this, I don't see any error messages in the console output, but my app is no longer retrieving any data. Will I need to create a custom container to be used by the app and extension in order for this to work with EVCloudKitDao?

    _Share Containers Between Apps_

    Optionally, configure your app to use multiple containers or share a container with other apps. For example, you might use one app internally to create record types and records programmatically to return a database to a known state. This app needs to share the same container as the end-user app you are developing and testing. _To do this, you enable the first app to use the default container of the second app_ or create a custom container that both apps share. iOS and Mac apps can also be configured to share the same containers.

    fixed? 
    opened by tbaggett 13
  • Swift 1.2 swiftClassFromString always return nil

    Swift 1.2 swiftClassFromString always return nil

    Tried several ways in this thread to little avail. http://stackoverflow.com/questions/24030814/swift-language-nsclassfromstring

    e.g. @objc(Messsage) class Message : EVCloudKitDataObject

    Fix: In EVReflection: Use objc_getClass(anyobjectTypeString) instead of swiftClassFromString(anyobjectTypeString).

    enhancement 
    opened by InfiniteRandomVariable 11
  • Unhandled exception when processing notifications that an item has been deleted

    Unhandled exception when processing notifications that an item has been deleted

    Quick question for you... I am continuing to test and ran into an issue. I am simulating situations where items are added on one device and removed on another. I am hitting an unhanded exception if I follow these steps:

    1. Launch my tvOS app with at least one of my EVCloudData object types in existence
    2. Return to main Apple TV screen but leave my app running in Xcode
    3. Delete the EVCloudData object record using the iCloud dashboard in a browser
    4. Return to my app on the Apple TV and BOOM goes the dynamite.

    The unhandled exception is occurring in the EVCloudData deleteObject method.

    /**
     Delete an object from every data collection where it's part of
    
     - parameter recordId: The recordId of the object that will be deleted
     :return: No return value
     */
    private func deleteObject(recordId: String) {
        for (filter, _) in self.data {
            let itemID: Int? = data[filter]!.EVindexOf {item in return item.recordID.recordName == recordId}
            if itemID != nil {
                data[filter]!.removeAtIndex(itemID!)
                NSOperationQueue.mainQueue().addOperationWithBlock {
                    (self.deletedHandlers[filter]!)(recordId: recordId, dataIndex:itemID!)
                    (self.dataChangedHandlers[filter]!)()
                    self.postDataDeletedNotification(filter, recordId: recordId, dataIndex: itemID!)
                }
                dataIsUpdated(filter)
            }
        }
    }
    

    This line of code is the culprit:

    let itemID: Int? = data[filter]!.EVindexOf {item in return item.recordID.recordName == recordId}
    

    It blows up when the item's recordName/recordId comparison is attempted. Here's the exception output:

    2015-11-30 15:52:47.748 Marquees[1818:672274] -[__NSDictionaryM recordName]: unrecognized selector sent to instance 0x147758100
    2015-11-30 15:52:47.751 Marquees[1818:672274] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryM recordName]: unrecognized selector sent to instance 0x147758100'
    *** First throw call stack:
    (0x1857458e8 0x184db3f80 0x18574c5fc 0x1857495a0 0x18564d6cc 0x1001e18f4 0x1001e0d40 0x1001d5da4 0x1001d6278 0x1001e55f0 0x1001f2f20 0x1001db530 0x100083010 0x1000830f8 0x18a6200b0 0x18a60ef20 0x186ceb790 0x186cebb10 0x1856fcee4 0x1856fc978 0x1856fa678 0x1856296c0 0x186b34088 0x18a3d1d64 0x1000855b0 0x1851ca974)
    libc++abi.dylib: terminating with uncaught exception of type NSException
    Message from debugger: Terminated due to signal 6
    

    Prior to executing the problem line, I inspected the item:

    Printing description of item:
    (EVCloudKitDao.EVCloudKitDataObject) item = 0x00000001476fb3f0 {
        EVReflection.EVObject = {
            ObjectiveC.NSObject = {}
        }
        recordID = 0x0000000147758100 {
            ObjectiveC.NSObject = {}
        }
        recordType = "Performance"
        creationDate = 0x000000014775cab0 2015-11-30 21:48:51 UTC
        creatorUserRecordID = Some {
            Some = 0x0000000147757ec0 {
                ObjectiveC.NSObject = {}
            }
        }
        modificationDate = 0x000000014775cb20 2015-11-30 21:48:51 UTC
        lastModifiedUserRecordID = Some {
            Some = 0x000000014773f5e0 {
                ObjectiveC.NSObject = {}
            }
        }
        recordChangeTag = "ihmhmw90"
        encodedSystemFields = 0 bytes {
            Some = 0x000000014773f060 0 bytes
        }
    }
    

    Have you seen this issue before? I'm not sure if any of my recent changes would have impacted this or not.

    opened by tbaggett 10
  • Added data state change notifications, ConnectStatus enum

    Added data state change notifications, ConnectStatus enum

    Added NSNotificationManager-based push notifications to be used in addition to or instead of handlers passed to the connect method. Also changed the completion handler’s isFinished Bool parameter to a new “status” ConnectStatus enumeration value.

    Apologies for the extraneous whitespace changes. Also, while I tested the additions in my own tvOS app, I didn't add any testing to the AppMessages sample app. I can do so, perhaps in the TestsViewController if that's what you're using to test with, but wanted input from you before proceeding with that.

    Please let me know if you have any questions or concerns.

    opened by tbaggett 10
  • tvOS restricts local storage usage

    tvOS restricts local storage usage

    Hi Edwin,

    Per the tvOS documentation, local storage is very limited for tvOS apps. I wanted to get your thoughts on disabling local EVCloudData caching on tvOS or forcing it to store the local files in the cache folder instead of the documents directory. I will be happy to implement what is needed once I hear from you. Thanks!

    Apple documentation on tvOS storage:

    Local Storage for Your App Is Limited

    The maximum size of an Apple TV app is limited to 200 MB. Moreover, your app can only access 500 KB of persistent storage that is local to the device (using the NSUserDefaults class). Outside of this limited local storage, all other data must be purgeable by the operating system when space is low. You have a few options for managing these resources:

    • Your app can store and retrieve user data in iCloud.
    • Your app can download the data it needs into its cache directory. Downloaded data is not deleted while the app is running. However, when space is low and your app is not running, this data may be deleted. Do not use the entire cache space as this can cause unpredictable results.
    • Your app can package read-only assets using on-demand resources. Then, at runtime, your app requests the resources it needs, and the operating system automatically downloads and manages those resources. Knowing how and when to load new assets while keeping your users engaged is critical to creating a successful app. For information on on-demand resources, see On-Demand Resources Guide This means that every app developed for the new Apple TV must be able to store data in iCloud and retrieve it in a way that provides a great customer experience.
    opened by tbaggett 10
  • atomic operation issue

    atomic operation issue

    Existence must be checked before insert a record into table. but these are 2 operations(check&insert), when multiple users do this in the same time, it will cause issue of inserting 2 same records.

    How to handle this?

    UserA: |----> check ----> not existence ----> insert ----> insert succ UserB: |--------> check ----> not existence ----> insert ----> insert succ

    So 2 same records will be inserted into table.

    opened by foolbear 8
  • CKNotification cast to CKQueryNotification

    CKNotification cast to CKQueryNotification

    I just found an error occurred during startup, "Could not cast value of type 'CKNotification' (0x35cde2ac) to 'CKQueryNotification' (0x35cde2d4)." in the line : var queryNotification: CKQueryNotification = cloudNotification as! CKQueryNotification. This is inside the function didReceiveRemoteNotification() in the EVCloudKitDao.swift. I think its the downcast problem.

    opened by yoonack 8
  • App Store Rejection for Non-public API Usage of CKDiscoveredUserInfo

    App Store Rejection for Non-public API Usage of CKDiscoveredUserInfo

    Hey Edwin!

    I built and submitted via Xcode 11.5 and my app targets iOS 12+.

    Here's what Apple said in the binary rejection email I received (twice):

    ITMS-90338: Non-public API usage - The app contains or inherits from non-public classes in Frameworks/EVCloudKitDao.framework/EVCloudKitDao: CKDiscoveredUserInfo . If method names in your source code match the private Apple APIs listed above, altering your method names will help prevent this app from being flagged in future submissions. In addition, note that one or more of the above APIs may be located in a static library that was included with your app. If so, they must be removed. For further information, visit the Technical Support Information at http://developer.apple.com/support/technical/

    I know when I tried to build with Xcode 12 last week, the build failed due to CKDiscoveredUserInfo not existing anymore (I think there were deprecation warnings and now it's finally removed in iOS 14). But not sure what would cause a rejection already when not even using Xcode 12. 🤔

    opened by jstheoriginal 2
  • After creating a new object, object's recordID is a String

    After creating a new object, object's recordID is a String

    I add a new object using EVCloudData.publicDB.saveItem. Subsequently attempting to call recordID.recordName causes an exception to be thrown.

    'NSInvalidArgumentException', reason: '-[__NSCFString recordName]: unrecognized selector sent to instance 0x1c2e63980'

    Sometimes this happens in upsertObject at let itemID: Int? = data[filter]!.EVindexOf {i in return i.recordID.recordName == recordId}.

    I have a subscription by using connect(...) to the type, with the predicate value true (all items).

    This appears to be happening on several subclasses of CKDataObject when there is a 'connection' to the class.

    opened by mgregory 2
  • Using a semaphore could cause a wait forever

    Using a semaphore could cause a wait forever

    Reported by Joseph Gates:

    Hi,

    Now running with

    Using EVCloudKitDao (3.5.1) Using EVReflection (5.6.0)

    Testing worked fine with XCTAssertNotNil et al. Thanks for your help!

    I did find another issue. You may want me to add it to the issue tracker for EVCloudKitDao.

    What happens is that the app fails to complete start up. I tracked it to this in EVCloudKitDao.swift

    /**
    Set or reset the quick reference to the container and database
    
    - parameter containerIdentifier: Passing on the name of the container
    */
    open func initializeDatabase(_ containerIdentifier: String? = nil) {
        let pathDir = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
        if pathDir.count > 0 {
            fileDirectory = pathDir[0] as NSString!
        } else {
            fileDirectory = ""
        }
        filemanager = FileManager.default
        ioQueue = DispatchQueue(label: "NL.EVICT.CloudKit.ioQueue", attributes: []) as DispatchQueue
    
        if let identifier = containerIdentifier {
            container = CKContainer(identifier: identifier)
        } else {
            container = CKContainer.default()
        }
        if self.isType == .isPublic {
            database = container.publicCloudDatabase
        } else {
            database = container.privateCloudDatabase
        }
    
        let sema = DispatchSemaphore(value: 0)
        container.accountStatus(completionHandler: {status, error in
            if error != nil {
                EVLog("Error: Initialising EVCloudKitDao - accountStatusWithCompletionHandler.\n\(error!.localizedDescription)")
            } else {
                self.accountStatus = status
            }
            EVLog("Account status = \(status.hashValue) (0=CouldNotDetermine/1=Available/2=Restricted/3=NoAccount)")
            sema.signal()
        })
    

    // let _ = sema.wait(timeout: DispatchTime.distantFuture) //???!!! EVLog("Container identifier = (container.containerIdentifier.debugDescription)") }

    The second to last statement is let _ = sema.wait(timeout: DispatchTime.distantFuture) //???!!!

    It waits on the semaphore forever. I commented it out and everything runs as expected. Any suggestions on what I might have or have not done that would cause the wait forever? I’m sure that your other users would have reported this before now if it were a common mistake. I checked the open/closed issues bur haven’t seen anything related.

    Let me know how I can help, and thanks again for your help,

    Joseph Gates [email protected] mailto:[email protected]

    opened by evermeer 0
  • ERROR: Unexpected type while converting value for JsonSerialization

    ERROR: Unexpected type while converting value for JsonSerialization

    🌀 ERROR: Unexpected type while converting value for JsonSerialization: <CKRecordID: 0x1c4437260; recordName=BACAF2DF-2B84-4815-94F5-F246E27B5489, zoneID=_defaultZone:defaultOwner> 🌀 ERROR: Unexpected type while converting value for JsonSerialization: <CKReference: 0x1c4422ac0; recordID=<CKRecordID: 0x1c4233a00; recordName=53BA8280-6396-4A6D-9F51-40BF78D22FBE, zoneID=defaultZone:defaultOwner>> 09/22/2017 15:45:59:967 Reading)[380:.] EVCloudKitDao.swift(538) backupData(:toFile:): Data is written to /var/mobile/Containers/Data/Application/1A249C94-6DD9-439F-8DA2-FB5BC17CADF7/Library/Caches/Filter_TraceReaderCount.bak)

    fixed? 
    opened by foolbear 4
  • Carthage Support

    Carthage Support

    Hi,

    I really want to use this component. I just don't like using Cccoapods. Can this be used via Carthage (preferred)? Or (in the future) SPM when Swift 3.0 is released?

    enhancement help wanted 
    opened by mingsai 4
  • Should both public and private DBs be checked for notifications?

    Should both public and private DBs be checked for notifications?

    The fetchChangeNotifications method is accessed via EVCloudData.privateDB and EVCloudData.publicDB. If my app is storing data in both the public and private DBs, should calls to both DB's fetchChangeNotifications methods be made?

    Also, I see messages like this in the console all the time, indicating that received notifications are not CloudKit Query notifications, even though they do appear to be just that. Is this indicating a problem, or can it be safely ignored?

    01/20/2016 15:38:16:975 Marquees)[459:.] AppDelegate.swift(257) application(_:didReceiveRemoteNotification:):
        Push received
    
    01/20/2016 15:38:16:976 Marquees)[459:.] AppDelegate.swift(259) application(_:didReceiveRemoteNotification:):
        Not a CloudKit Query notification.
    
    01/20/2016 15:38:16:978 Marquees)[459:.] EVCloudKitDao.swift(1195) didReceiveRemoteNotification(_:executeIfNonQuery:inserted:updated:deleted:completed:):
        WARNING: The retrieved notification is not a CloudKit query notification.
    ===>userInfo = [ck: {
        ce = 2;
        cid = "iCloud.com.tommyb.Marquees";
        nid = "363ec9be-24c5-4c2f-949e-5989c0567272";
    }]
    notification = <CKNotification: 0x14470a510; notificationType=0, notificationID=<CKNotificationID: 0x144725e90; UUID=363ec9be-24c5-4c2f-949e-5989c0567272>, containerIdentifier=iCloud.com.tommyb.Marquees>
    

    Thanks for the needed clarification on this.

    opened by tbaggett 5
Releases(3.6.1)
Owner
Edwin Vermeer
Edwin Vermeer
Enables developers to write code that interacts with CloudKit in targets that don't support the CloudKit Framework directly

CloudKit Web Services This package enables developers to write code that interac

Eric Dorphy 2 Dec 27, 2021
Sync Realm Database with CloudKit

IceCream helps you sync Realm Database with CloudKit. It works like magic! Features Realm Database Off-line First Thread Safety Reactive Programming O

Soledad 1.8k Jan 6, 2023
This is the BlurrMC social media platform for iOS hosted on Apple's CloudKit.

BlurrMC for iOS This is the BlurrMC social media platform for IOS! WOOOOOOO! On this version of the social media platform, you have the full featured

BlurrMC 0 Nov 4, 2021
iForage helps foragers to track and manage foraging spots around them using CloudKit

iForage CloudKit Preface To expand on what I've created here: https://github.com/LynchConnor/iForage, I initially developed the app using Firebase. Th

Connor Lynch 3 Jul 14, 2022
CoreDataCloudKitShare - Learn how to use Core Data CloudKit

Sharing Core Data Objects Between iCloud Users Implement the flow to share data

null 3 Feb 10, 2022
CloudKit, Apple’s remote data storage service, provides a possibility to store app data using users’ iCloud accounts as a back-end storage service.

CloudKit, Apple’s remote data storage service, provides a possibility to store app data using users’ iCloud accounts as a back-end storage service. He

Yalantis 252 Nov 4, 2022
CodableCloudKit allows you to easily save and retrieve Codable objects to iCloud Database (CloudKit)

CodableCloudKit CodableCloudKit allows you to easily save and retrieve Codable objects to iCloud Database (CloudKit) Features ℹ️ Add CodableCloudKit f

Laurent Grondin 65 Oct 23, 2022
A stand-alone Swift wrapper around the mongo-c client library, enabling access to MongoDB servers.

This package is deprecated in favour of the official Mongo Swift Driver. We advise users to switch to that pack

PerfectlySoft Inc. 54 Jul 9, 2022
An alternative to Core Data for people who like having direct SQL access.

FCModel 2 An alternative to Core Data for people who like having direct SQL access. By Marco Arment. See the LICENSE file for license info (it's the M

null 1.7k Dec 28, 2022
Easy direct access to your database 🎯

OHMySQL ★★ Every star is appreciated! ★★ The library supports Objective-C and Swift, iOS and macOS. You can connect to your remote MySQL database usin

Oleg 210 Dec 28, 2022
A stand-alone Swift wrapper around the MySQL client library, enabling access to MySQL servers.

Perfect - MySQL Connector This project provides a Swift wrapper around the MySQL client library, enabling access to MySQL database servers. This packa

PerfectlySoft Inc. 118 Jan 1, 2023
A stand-alone Swift wrapper around the libpq client library, enabling access to PostgreSQL servers.

Perfect - PostgreSQL Connector This project provides a Swift wrapper around the libpq client library, enabling access to PostgreSQL servers. This pack

PerfectlySoft Inc. 51 Nov 19, 2022
A stand-alone Swift wrapper around the FileMaker XML Web publishing interface, enabling access to FileMaker servers.

Perfect - FileMaker Server Connector This project provides access to FileMaker Server databases using the XML Web publishing interface. This package b

PerfectlySoft Inc. 33 Jul 13, 2022
Simplified access to Apple's CloudKit

EVCloudKitDao Discuss EVCloudKitDao : What is this With Apple CloudKit, you can focus on your client-side app development and let iCloud eliminate the

Edwin Vermeer 632 Dec 29, 2022
🐵Fooling around with Apples private framework AvatarKit

Fooling around with Apples private framework AvatarKit, the framework used in Messages.app for recording Animoji videos. If you are looking to create your own Animoji, take a look at SBSCustomAnimoji.

Simon Støvring 968 Dec 25, 2022
Enables developers to write code that interacts with CloudKit in targets that don't support the CloudKit Framework directly

CloudKit Web Services This package enables developers to write code that interac

Eric Dorphy 3 May 11, 2022
Enables developers to write code that interacts with CloudKit in targets that don't support the CloudKit Framework directly

CloudKit Web Services This package enables developers to write code that interac

Eric Dorphy 2 Dec 27, 2021
Grand Central Dispatch simplified with swift.

GCDKit GCDKit is Grand Central Dispatch simplified with Swift. for Swift 1.2: Use version 1.0.1 for Swift 2.1 / 2.2: Use the master branch Introductio

John Estropia 317 Dec 6, 2022
Grand Central Dispatch simplified with swift.

GCDKit GCDKit is Grand Central Dispatch simplified with Swift. for Swift 1.2: Use version 1.0.1 for Swift 2.1 / 2.2: Use the master branch Introductio

John Estropia 317 Dec 6, 2022