SiriKit Access To Core Data

Hello,


I was wondering if its possible to access my apps (iOS) core data within my IntentHandler class. My initial thought was to give the Intent target access to my DataAccess class which handles my core data work. But within that class I reference my RoutineModel class. So I would need to give Intent target access to that. And of course RoutineModel references my Functions class which in return references a bunch of my cocoapods.


As you can see, this can be quit the access dilemma. I have come across such nested access right before but always manged to get away with either duplicating parts in a separate class or avoiding the use all together.


So this maybe a gneral question as to how one should handle such nested access rights.


EDIT: It just popped into my mind that I could technically import the whole Target (iOS app) like

@testable import MyTarget

or

import MyTarget


and get access to all classes. I recall doing this with the UI/Unit tests. Is this sort of accepted or intended? Is there another option(s)


EDIT 2: using import Chronic unfortunately doesn't work, results in some errors (see attachment)


Accepted Reply

I ended up getting this to workout nicely.

1. Core Data model in its own Framework

I ended up putting my .momd & corresponding NSManagedObjects into an embedded framework.


1.1 In my case I added a new project into my workspace

This is just so that I dont have too many targets in my main project. Also I have more flexbility in the future to move the framework out of my workspace and into its own. It also gets its own independent identifier


So I had 3 projects in my workspace

- Main project with my iOS, app extensions and watchKit

- My ChronicKit (with my frameworks)

- Pods project


2.2 I added an iOS & Watch framework targets

Make sure to add these targets to the new project within your workspace. You just hit target and on the iOS tab, select the cocoa touch framework. Do the same for the Watch framework. You will then have two targets. You can't name them the same so I named one ChronicKit and the other ChronicKitWatch


OPTIONAL

2.3 I also managed to get both targets product name to be ChronicKit

This is an important step if you want both your frameworks to be named XXXXXX.framework (ie the same) If you don't have a watch target and are not planning to share your .momd with it, this and adding the watch target above are unnesseccary. There is a setting in the build settings that you can set. The following two settings for BOTH targets need to be the same as the name of your framework should be.


Just in cae the image below doesnt appear, I'll write it out here: Product Module Name & Product Name


This is neccessary becaue when you share your framework to a file/class that has both iOS and Watch as targets, the import statement will not clash.

import ChronicKit

2. Moved my Core Data database to an App Group shared location

This was also a little tricky but doable and only neccessary on the iOS side so that my app extension have access to the database. To this you need to

2.1 Enable "App Group" capbability on the iOS target and app extensions


2.2 Migrate your database to a new location

This is the tricky part. What I did was check if a database exists in the old location, if so migrate it. This happens only once.


2.2.1 Define your old and new location

    static let oldLocationURL = sharedInstance.applicationDocumentsDirectory.appendingPathComponent("XXXXXX.sqlite")
    static let newLocationURL = sharedInstance.applicationGroupDocumentDirectory.appendingPathComponent("XXXXXX.sqlite")


2.2.2 Make your persistentStoreCoordinator point to the new location

            try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: newLocationURL, options: options)


2.2.3 Add the following two functions to whereever you handle core data stuff (wherever your persistentStoreCoordinator and stuff is)

    func checkIfMigrationRequired(oldLocationURL: URL, newLocationURL: URL) -> (needsMigration: Bool, targetURL: URL) {
        var needsMigration: Bool = false
        var targetURL: URL = DataAccess.newLocationURL
        if FileManager.default.fileExists(atPath: oldLocationURL.path) {
            needsMigration = true
            targetURL = DataAccess.oldLocationURL
        }
        if FileManager.default.fileExists(atPath: newLocationURL.path) {
            needsMigration = false
            targetURL = DataAccess.newLocationURL
        }
        print(needsMigration ,targetURL)
        return (needsMigration ,targetURL)
    }
    func migrateCoreDataStore(from oldLocationURL: URL, to newLocationURL: URL) {
        if FileManager.default.fileExists(atPath: oldLocationURL.path) {
            do {

                if let newStore = self.persistentStoreCoordinator.persistentStore(for: newLocationURL) {
                    try self.persistentStoreCoordinator.remove(newStore)
                }

                let oldStore = try self.persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: oldLocationURL, options: DataAccess.options)
                try self.persistentStoreCoordinator.migratePersistentStore(oldStore, to: newLocationURL, options: DataAccess.options, withType: NSSQLiteStoreType)
                try FileManager.default.removeItem(at: oldLocationURL)

                print("CoreData store moved")
            }
            catch let error {
                print(error)
            }
        }
    }

