How to test iCloud synchronisation between Cocoa app and iOS app - objective-c

My problem is simple:
I'm now developing Cocoa application which stores data in iCloud (key value store). In order to test iCloud synchronisation I've build a simple iOS app, and installed it in my device.
I did iCloud initialisation in cocoa app:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dataUpdatedFromCloud:) name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:[NSUbiquitousKeyValueStore defaultStore]];
[[NSUbiquitousKeyValueStore defaultStore] synchronize];
and selector:
+(void)dataUpdatedFromCloud:(NSNotification *)notification {
NSLog(#"dataUpdatedFromCloud: %#", [[NSUbiquitousKeyValueStore defaultStore] objectForKey:#"theKey"]);
}
in order to catch data when it comes from iCloud.
I do everything same in iOS app, with extra action when I store data in NSUbiquitousKeyValueStore:
// Script 1
[[NSUbiquitousKeyValueStore defaultStore] setDictionary:#{#"id":#1} forKey:#"theKey"];
My testing scenario is:
Run cocoa application in Xcode
Run iOS app in my device
In iOS app I have a button, which calls Script 1
Here I expect dataUpdatedFromCloud is called in 10-20 seconds on Cocoa app, but it doesn't!!!
Am I doing everything correct, or maybe I understood something incorrectly?

Well, probably, but reasons it might fail include:
The iCloud container ID is not exactly the same on the two apps. On the one hand this is obvious, but on the other it's the easiest thing to screw up, so check again.
The iCloud container ID is OK but the provisioning profile doesn't actually enable iCloud in both apps. You should check ubiquityIdentityToken in both apps to verify this.
The value has already been synced, for example, when the app wasn't running. The notification you're looking for only gets posted if the change happened while the app was running and if the new value is different than the old one. Since you're always setting #1 as the value, it's entirely possible that the value has already been received and that new attempts aren't changing anything. For testing, it might help to replace #1 with something like #([NSDate timeIntervalSinceReferenceDate]) just to make sure you get a different value every time.
iCloud is temporarily confused/down/broken/just plain slow. Although 10-20 seconds is reasonable, there's no performance guarantee. If it shows up after 30 seconds, or even 30 minutes, it's still working as advertised.

I've had some success with gummed up iCloud sync by resetting iCloud's documents and data as described here: http://support.apple.com/kb/HT5824
Sometimes iCloud gets confused about what it has already synced and where—testing, particularly when you're making repetitive identical updates, makes that more likely in my experience.

Related

How do I access logs from the past day with os_log from Apple Watch?

I'm trying to troubleshoot an issue on watchOS.
I'm not sure how to reproduce the problem I'm seeing, but I do encounter it occasionally during testing on a real device in the wild, so I'm trying to use os_log in order to diagnose the problem after the fact.
As a first step, to make sure I understand how how to write to the log and access it later, I've attempted to log an event any time the app first loads.
In the ExtensionDelegate.swift file for my app, I added this:
import os.log
extension OSLog {
private static var subsystem = Bundle.main.bundleIdentifier!
static let health = OSLog(subsystem: subsystem,
category: "health")
}
Then, I updated the applicationDidBecomeActive delegate function with this:
func applicationDidBecomeActive() {
os_log("App Started",
log: OSLog.health,
type: .error)
}
I know it's not really an error message, but from what I've read, messages that are not .error are not written to saved to the log for later. I want to make sure it gets written to the log like a real error would.
I installed the sysdiagnose profile, then installed the the most recent version of my app.
After testing the app for the day, I attempted to export the file. Following the instructions I've found elsewhere, I produced a sysdiagnose on Apple Watch by holding the Digital Crown and Side button for two seconds (and felt the haptic feedback when I released).
Then, I put the watch on the charger for a few minutes per the instructions here, which recommended 15 minutes.
I opened the Watch app on my paired iPhone, then went to General > Diagnostic Logs and downloaded the sysdiagnose from Apple Watch, and sent it to my computer with AirDrop.
This gave me a tarball file (for example, sysdiagnose_2021.03.05_17-01-57-0700_Watch-OS_Watch_18S801.tar.gz). Once I decompressed that, I had a folder of lots of files and subfolders.
After poking around in this folder, I figured my best bet was to look in the system_logs.logarchive file. I opened that in the macOS Console app, set the Showing dropdown to All Messages, and looked around the time I opened the app. I didn't see any log output from my app.
I also filtered for "App Started" (the log message from my app) and didn't find anything.
Then, I filtered by category for "health" and didn't find the event I had logged.
Is system_logs.logarchive the correct place to be looking for the log output from my app?
If not, where should I be looking? Or what am I doing wrong?
I really want a better understanding of how I can log messages on Apple Watch so I can view them later so I can make my Apple Watch apps more robust, but I'm at a dead end.
Am I looking in the wrong place? Or am I setting up the logging wrong? Or is it something else? I would appreciate any guidance about this!
According to the Apple Dev Forms, sysdiagnose allows you to view the logs on your apple watch.

Does UIManagedDocument handle conflcts

Does UIManagedDocument handle row level conflcts for me or do I need to handle those, and his answer was it does handle row level conflicts. Maybe I misunderstood him but I am not seeing this.
So I am going to ask a few key questions in hopes of getting some clarification here, again this is UIManagedDocument
If I have a table Author having first and last name fields what happens if:
A) I have a row with author: 'Jon Do' - it has sync'd to two devices. Then I edit the first name on one device from 'Jon' to 'John' and edit the last name on the other device from 'Do' to 'Doe'. How will iCloud and UIManagedDocument handle this? Will I get some sort of a notification that I need to respond to in order to handle the conflict? I tried the following code but I never get the notificiation:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(_ondocumentStateChangedNotification:) name:UIDocumentStateChangedNotification object:self.openedDoc];
B) What if I add a different author to the two devices so that there are now two rows to be merged will UIManagedDocument handle that for me or do I need to do something myself? If I need to do something myself what do I need to do? I tried signing up for the NSPersistentStoreDidImportUbiquitousContentChangesNotification but that doesn't ever seem to come through for me either. Which context do I sign up against? I tried this:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(_onPersistentStoreDidImportUbiquitousContentChanges:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotificationobject:self.openedCaddy.managedObjectContext.parentContext];
Also do I need to handle the NSManagedObjectContextDidSaveNotification myself for a UIManagedDocument or does UIManagedDocument handle that for me?
Any and all help is greatly appreciated I am really struggling here. I adopted iCloud and UIManagedDocument at the launch of iOS 5 and fumbled my way though the lack of documentaiton then but managed to ship a product but now I want iCloud to really work not simply do a winner takes all approach to document sync.
I figured it out. UIManagedDocument does handle row level conflicts.
The reason I never saw any name:NSPersistentStoreDidImportUbiquitousContentChangesNotificationobject notifications is because I was not setting the moc's persistent store options properly. To get core data to start syncing you have to set at a minimum the following options: NSPersistentStoreUbiquitousContentNameKey & NSPersistentStoreUbiquitousContentURLKey.
When I first started using iCloud 2 years ago I always assumed that the call to setUbiquitous:itemAtURL:destinationURL:error: did this for me and was equivalent but it's not. setUbiquitous:itemAtURL:destinationURL:error: simply tells iCloud to sync the files at that URL, it does nothing with core data - think dropbox. To get the core data portion of UIManagedDocument to sync those two options need to be set.
I have started a github repo that clarifies some of this and makes getting up and running with UIManagedDocument much easier. http://github.com/dtrotzjr/APManagedDocument
Hopefully this helps someone else. I feel rather dumb not realizing this sooner but when I first implemented iCloud sync for iOS 5 2 years ago the documentation was sparse and iCloud didn't work very well so I assumed my behavior was normal.

