Application with CloudKit and Coredata for the local storage. How to cope with the order of CKSubscritpion notifications - objective-c

Hello I have created a macOS cocoa application that uses Cloudkit, and Coredata to store a local copy of the storage. My application has an 'item' record which stores CKReferences to 'subitems'. The local storage is maintained through the use of CKSubscription notifications that trigger Core Data code for the creation/update of the local objects. The issue is that if I create a new item with subitems and later launch the app on another Mac, it can be that the other copy receives the sub-item creation notification earlier than the item's. Thus the code receives the sub-item but there is no corresponding Core Data object yet available at that point. Does anybody imagine a good pattern to solve this issue ?

Use the sync api. Apple recommends you use subscriptions only to get notified that something has changed not what. When you receive a notification fetch all changes that have been made since the last update.

Apple faces this same issue with the Notes app where they have folders containing notes, and notes containing attachments i.e. they cannot download a note that is in a folder that doesn't exist yet. How they solve it is using a private class CKRecordGraph and the method + (id)topologicallySortRecords:(id)arg1 withError:(id *)arg2; which sorts based on the CKReferences so they ensure they upload the folders before the notes that are inside (i.e. have a reference to the containing folder). Since the client will download the changes in order, it would be impossible for another client to download the note before the folder. You might also want to experiment with placeholder objects so you create the object but then fill in the info later. You could experiment by using the private class and then provide your own basic implementation perhaps customised for your own entities, e.g. always order the parent entities before the children. Here is the header for CKRecordGraph maybe one day it'll be public (I submitted feedback FB6117775 requesting it).
//
// Generated by class-dump 3.5-MH (64 bit).
//
// class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2015 by Steve Nygard.
//
#import <objc/NSObject.h>
#class NSArray, NSMutableArray;
#interface CKRecordGraph : NSObject
{
NSMutableArray *_nodes;
NSArray *_sortedRecords;
}
#property (strong, nonatomic) NSMutableArray *nodes; // #synthesize nodes=_nodes;
#property (strong, nonatomic) NSArray *sortedRecords; // #synthesize sortedRecords=_sortedRecords;
+ (id)topologicallySortRecords:(id)arg1 withError:(id *)arg2;
- (BOOL)addRecords:(id)arg1 error:(id *)arg2;
- (id)description;
- (id)init;
- (id)recordsByTopologicalSortWithError:(id *)arg1;
#end

Related

What is the proper identifier to use when adding a keychain item using KeyChainItemWrapper?

I don't really understand what the identifier is or how it is used.
Is it arbitrary? Is it my app name in reverse?
Below I use "test" but should it be: "com.mydomain.myApp.test"?
Or my app bundle identifier: "com.Soundpaper.soundpaper.fakeID123"?
Or what?
Thank you.
#property (nonatomic, strong) KeychainItemWrapper *myChain;
. . .
if (myChain == nil)
{
// first question: what identifier should I use?
myChain = [[KeychainItemWrapper alloc] initWithIdentifier:#"test" accessGroup:nil];
}
The KeychainItemWrapper is a wrapper class to hide all the CFRef conversion stuff from the "typical" developer.
Being so, all the "rules" of the Keychain services in the Security framework hold.
Therefore, whenever you access a keychain item, the framework will automatically "add" the bundle id of your app to that item, to ensure that you can read only your own items, see e.g. SecItemAdd discussion.
Hence, the identifier is completely arbitrary and may only raise conflicts within your own app, not with others.
Remark: if you specify an accessGroup, you can share keys between apps, see Sharing Access to Keychain Items Among a Collection of Apps

How to make Xcode recognise Objective-C commands in order to connect to Firebase?

Newbie here. In short: I'm using Arduino to count how many cars enter my garage and have the data sent to firebase. I was able to connect/read/write using Swift 4. Therefore, I decided to install a pod this one to improve the design of the app. The pod is written in objective-C (I've never used the language), so I thought it would be easier to have the firebase in the same class and language as the pod.
Since I have some code written in Swift 4, I decided to create a bridging header in order to "mix and match" both languages in the same project.
Therefore, the issues I am currently facing are:
1 - In the LMViewController.m file I've added:
#import Firebase;
#import FirebaseDatabase;
along with this code inside - (void)viewDidLoad method:
[FIRApp configure];
#property (strong, nonatomic) FIRDatabaseReference *ref;
self.ref = [[FIRDatabase database] reference];
This code automatically raises red flags due to the fact that it is not recognising it. I am using the code exactly as specified on google's firebase website. Firebase for ios.
2 - I have also tried to add the import FirebaseDatabase within swift appDelegate. No success in the .m file tho.
I would appreciate your help in order to understand how can I make xcode not give me errors when creating a firebase reference in order to read from it and assign the value to the pod code.

