How to get a fresh copy of CKRecord from NSPersistentCloudKitContainer? - swift

According to Apple docs here
https://developer.apple.com/documentation/coredata/mirroring_a_core_data_store_with_cloudkit/reading_cloudkit_records_for_core_data
You can get a CKRecord from a NSManagedObject using NSPersistentCloudKitContainer.
Access CloudKit Objects
You can access a managed object’s CKRecord directly through its associated context using record(for:) for a single record, or records(for:) for multiple records. To retrieve the record ID only, use recordID(for:), or recordIDs(for:).
Alternatively, use the class functions record(for:), records(for:), recordID(for:), and recordIDs(for:) on NSPersistentCloudKitContainer.
But it doesn't say how to get a fresh copy? If you are working with CloudKit Sharing, you are modifying CK without going through CoreData. In which case you have to wait for NSPersistentCloudKitContainer to next fetch for updates.
But if you want to present the latest Sharing Status, you need to query Cloudkit. How to do this?

Using the fetch with RecordID (below) will let you get the latest record:
func fetch(withRecordID recordID: CKRecord.ID,
completionHandler: #escaping (CKRecord?, Error?) -> Void)
If you want to fetch it and then update a property and save, you'd do something like:
Get the record from cloudkit
CKContainer.default().publicCloudDatabase.fetch(withRecordID: user.id!) { updatedRecord, error in
Then set the new array for the key and key type I'm saving (where array for user is the thing I've updated and 'keyType' is the CK record field name)
updatedRecord!.setObject(arrayForUser as __CKRecordObjCValue?, forKey: keyType)
Then make the save call, passing the updated record:
CKContainer.default().publicCloudDatabase.save(updatedRecord!) { savedRecord, error in

Related

Will users get access to sensitive data with this database retrieve method?

I'm making a dating app. Currently, I have a "feed" that's getting data off of the node "users" (database image below):
The node has the users: email, name, and birthday..etc. I only want users to have access to other users name. If I leave my data structure as is, will users be able to somehow get the user's birthday? How? If I don't display it at all on my app?
Fetch value code
override func viewDidLoad() {
super.viewDidLoad()
FIRApp.configure()
refArtists = FIRDatabase.database().reference().child("users");
//observing the data changes
refArtists.observe(FIRDataEventType.value, with: { (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
self.artistList.removeAll()
//iterating through all the values
for artists in snapshot.children.allObjects as! [FIRDataSnapshot] {
//getting values
let artistObject = artists.value as? [String: AnyObject]
let artistName = artistObject?["name"]
let artistId = artistObject?["id"]
let artistGenre = artistObject?["interest"]
//creating artist object with model and fetched values
let user = ArtistModel(id: artistId as! String?, name: artistName as! String?, genre: artistGenre as! String?)
//appending it to list
self.artistList.append(user)
}
//reloading the tableview
self.tableViewArtists.reloadData()
}
})
}
That's not a production DB structure, Be very careful with firebase database and public access, Firebase usually sends out emails for un-secure rules.
In the real world, If your app manages to go viral and catch eyes, Jailbroken Devices will come into play.
A jailbroken device with an equipped modern file manager can get your database credentials, This is safe and dangerous.
Safe When:
You want a easy setup for you and your project and firebase
You have a well written rule that will deny incoming requests from unwanted access
You have a good database structure (See Firebase: Structure Your Database
Not Safe When:
You don't have secure Firebase Access Rules
You have a bad database structure and grouping, an attackers hope.
You read everything at once.
Recommended Steps:
Use the real world Firebase security rules, they're fast, secure, and does not require an app update, so you can make changes at any time of the day.
Firebase Rules:
Understand Firebase Realtime Database Rules

How to fetch CKRecords without parent record?

I need to check which CKRecords in CloudKit container don't have parent record yet. How I can fetch only records that do not have parent yet? I've tried NSPredicate, but getting an error "Unknown field 'parent'".
You can’t achieve this with a simple query, but you can filter fetched records where Share property is nil.

Filter NSArraycontroller Core Data before searching

I have an NSArrayController managing contacts stored in Core Data and presented in a NSTable. The table provides a search field to the user to look for contacts by name. Everything is implemented in Swift using Cocoa bindings and works well.
Now I want to add an additional filter before the user types his search string. The filter should limit the contacts via an additional attribute like a creation date. How can this be done? Via modifying the predicate or do I need to subclass NSArrayController?
Any help would be appreciated.
Meanwhile I tried the approach mentioned by Willeke. Setting a default fetch predicate for the array controller initially with the Interface Builder and executing the following code every time the date date value changes, worked fine.
let fetchRequest = ContactArrayController.defaultFetchRequest()
fetchRequest.predicate = NSPredicate(format: "events.#max.eventDate < %#", lastDate as NSDate)
try? ContactArrayController.fetch(with: fetchRequest, merge: false)
where event is a related entity to the entity in the array controller.
However this appears to be an overhead to me, as the data needs to be fetched every time into the array controller instead of just applying a filter
Maybe there are better solutions

CloudKit - How to retrieve the ckRecordID of the record just saved using CKModifyRecordsOperation

I'm using CoreData for keeping a local cache of records in CloudKit. When saving a new record, I do the following:
Insert record to CoreData. I flag this record as not updated in CloudKit. Just in case my CKModifyRecordsOperation fails, I can still update it at a later time to CloudKit using this flag.
Insert record to CloudKit using CKModifyRecordsOperation.
Try fetching the ckRecordID of the record inserted in step #2. (That's where my logic fails as I'm not sure how I can achieve this). I do not have any other keys (reference) and wish to use only CKRecordID as a reference between CoreData and CloudKit.
Update the ckRecordID (fetched in step #3) to CoreData.
What would be the best logic to solve the above? Thank you for your time and responses.
I solved this by creating a CKRecordID locally and updating it in CloudKit. Below is the quote from apple documentation:
To assign a custom record ID to a new record, you must create the
CKRecordID object first. You need to know the intended name and zone
information for that record, which might also require creating a
CKRecordZone.ID object. After creating the record ID object,
initialize your new record using its init(__recordType:recordID:)
method.
Here's my code:
let zone = CKRecordZone(zoneName: Schema.Zone.group)
let uuid = NSUUID()
let recordType = Schema.RecordType.group
let recordName = uuid.uuidString
let recordID = CKRecordID(recordName: recordName, zoneID: zone.zoneID)
let newRecord = CKRecord(recordType: recordType, recordID: recordID)

Core Data primary key ID for a row in the database

Suppose I have a list of books stored in Core Data. I want to search for a book by it's primary key ID.
I know the sqlite file created by Core Data has an ID column in each table, but this doesn't seem to be exposed to me in anyway.
Does anyone have any recommendations?
-[NSManagedObject objectID] is the unique ID for an object instance in Core Data. It can be serialized via -[NSManagedObjectID URIRepresentation]. You can retrieve the objectID from a persistent store coordinator with -[NSPersistentStoreCoordinator managedObjectIDForURIRepresentation:] and then get the object from a managed object context with -[NSManagedObjectContext objectWithID:].
BUT
You should keep in mind that Core Data is not an ORM. It is an object graph management framework. That is uses SQLite (and unique row IDs) as a backend is purely an implementation detail. The sooner you can get yourself out of the SQL/RDBMS mindset, the faster you will be happy with Core Data. Instead of trying to find an object from a stored ID, consider why you need that object and what object needs it. If an instance of class Foo needs to be able to get to an instance of class Bar, why not just create an association from the Foo to the Bar and set the appropriate Bar instance as the target of the association on the appropriate Foo instance. Let Core Data keep track of object IDs.
As Barry Wark said, remember always that Core Data is not an orm. Pure SQL details are not exposed to the user and every row is just an object. By the way, sometime you should need to access the "primary key", for example when you need to sync the coredata db with external sql databases (in my case I needed it in a callback function to change the state of an object after INSERT it with success in the remote db). In this case, you can use:
objectId = [[[myCoredataObject objectID] URIRepresentation] absoluteString]
that will return a string like: x-coredata://76BA122F-0BF5-4D9D-AE3F-BD321271B004/Object/p521 that is the unique id used by Core Data to identify that object.
If you want to get back an object with that unique id:
NSManagedObject *managedObject = [managedObjectContext objectWithID:[persistentStoreCoordinator managedObjectIDForURIRepresentation:[NSURL URLWithString:objectId]]];
NB: Remember that if the receiver has not yet been saved in the CoreData Context, the object ID is a temporary value that will change when the object is saved.
This is the way you can get the object id as String using Swift from a NSManagedObject:
entity.objectID.uriRepresentation().absoluteString
in Swift this will be done by getting ID of the row as URI then get last path of URI
entity.objectID.uriRepresentation().lastPathComponent
the output of last path will look like this
p12
this output is string so you can remove the p using:
trimmingCharacters()
// Like this
let id = entity.objectID.uriRepresentation().lastPathComponent.trimmingCharacters(in: ["p"])