ioscore-dataswift3watchos-3appgroups

Swift3: Empty fetch when accessing Core Data from Watch Extension via AppGroups


currently I'm working on an update for an already existing App (migration to Swift 3). I'm having targets for Today-, Search-, Message- and Watch Extensions. Every target needs to access the Core Data Model of my App, so I created an AppGroup and enabled the Capability for every target. Although I've subclassed the NSPersistentStoreCoordinator, so everything is stored in a shared folder:

import CoreData
class PFPersistentContainer: NSPersistentContainer {
    override open class func defaultDirectoryURL() -> URL {
        if let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "add-groupname-here") {
            return url
        }
        // Fallback
        return super.defaultDirectoryURL()
    }
}

This class-file, as well as my Core-Data-Model and the following class are all in target membership of all of the mentioned targets. The Codegen of the Entities is set to Class Definition. So far I'm using the default implementation for the Core Data Stack:

class DataManager {
    /**
     * Singleton Implementation
     */
    internal static let sharedInstance:DataManager = {
        let instance = DataManager()
        return instance
    }()

    // MARK: - Core Data stack
    lazy var persistentContainer: PFPersistentContainer = {
        let container = PFPersistentContainer(name: "Data")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()

    // MARK: - Core Data Saving support

    func saveContext () {
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }
}

Now the weird part: Accessing this data from the Today- and Message-Extension seems to work gracefully (I'm assuming the Search-Extension is working too, but so far as a developer I'm not able to test this). Trying to fetch it with the same Request from the Watch App's Extension results in an empty Array. Here is my fetch code:

    internal func getLimitedPOIsWithCalculatedDistance(andCurrentLocation currentLocation:CLLocation) -> Array<POI> {
        var poiArray:Array< POI > = []
        guard let context = self.backgroundContext else { return [] }
        do {
            // Initialize Fetch Request
            let request:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "POI")

            // Create Entity Description
            let entityDescription = NSEntityDescription.entity(forEntityName: "POI", in: context)

            // Configure Fetch Request
            request.entity = entityDescription

            // Configure Predicate
            let predicate = NSPredicate(format: "(disabled == NO) AND (locationDistanceCalculated <= \(SETTINGS_MAXIMUM_FETCH_DISTANCE))")
            request.predicate = predicate
            if let result = try context.fetch(request) as? Array<POI> {
                for poi in result {
                    let poiLocation = CLLocation(latitude: poi.locationLatitude, longitude: poi.locationLongitude)
                    let distance = currentLocation.distance(from: poiLocation)
                    poi.locationDistanceTransient = Double(distance)
                }

                poiArray = result.filter({ (poi) -> Bool in
                    return poi.locationDistanceTransient > 0.0
                })

                poiArray.sort { (first, second) -> Bool in
                    return first.locationDistanceTransient < second.locationDistanceTransient
                }
            }
        } catch {
            print("Error in WatchDataInterface.getLimitedPOIsWithCalculatedDistance(): \(error.localizedDescription)")
        }
        return poiArray
    }

A bit more context for better understanding: the POI Entitie contains latitude and longitude of a location. When starting the app, I'm pre-calculating the distance to each point in the database from the current user's position. When the Today-Extension tries to get the nearest x (in my case 8) POIs to the current users position, fetching all 15k POIs and calculating their distance is to much memory wise. So I had to pre-calculate the distance (stored in locationDistanceCalculated), then fetch in a given radius (the static SETTINGS_MAXIMUM_FETCH_DISTANCE) and calculate a precise distance during the fetch process (stored into a transient property locationDistanceTransient).

In the Watch App's ExtensionDelegate Class a CLLocationManagerDelegate is implemented and the code is called, when the User's Location is updated:

extension ExtensionDelegate: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        OperationQueue.main.addOperation{
            if let watchDataManager = self.watchDataManager {
                // getNearestPOIs() calls the getLimitedPOIsWithCalculatedDistance() function and returns only a numberOfPOIs
                self.nearbyPOIs = watchDataManager.getNearestPOIs(numberOfPOIs: 4)
            }
        }
    }
}

It was tested in Simulator and on a Device. The context.fetch always returns an empty array (Yes, core data contains values and yes I've tested it without the predicate). Am I missing anything new in Core Data, which I haven't considered yet or are their any limitations in WatchOS3 why this isn't working out? Does anyone has a clue? Thanks for your help.

Update: when using a Watch Framework target to access Core Data, like it's described in this project, the fetch keeps being empty. Maybe this could be the right path, but a Watch Framework is the wrong selection. Will keep you up to date.

Update 2: I've already checked the App Programming Guide for WatchOS and the transferFile:metadata: function in the API References, but it doesn't seem to be a suitable way to send these large amounts of data to the AppleWatch. I just can't rely on the circumstance, that the user is checking the app more often than he is traveling out of the SETTINGS_MAXIMUM_FETCH_DISTANCE radius and for locations with a high density of POIs this data is even more extensive.

Update 3: I've reimplemented the nearby Feature for POIs to fetch only in a given radius. If this solves a problem for you, check out this public Gist.


Solution

  • Since Watch OS3, you cannot access a CoreData base using App Group trick ( because the watch is supposed to work without a phone.

    So you have to use WatchConnectivity to fetch your data