How to properly implement a QLPreviewPanel data source keeping in mind security-scoped URLs?

There is a problem with a poorly chosen, non-composable abstractions in Cocoa that arise when one tries to combine a Quick Look preview panel and a security-scoped URLs.
I have a concrete example:
Imagine we are trying to show a preview for some objects from the MediaLibrary (MediaLibrary.framework allows applications to browse the iPhoto, Aperture... and Photos libraries via convenient API).
The most simple and straightforward way to do so is to adapt the 'MLMediaObject' class (that represent a particular photo or video item) to implement the 'QLPreviewItem' protocol (which can be passed to the QLPreviewPanel):
MLMediaObject+PreviewItem.h
#import <MediaLibrary/MLMediaObject.h>
#import <Quartz/Quartz.h>
#interface MLMediaObject (PreviewItem) <QLPreviewItem>
#end
MLMediaObject+PreviewItem.m
#import "MLMediaObject+PreviewItem.h"
#implementation MLMediaObject (PreviewItem)
- (NSURL*) previewItemURL
{
return self.URL;
}
- (NSString*) previewItemTitle
{
return self.name;
}
#end
Simple. Now image the following QLPreviewPanel data source implementation:
AlbumViewController.m
- (NSInteger) numberOfPreviewItemsInPreviewPanel: (QLPreviewPanel*) panel
{
// 'currentAlbum' property contains the currently-represented MLMediaGroup object.
return self.currentAlbum.count;
}
- (id<QLPreviewItem>) previewPanel: (QLPreviewPanel*) panel previewItemAtIndex: (NSInteger) index
{
return self.currentAlbum[index];
}
So far so good. But if we look into the sparse and usually misleading Apple documentation, we may find out the following important details:
URL
The location of the media object. (read-only)
This property is provided as a security-scoped URL. In order to gain access to the file that this URL refers to, the caller must call startAccessingSecurityScopedResource before and stopAccessingSecurityScopedResource after using the URL to access the file.
So, it is obvious that access to the resource have to be bracketed with a startAccessingSecurityScopedResource/stopAccessingSecurityScopedResource calls pair.
The question is where should I put these calls given the current QLPreviewPanelDataSource protocol definition? It is up to QLPreviewPanel to access the resource, not my code, but unfortunately I hardly ever will believe that Apple updated QL to operate in a sandboxing-wise environment.
How do I handle the cases, when startAccessingSecurityScopedResource call returns NO, stating about failure to obtain an access?
Seems like when you try to startAccessingSecurityScopedResource on an URL that is already being accessed you get the failure flag on return. Like, everything is OK, but you get an error flag. Seems like these start/stop... calls have to be paired precisely, and even a balanced nesting is forbidden. So, how to differentiate between the two possibilities when you get a NO on return: a security-scoped URL that is already being accessed and a security-scoped URL that failed to 'resolve'?
It is an experimentally-proven fact, that your application can only access a finite amount of security-scoped URLs (you can take about ~1500 URLs before it will silently stop working). So, how should I properly relinquish access to the security-scoped URLs after I've passed them to the QLPreviewPanel? When it it a right time to do so? It seems to me like it is a private implementation detail of a QLPreviewPanel class and I can make no assumptions about its internal workings.
You can use:
- (void)beginPreviewPanelControl:(QLPreviewPanel *)panel {
[bookmarkURL startAccessingSecurityScopedResource];
//... Your code
}
and
- (void)endPreviewPanelControl:(QLPreviewPanel *)panel {
//... Your Code
[bookmarkURL stopAccessingSecurityScopedResource];
}

Objective-C: Do non-imported classes get compiled and linked into final binary?