2.2.4 do the migration once in the didFinishLaunchingWithOptions

        let (needsMigration, _) = DataAccess.sharedInstance.checkIfMigrationRequired(oldLocationURL: DataAccess.oldLocationURL, newLocationURL: DataAccess.newLocationURL)
        if needsMigration {
            DataAccess.sharedInstance.migrateCoreDataStore(from: DataAccess.oldLocationURL, to: DataAccess.newLocationURL)
        }

Replies

See Structuring Your App’s Services on https://developer.apple.com/library/prerelease/content/documentation/Intents/Conceptual/SiriIntegrationGuide/CreatingtheIntentsExtension.html#//apple_ref/doc/uid/TP40016875-CH4-SW1. Your data layer needs to be in a self-contained framework

I ended up getting this to workout nicely.

1. Core Data model in its own Framework

I ended up putting my .momd & corresponding NSManagedObjects into an embedded framework.


1.1 In my case I added a new project into my workspace

This is just so that I dont have too many targets in my main project. Also I have more flexbility in the future to move the framework out of my workspace and into its own. It also gets its own independent identifier


So I had 3 projects in my workspace

- Main project with my iOS, app extensions and watchKit

- My ChronicKit (with my frameworks)

- Pods project


2.2 I added an iOS & Watch framework targets

Make sure to add these targets to the new project within your workspace. You just hit target and on the iOS tab, select the cocoa touch framework. Do the same for the Watch framework. You will then have two targets. You can't name them the same so I named one ChronicKit and the other ChronicKitWatch


OPTIONAL

2.3 I also managed to get both targets product name to be ChronicKit

This is an important step if you want both your frameworks to be named XXXXXX.framework (ie the same) If you don't have a watch target and are not planning to share your .momd with it, this and adding the watch target above are unnesseccary. There is a setting in the build settings that you can set. The following two settings for BOTH targets need to be the same as the name of your framework should be.


Just in cae the image below doesnt appear, I'll write it out here: Product Module Name & Product Name


This is neccessary becaue when you share your framework to a file/class that has both iOS and Watch as targets, the import statement will not clash.

import ChronicKit

2. Moved my Core Data database to an App Group shared location

This was also a little tricky but doable and only neccessary on the iOS side so that my app extension have access to the database. To this you need to

2.1 Enable "App Group" capbability on the iOS target and app extensions


2.2 Migrate your database to a new location

This is the tricky part. What I did was check if a database exists in the old location, if so migrate it. This happens only once.


2.2.1 Define your old and new location

    static let oldLocationURL = sharedInstance.applicationDocumentsDirectory.appendingPathComponent("XXXXXX.sqlite")
    static let newLocationURL = sharedInstance.applicationGroupDocumentDirectory.appendingPathComponent("XXXXXX.sqlite")


2.2.2 Make your persistentStoreCoordinator point to the new location

            try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: newLocationURL, options: options)


2.2.3 Add the following two functions to whereever you handle core data stuff (wherever your persistentStoreCoordinator and stuff is)

    func checkIfMigrationRequired(oldLocationURL: URL, newLocationURL: URL) -> (needsMigration: Bool, targetURL: URL) {
        var needsMigration: Bool = false
        var targetURL: URL = DataAccess.newLocationURL
        if FileManager.default.fileExists(atPath: oldLocationURL.path) {
            needsMigration = true
            targetURL = DataAccess.oldLocationURL
        }
        if FileManager.default.fileExists(atPath: newLocationURL.path) {
            needsMigration = false
            targetURL = DataAccess.newLocationURL
        }
        print(needsMigration ,targetURL)
        return (needsMigration ,targetURL)
    }
    func migrateCoreDataStore(from oldLocationURL: URL, to newLocationURL: URL) {
        if FileManager.default.fileExists(atPath: oldLocationURL.path) {
            do {

                if let newStore = self.persistentStoreCoordinator.persistentStore(for: newLocationURL) {
                    try self.persistentStoreCoordinator.remove(newStore)
                }

                let oldStore = try self.persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: oldLocationURL, options: DataAccess.options)
                try self.persistentStoreCoordinator.migratePersistentStore(oldStore, to: newLocationURL, options: DataAccess.options, withType: NSSQLiteStoreType)
                try FileManager.default.removeItem(at: oldLocationURL)

                print("CoreData store moved")
            }
            catch let error {
                print(error)
            }
        }
    }

2.2.4 do the migration once in the didFinishLaunchingWithOptions

        let (needsMigration, _) = DataAccess.sharedInstance.checkIfMigrationRequired(oldLocationURL: DataAccess.oldLocationURL, newLocationURL: DataAccess.newLocationURL)
        if needsMigration {
            DataAccess.sharedInstance.migrateCoreDataStore(from: DataAccess.oldLocationURL, to: DataAccess.newLocationURL)
        }