iOS - wait till a process ends

This is an IOS6 question.
I have an app that is calling a class (A) to check something. Then I want to call a class (B) to do something else
Is it possible to make sure process B doesn't start before process A finishes?
At the moment, I just call one after the other in the RootVC.
Each is showing a modal view, and I only get to see B ..
[self performA];
[self performB];
Thanks
There are several tools for managing the order of execution of parts of your application available to you. However since you are presenting view controllers you have a couple of constraints; you don't want to block the main thread (or else the app will become unresponsive) and you must perform UI actions on the main thread.
In this case the most common, and probably most appropriate, solution is to setup a callback to trigger action B when action A finishes.
The modal view controller presented as part of A might call a delegate when it has finished its task successfully. That delegate can then begin task B.
Alternately you might pass a block to A which A will execute when it finishes. That block can then perform task B.
I took the dare and failed.
The story: My app has been giving me hell updating from an iOS4 target to iOS6 (with a contingent sub of code for iOS5/3GS). It crashes unless i use #try etc... with a built in delay interval on the reattempt (which is stupid, 'cause I don't know how large a database the users have, nor how long it will take to load them). It's a painful way to get around my real problem: the view loads before the CoreData stack (logs) can be loaded completely and I don't see a way to make the initial view wait until its NSMutableArray (based on the CoreData database of my object) loads. Basically, I keep getting a false error about addObjectsSortedBy: the foremost attribute of my entity.
Threading does seem to be the answer, but I need to load an NSMutableArray and feed it into my initialViewController, which will be visible on every launch (excluding FirstTime initial), but my attempt (okay, 12 attempts) to use threading just made the crash occur earlier in the app launch.
The result: I bow down to those who have wrangled that bull of threads.
My solution has been to build in a notification in the AppDelegate.m, my initialViewController viewDidLoad is told to listen for it before anything else. If it gets the notification it skips ahead and completes the normal process unto [super viewDidLoad]; if not, it executes #try, #catch, #finally. In the #try I attempt to proceed as though the notification arrived (like it was a little late), then I handle (#catch) the error by displaying a "Please Wait" label to the user, then I tell the app to wait .xx and repeat the original addObjectsSortedBy: command as though everything were kösher to begin with.The sweet-spot for my app, with images and data in the logs appears to be .15 for the wait interval #50 test entries, with time to spare and no obvious lag on load. I could probably go down to .10 #50 entries.
BUT: I don't know how to scale this, without having the logs loaded enough to get an object.count! Without that, there is no way to scale my delay, which means it may (read:will) not work for large logs with many entries (200+)!
I have a work-around, but I'm going to keep trying to get a grip on threading, in order to have a solution. And to be honest, once I hit 20 entries, the notification never hits in time for the #try to occur.
If you can, use threads. I painted myself into a corner by failing to do so early on and am paying for it: my app has been in need of an overhaul, but I need this notch in my belt before it will be worthwhile. The earlier you can implement threaded loading the better for your long-term development. In the meantime, you may be able to use my work-around to continue testing other parts of your app.