In Objective-C, do all source files in the project get compiled and linked into the final binary even if they are not imported by any classes?
That depends on your target settings. If you go to the target's Build Phases, anything listed under Compile Sources will be compiled.
If the source file is part of the build then its classes end up in the binary and available at runtime. That's because the runtime is reflective and can be queried later to find them. The compiler cannot be certain that won't happen.
EDIT: a classic use case is an informal protocol. You may write code like:
- (void)initWithObject:(id)object
{
self = [super init];
if(self)
{
_title = [object title];
_value = [object value];
}
return self;
}
Then you might decide you want to add an implementation of that informal protocol to e.g. NSString:
#interface NSString (MYPropertyProtocol)
#end
#implementation NSString (MYPropertyProtocol)
- (NSString *)title
{
return #"Content";
}
- (NSString *)value
{
return self;
}
#end
Just because nobody imports the NSString category doesn't mean the methods aren't used.
Going beyond that, in my current app we have a protocol like (this is a heavily cut-down version):
#protocol MYTableViewCell
+ (NSArray *)acceptedObjectClasses;
- (void)setObject:(id)object;
#end
Subsequently UITableView subclasses can opt to implement MYTableViewCell. If so they are found automatically by traversing objc_getClassList (and a lot of caching) and used automatically by my table view data source if it wants to display any object type declared as supported by acceptedObjectClasses. So that's taken most of the hassle out of writing the table view data source — I've got just one, which can accept any combination of objects used anywhere in the app, yet it's very short and easy mentally to check for errors, and need never grow even as the app learns about more things that need to be displayed in tables.
Nobody ever explicitly imports those table view cells, because it'd just mean having to do the same work twice — declare the class in Objective-C, which the compiler will check for me, then go and redeclare it to the custom cut-down lookup system that I've implemented instead of just using Objective-C's, which I probably need to do manual verification on.
Click on a .m file in the Project Navigator on the left side of Xcode, then open the right-hand tile (with the right-hand "View" button in the toolbar) and look at the "Target Membership" section. There is a checkbox with your project name beside it. If the box is checked, that .m file is included in the build. If the checkbox is not checked then the .m is essentially ignored.
You can also check this by clicking on your project name at the top of the ProjectNavigator column, selecting the project under "TARGETS", selecting "Build Phases", and selecting "Compile Sources". All of the included .m files will be shown and you can add or delete files from the list using the + - buttons at the bottom of the list.

ACAccount.h sudden error

I was working on my project just fine and all of the sudden I get the following error in my project.
This is a problem in a in the Accounts Framework, which I didn't even get close to touch (or any framework). Specially since those files are locked.
Does anyone know why I am getting this and how to fix it?
Sorry I cant be more descriptive, but it suddenly happened and I never touched that.
I got following error after editing ACAccount.h.
Open headers folder, select ACAccount.h and press ⌘z(undo).
or compare your ACAccount.h file with below code
//
// ACAccount.h
// Accounts
//
// Copyright (c) 2011-2012 Apple Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <Accounts/AccountsDefines.h>
#class ACAccountType, ACAccountCredential;
// The ACAccount class represents an account stored on the system.
// Accounts are created not bound to any store. Once an account is saved it belongs
// to the store it was saved into.
ACCOUNTS_CLASS_AVAILABLE(10_8, 5_0)
#interface ACAccount : NSObject
// Creates a new account object with a specified account type.
- (id)initWithAccountType:(ACAccountType *)type;
// This identifier can be used to look up the account using [ACAccountStore accountWithIdentifier:].
#property (readonly, weak, NS_NONATOMIC_IOSONLY) NSString *identifier;
// Accounts are stored with a particular account type. All available accounts of a particular type
// can be looked up using [ACAccountStore accountsWithAccountType:]. When creating new accounts
// this property is required.
#property (strong, NS_NONATOMIC_IOSONLY) ACAccountType *accountType;
// A human readable description of the account.
// This property is only available to applications that have been granted access to the account by the user.
#property (copy, NS_NONATOMIC_IOSONLY) NSString *accountDescription;
// The username for the account. This property can be set and saved during account creation. The username is
// only available to applications that have been granted access to the account by the user.
#property (copy, NS_NONATOMIC_IOSONLY) NSString *username;
// The credential for the account. This property can be set and saved during account creation. It is
// inaccessible once the account has been saved.
#property (strong, NS_NONATOMIC_IOSONLY) ACAccountCredential *credential;
#end
Did you import ACAccount.h directly? If so, don't do that. Import only top-level headers—in this case, that'd be <Accounts/Accounts.h>.
In my case it wasnt that I modified AcAccount.h, which is difficult since Xcode alerts that you don't own that file, the problem was that I accidentally modified 'main.c', instead of giving an error on the line I accidentally modified it gave that misleading message.