My App requires access to iCloud. I used to be able to get the User's name components (family+given name) using:
let dummyZone = CKRecordZone (zoneName: UUID().uuidString)
let dummyShare = CKShare (recordZoneID: dummyZone.zoneID)
Persistence.logger.notice ("\(#function): Dummy Zone: \(dummyZone.zoneID.zoneName)")
// Save the dummyZone and then the dummyShare (for/in the dummyZone)
let _ = try await container.privateCloudDatabase.save (dummyZone)
let _ = try await container.privateCloudDatabase.save (dummyShare)
// Extract the dummyShare's owner's identity - which is 'us/me'
let userIdentity = dummyShare.owner.userIdentity
where the resulting userIdentity had a filled out nameComponents. Now, recently, it seems to be empty.
Did something change in the interfaces?
I've also tried, more directly:
let userRecordID = try await container.userRecordID()
let userParticipant = try await container.shareParticipant(forUserRecordID: userRecordID)
let userIdentity = userParticipant.userIdentity
and still nameComponents is empty.
Given that my App requires iCloud, is there a way to get (familyName,givenName)?
Post
Replies
Boosts
Views
Activity
I've defined 'public' and 'private' configurations in CoreData and configured the 'public' configuration to be handled in the CloudKit publicDB. The two configurations contain disjoint sets of NSManagedObject entity types. Some of the entities in the private configuration reference an entity in the public configuration using a UUID; thereby avoiding cross-configuration CoreData relationships. Avoiding this is a requirement of CoreData + CloudKit.
Now, since the UUID is meant to point to an object, in the awakeFromFetch() method I decided to fill in a transient property. No go. Even for a transient property there cannot be a cross-configuration relationship.
Why? Is there a way to avoid this constraint?
The cost of not having a transient property is to do a fetch of the object based on the UUID every time it is needed. Is this something that one should never worry about?
What interfaces do I use to propagate a CloudKit change in a shared zone to a notification/badge to all participants in the shared zone?
Assume I have a 'League' that is the root object in a shared zone and that N Players are members of the league. One of the players, the 'organizer', schedules a 'Game' that is open to any of the players. When the organizer creates the game (in the league's shared zone) and it is mirrored in CloudKit, how can the other players see it (as a timely notification)?
I already observe .NSPersistentStoreRemoteChange on NSPersistentStoreCoordinator and NSPersistentCloudKitContainer.eventChangedNotification on NSPersistentCloudKitContainer. Are these delivered in the background? Can/Should they generate a 'local/remote' notification for handling at the AppDelegate level? How?
Do I need to use a CKDatabaseSubscription looking for CD_Game records directly? (I'd rather not; because then I'd have a potential race between the remote iCloud database(s) and the local CoreData)
How can one avoid SwiftUI processing of views referencing a CoreData entity that has been deleted?
I've taken to 'wrapping' View body's with the following:
struct ManagedObjectDeleteWrapper<Object, Root> : View
where Object : NSManagedObject, Root : View {
var object: Object
var root: (Object) -> Root
var body: some View {
Group {
if object.isFault { EmptyView() }
else { root (object) }
}
}
}
like such:
struct GameView: View {
@EnvironmentObject private var game: Game
var body: some View {
ManagedObjectDeleteWrapper (object: game) { _ in
// Show `game` attributes and relationships
}
}
}
but this has become quite tedious. It is not as simple a wrapping the 'top-level' view (in my App, three or so TabViews), but one must wrap multiple subviews if they also reference game.
How can I approach this short of using game.isFault or ManagedObjectDeleteWrapper everywhere?
I'm cognizant that perhaps my 'view flow' is leading to this problem; is there a generic approach to view hierarchies that avoids the problem? Maybe 'only delete an object from a list view; only show the object content view a navigation link on that list view' ? My current design has three tab views showing game content and one tab showing 'competitions' (as set of games); a game is deleted from the competitions tab... and if that game is being displayed all the other tab views are invalid.
Does an 'archive build' require 'preview content'?
I've put swift source files into 'Preview Content' directories - one directory for the top-level App and one for an embedded framework. An 'archive build' fails, complaining about the code in a #Preview block.
#Preview {
// the archive build fails to find this `default` ??
let controller = PersistenceControllerTopLevelPreview.default
let user = controller.user
...
The framework has its 'Preview Context' specified in the Development Assets; same with the top-level App.
Suggestions on what is causing this? Where to look? Could it be related to having a CoreData 'abstract entity' that occurs in two configurations - one configuration for 'public' and one for 'private/shared'?
I've defined a FileDocument with an init(configuration: ReadConfiguration) .... I've got a URL via .fileImporter - now I'd like to actually read and process (the JSON content). How do I get the ReadConfiguration from the URL? (And what would I search for to avoid this probably trivial issue?)
Of course, I wrote the JSON file in the first place... so I could just read the contents as Data and decode it to JSON. But then what is the point of FileDocument other than to provide .fileExporter with a required argument?
What is the recommended architecture for a CoreData+CloudKit iOS App that has a large public database?
Assume the public database contains 10,000+ Locations from which a User would select a few as favorites. Totally impractical to mirror the locations such that they appear in the App's CoreData having been synced from the .public CloudKit database. Presumably one uses the CloudKit API to query the .public database and display some subset of locations for the User to select? (The selected locations can then be stored in the Users .private (or perhaps .shared) database.)
How does one configure CoreData + CloudKit for this scenario? Is there another approach?
How does one know when the CloudKit data in a CoreData+CloudKit (NSPersistentCloudKitContainer) has been fully synchronized. UseCase would be a user starts the App on a second device, user deletes then reinstalls the App, another User accepts a share and the share needs to sync.
Is the containerEventChanged Notification for 'import success' the definitive event?
CoreData: CloudKit: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _finishedRequest:withResult:](3403): Finished request: <NSCloudKitMirroringImportRequest: 0x600002345d10> 729A742A-7F3B-42F1-B04C-72705D41FFEF with result: <NSCloudKitMirroringResult: 0x600000c4edc0> storeIdentifier: 79FA5848-A135-41B1-A36A-09F2F914D23D success: 1 madeChanges: 0 error: (null)
As the sync could be time-consuming, is there a way to identify a single CloudKit record?
I have a CoreData model with two entities, 'User' and 'Player', that both use 'Person' as their 'Parent Entity'. While the App appears to work correctly in the simulator, including with CloudKit via NSPersistentCloudKitContainer, I get a crash in Xcode Previews:
libswiftCore.dylib [
AGScoringModel/Persistence.swift:183: Fatal error: #init(inMemory:): Failed to load persistent stores:Error Domain=NSCocoaErrorDomain Code=134110 "An error occurred during persistent store migration." UserInfo={sourceURL=file:///Users/ebg/Library/Developer/.../CoreDataStores/private/database.sqlite, reason=Cannot migrate store in-place: Cannot merge multiple root entity source tables into one destination entity root table, destinationURL=file:///Users/ebg/Library/Developer/.../CoreDataStores/private/database.sqlite, NSUnderlyingError=0x600000ce02a0 {Error Domain=NSCocoaErrorDomain Code=134110 "An error occurred during persistent store migration." UserInfo={message=Cannot merge multiple root entity source tables into one destination entity root table, destinationRootEntity=Person, NSUnderlyingException=Cannot merge multiple root entity source tables into one destination entity root table, sourceRootEntities=(
User,
Player
), reason=Cannot merge multiple root entity source tables into one destination entity root table}}}
]
Why is this? Something in my configuration for persistent container?
My App uses Core Data + CloudKit and requires use of CloudKit to store persistent data, that will be shared between Users. I’d like to establish the User’s identity - like so they can augment their Core Data ‘Player’ entity w/ App specific info. Certain interfaces have been deprecated (‘user discoverability’). I’ve taken to creating a ‘dummy shared zone’ and extracting the ‘owner.userIdentity':
public func establishUser () async -> User? {
let container = cloudKitContainer
// If we store the userUUID then the implication is that the `user` can
// never be deleted; that is fair enough.
let userUUIDKey = "userUUID"
var userIdentity = Optional<CKUserIdentity>.none
do {
//
// We'll store the UUID of the CoreData `User` in the CloudKit `User` record. If there
// is no UUID in CloudKit, then this will be the first time the User has ever started
// the App. We'll create a user and update the CloudKit `User` record
//
let userID = try await container.userRecordID ()
let userRecord = try await container.publicCloudDatabase.record (for: userID)
// If the `userRecord` does not have a `userUUIDKey` then we must create a new `User`.
if nil == userRecord[userUUIDKey] {
// See if the user has the required iCloud account.
let userStatus = try await container.accountStatus()
guard userStatus == .available
else {
print ("JKP: \(#function) accountStatus: \(userStatus)")
return nil
}
//
// Create a `dummyShare` (in a 'dummyZone') so we can access the share's owner
// That owner will have our `userIdentity`
//
do {
let dummyZone = CKRecordZone (zoneName: UUID().uuidString)
let dummyShare = CKShare (recordZoneID: dummyZone.zoneID)
print ("JKP: User: Establish Zone: \(dummyZone.zoneID.zoneName)")
// Save the dummyZone and then the dummyShare (for/in the dummyZone)
let _ = try await container.privateCloudDatabase.save (dummyZone)
let _ = try await container.privateCloudDatabase.save (dummyShare)
// Extract the dummyShare's owner's identity - which is 'us/me'
userIdentity = dummyShare.owner.userIdentity
// Cleanup by deleting the 'dummyShare' and then the 'dummyZone'
let _ = try await container.privateCloudDatabase.deleteRecord (withID: dummyShare.recordID)
let _ = try await container.privateCloudDatabase.deleteRecordZone (withID: dummyZone.zoneID)
}
catch {
print ("JKP: User Establish Error: \(error.localizedDescription)")
}
// Create `newUser` with the `userRecordId`. We'll use this to lookup the
// Core Data User when players appear in a League.
let newUser = User.create (context,
scope: Player.Scope.owner,
name: (userIdentity?.nameComponents ?? PersistenceController.nameDefault),
identification: userIdentity?.lookupInfo
.map { PlayerIdentification.create (lookupInfo: $0) } ?? PlayerIdentification())
… }
Is this how getting the userIdentity is meant to be done (w/o using the deprecated interfaces)? The deprecated interfaces, when warned in Xcode, reference a sample project; that project doesn’t actually use/get the userIdentity.
Also the deleteRecord and deleteRecordZone don’t appear to remove the dummy zone in CloudKit; why?
I've an existing app that is built using libresolv - it has calls to res_init(), res_query() and others. It uses these to lookup TXT records from a well-known, public location. When building under Xcode12 for an iOS14 target the following errors occur:
Undefined symbols for architecture x86_64:
"_res_9_init", referenced from:
_baseSeedQuery in BaselineCoreSafe.o
"_res_9_query", referenced from:
_baseSeedQuery in BaselineCoreSafe.o
"_res_9_ns_initparse", referenced from:
_baseSeedQuery in BaselineCoreSafe.o
"_res_9_ns_parserr", referenced from:
_baseSeedQuery in BaselineCoreSafe.o
"_res_9_ns_sprintrr", referenced from:
_baseSeedQuery in BaselineCoreSafe.o
The build is done with Swift PM and this portion of the code is built with:
linkerSettings: [
.linkedLibrary("resolv"),
.linkedLibrary("pthread"),
.linkedLibrary("bsd", .when(platforms: [.linux])),
]
For iOS14 is the res_* functionality in another library? Is there a different approach to use?
The Xcode 11 vs 12 contents aren't all that different:
Xcode.app//Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/libresolv.9.tbd
Xcode.app//Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/libresolv.tbd
$ find Xcode-beta.app/ -name 'libres*.*' -print | grep iPhoneOS
Xcode-beta.app//Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/libresolv.dylib
Xcode-beta.app//Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/libresolv.9.tbd
Xcode-beta.app//Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/libresolv.9.dylib
Xcode-beta.app//Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/libresolv.tbd
Is there a better build flag to use?
I'm unsure how to interpret a result from the 'Leak Checks' Instrument in 'Pure C' code. At the very beginning of a test run I see a leak reported. If I let the run proceed the test end occurs and the reported leak shows up with an 'Event Type' of 'free' - with a backtrace showing the exact function call sequence where I'd expect the memory to be freed.Why am I getting a leak reported in the first place?If this is a common 'false positive' then the only option I see is to check each reported leak to see if it actually got freed later. Let's just say 'tedious'.https://ibb.co/PTJp9rSImage shows a single leak, in the selected region. Looking at the history for that memory location shows a 'malloc' and a 'free' - why the leak?