paymentQueueRestoreCompletedTransactionsFinished: is returning an empty queue when it should have transactions in it

This is my first time using Store Kit and everything has gone great up until I attempt to restore purchases. I've seen several other posts on stack overflow about similar issues but I've not found a solution that works for me.
I have a button in my app that calls [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]. This in turn triggers the SKPaymentTransactionObserver method paymentQueueRestoreCompletedTransactionsFinished:. The problem is that paymentQueueRestoreCompletedTransactionsFinished: has zero transactions in the returned queue.
If I then attempt to make the purchase I am notified that I have already made the purchase. This tells me that the store knows that my test Apple ID has successfully made the purchase on a previous attempt. Why then does paymentQueueRestoreCompletedTransactionsFinished: return an empty transactions collection on it's queue?
There has been some mention of the sandbox behaving erratically but I need to see this working before I go live to the AppStore.
Any ideas? Am I missing something?
Thanks in advance.
Are you handling the transactions in -paymentQueue:updatedTransactions:? This callback gets your restored transactions before the paymentQueueRestoreCompletedTransactionsFinished: callback.
You should do your restore processing and handling inside -paymentQueue:updatedTransactions:.
I believe this is a bug with the sandbox App Store. My restores were not working with my test accounts (created in the iOS 5.0 era). -paymentQueue:updatedTransactions: was not getting called during a restore.
As suggested by process255's comment, I created a new test user in iTunes Connect. With the new test account, everything works fine!
It depends upon the product type the which products will be queued back in:
-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
as an alternate, read the receipt at:
[[NSBundle mainBundle] appStoreReceiptURL]
only problem with the receipts is that they are stored locally, but apple don't keeps it hanging .. ofcourse the purchases are saved on the apple server too. Refresh / load the receipt by:
SKReceiptRefreshRequest *recreq = [[SKReceiptRefreshRequest alloc] init];
[recreq start];
I had this same issue, but instead of creating a new user, I just went to iTunes & Aoo Stores in Settings and logged out of the sandbox test account and tried it again. Working!

Storing objects in iOS for later use

My app is pulling in JSON data from our web service. In all instances up til now, the information would be retained in memory or just refreshed on the fly with no need to retain anything locally other than the login token used in the API. With one of the new features we are adding, we will be taking in a group of locations, 26 max, with long, lat, radius, and name.
I also need to add 1-2 fields to this data to create a larger object. So my question is, what would be the best way to store this type of data in the iOS filesystem? Currently I have been using the NSUserDefaults, but that seems sort of limited or ill advised for larger amounts of data. Maybe not.
This data will need to be retrieved, changed or edited, and resaved. All of this while still retaining the ability to pull any of those 26 objects. Thank you in advance for reading and helping out.
For such a small amount of data (26 items) I suggest archiving.
Save to plist using NSKeyedArchiver/NSKeyedUnarchiver. Read your data from the delegate's didFinishLaunchingWithOptions, and listen for UIApplicationWillResignActiveNotification to save it.
A NSUserDefaults is a plist with features designed to store user preferences. It's often used instead a regular plist to save a couple of lines of code, which I think it's a bad idea because you get an additional complexity unrelated to your task.
If you want the login to be protected against someone stealing the device and performing forensics use the Keychain. You may want to use a wrapper and read some articles, comment if you are interested.
If you look for more features see Best way to store data on iphone but doesn't seem to be the case now.
Some code to get you started... Register to call save on app resign:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(saveMyData)
name:UIApplicationWillResignActiveNotification
object:nil];
On each object of the graph/dictionary/whatever you want to archive implement NSCoding:
- (void)encodeWithCoder:(NSCoder*)coder {
[coder encodeObject:myIvar forKey:kmyIvar];
}
- (id)initWithCoder:(NSCoder*)coder {
if((self = [super initWithCoder:coder])) {
self.myIvar = [[coder decodeObjectForKey:kmyIvar] retain];
}
return self;
}
Check out this guide on core data. It's the best way to store data locally on your device. It's a native cocoa API and it is bindings compatible. Plus, you can choose whether to store data as XML, SQLite, and Binary.
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/cdProgrammingGuide.html
For anything remotely large, I would use this.
I had this same question and just figured out a much better solution.
What you could do is just store the JSON string as your NSUserDefault. Then, when you reload the app use the same method (or framework utility) you used to map the JSON string to your objects the first time. This way you can still take advantage of the ease of NSUserDefaults.
If you're using RestKit to manage your web services it gets even easier. The answer on this post shows how to use RestKit's JSON parser to map from JSON to your object.
Deserializing local NSString of JSON into objects via RestKit (no network download)