Uploading multiple images from Share Extension - ios

I currently have a share extension set up that will upload an image selected from the Photo app to a server. This works fine using the code below.
int fileNum=10;
NSItemProvider *attachment = inputItem.attachments[0];
if ([attachment hasItemConformingToTypeIdentifier:(NSString*)kUTTypeImage])
{
[attachment loadItemForTypeIdentifier:(NSString*)kUTTypeImage options:nil completionHandler:^(id item,NSError *error)
{
if (item)
{
NSLog (#"image %#",item);
//upload image here
NSData *data=[NSData dataWithContentsOfURL:item];
activityRecord.activityType=#"Images";
AppRecord *appRecord=[[AppRecord alloc] init];
appRecord.fileName=[NSString stringWithFormat:#"activity_%#%i(%i).jpg",activityRecord.supplierID,activityRecord.activityID,fileNum];
appRecord.fileBytes=data;
[fileRecords addObject:appRecord];
activityRecord.activityFiles=fileRecords;
[[Settings getInstance] uploadActivityRecord:activityRecord withDelegate:self];
[self.extensionContext completeRequestReturningItems:#[] completionHandler:nil];
}
}];
}
I had a previous problem where the loadItemForTypeIdentifier method wasn't being called, and it was resolved by calling completeRequestReturningItems within the completion block.
The problem I have now is that if I want to upload multiple files then I need to call loadItemForTypeIdentifier within a for loop (for each image) but how can I do that if the completeRequestReturningItems method will be called after the first image/item?
Many Thanks
Paul

I ran into the same problem recently and was able to resolve it by adding a counter and counting down as the images successfully completed their block. Within the loadItemForTypeIdentifier completion block I then check to see if all items have been called before calling the completeRequestReturningItems within a dispatch_once block (just for safety's sake).
__block NSInteger imageCount;
static dispatch_once_t oncePredicate;
NSItemProvider *attachment = inputItem.attachments[0];
if ([attachment hasItemConformingToTypeIdentifier:(NSString*)kUTTypeImage])
{
[attachment loadItemForTypeIdentifier:(NSString*)kUTTypeImage options:nil completionHandler:^(NSData *item ,NSError *error)
{
if (item)
{
// do whatever you need to
imageCount --;
if(imageCount == 0){
dispatch_once(&oncePredicate, ^{
[self.extensionContext completeRequestReturningItems:#[] completionHandler:nil];
});
}
}
}];
}
I can't say I feel like this is an overly elegant solution however, so if someone knows of a more appropriate way of handling this common use case I'd love to hear about it.

Related

Why doesn't NSOperationQueue execute a block submitted with addOperationWithBlock?

Here is the thing, I got some code, that does not execute (the compiler run the code but does not do anything)... Here is the code... Using NSURLSessionDelegate and [NSOperationQueue mainQueue] addOperationWithBlock
#interface tablaPosViewController : UIViewController <NSURLSessionDelegate>
#end
#implementation tablaPosViewController ()
- (void)viewDidLoad
{
//some code to set the view, labels and stuff
[self downloadTheHTMLdata] // this code download some data from WWW
}
- (void)downloadTheHMTLdata
{
//some code to set the session using an object
[object.downloadTask resume]; //Begins the download
}
- (void)toFixTablaPosiciones
{
//some code to do with the downloaded data from WWW (and HTML sheet)
//here, parse the HTML Sheet, and put some data into an Arrays, and another public vars
//call another method
[self PutTheDataInLabel];
}
- (void)PutTheDataInLabel
{
//some code to put all the data in every label
//take the public vars that was set in the previous method, and do some code with it
//and put the info into the labels
//call another method
[self MoreStuff];
}
- (void)MoreStuff
{
//some code..
}
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
//When the download finish this method fire up!
//this is to copy file from tmp folder to Docs folder
if ([fileManager fileExistsAtPath:[destinationURL path]])
{
[fileManager removeItemAtURL:destinationURL error:nil];
}
BOOL success = [fileManager copyItemAtURL:location //TMP download folder
toURL:destinationURL //Documents folder
error:&error];
//HERES COMES THE TROUBLE!!!
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self toFixTablaPosiciones]; //Call this method, that has other method calls!
}];
}
#end
UPDATE
This another code put methods in the queue...
-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([downloadTasks count] == 0) {
if (appDelegate.backgroundTransferCompletionHandler != nil) {
void(^completionHandler)() = appDelegate.backgroundTransferCompletionHandler;
appDelegate.backgroundTransferCompletionHandler = nil;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
completionHandler();
}];
}
}
}];
}
The issue is when the download file ends, calls -(void)URLSession:(NSURLSession *)session downloadTask:... method and I wait to [[NSOperationQueue mainQueue] addOperationWithBlock:^{... runs everything... but it does not execute anythig of [self toFixTablaPosiciones]!!.
I ran the code step by step, and I see how the compiler runs all the code, method over method... but the view never updates, runs but not executes, simply does do anything, and I have an Activity Indicator, and want to stop it, but the lablels stills with de dummie data and the Activity Indicator never stops and never disappears.
In a previous view, I download another file using a similiar class, and downloads very quickly. Going to this view/class try to perform the download and this is the thing...
Hoping any body can help me and send me any advice. Thanks!
One technique I use, as do most experienced developers, is to use asserts and also NSLog (which you can comment on and off) to verify that assumptions you are making about your code are in fact true. Try cutting and pasting the below code and see what happens - it should help. In the future, don't bang your head on a wall - start adding asserts and logs. At some point you will find that some assumption is untrue.
-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
NSLog(#"Received URLSessionDidFinishEventsForBackgroundURLSession: application state is %d", [UIApplication sharedApplication] applicationState];
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
NSLog(#"Download task count %d", [downloadTasks count]);
#warning "This looks like incorrect logic, but I don't know this background code. Wouldn't you see "1", or more? Won't you get an array of the tasks you submitted???
if ([downloadTasks count] == 0) {
assert(appDelegate.backgroundTransferCompletionHandler); // if this is nil, your app will be stuck in some odd state forever, so treat this like a fatal error
void(^completionHandler)() = appDelegate.backgroundTransferCompletionHandler;
appDelegate.backgroundTransferCompletionHandler = nil;
assert([NSOperationQueue mainQueue]); // why not?
assert(![NSOperationQueue mainQueue].isSuspended); // I looked at the class description, the queue **could** be suspended
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(#"Completion Operation called!");
assert(completionHandler); // perhaps an ARC bug, unlikely, but then you cannot get this code to work, right?
completionHandler();
}];
}
}];
}

Synchronization issue with ALAssetsLibrary assetForURL method

I am having some synchronization issue with loading asset from ALAssetsLibrary.
Actually, what I am trying is to load some pictures from camera roll whose urls are given by some database query. Now after obtaining urls from database I use those urls to load the pictures using assetForURL method of ALAssetsLibrary and after the picture is loaded I display the picture to some view. So I call the method inside a loop that is executed every time the query result-set returns a record. And everything works fine till now. Below is a sample code to demonstrate the process:
ALAssetsLibrary* library = [ALAssetsLibrary new];
//dispatch_group_t queueGroup = dispatch_group_create();
while ([rs next]) {
//some data load up
//load thumbnails of available images
[library assetForURL:url resultBlock:^(ALAsset *asset) {
UIImage* img = [[UIImage imageWithCGImage:asset.aspectRatioThumbnail] retain];
//dispatch_group_async(queueGroup, dispatch_get_main_queue(), ^{
//create views and add to container view
CGFloat left = (8.0f + dimension) * i + 8.0f;
CGRect rect = CGRectMake(left, 8.0f, dimension, dimension);
TileView* tileView = [[NSBundle mainBundle] loadNibNamed:#"TileView" owner:nil options:nil][0];
[tileView setFrame:rect];
tileView.tag = i;
tileView.active = NO;
[self.thumbnailContainer addSubview:tileView];
//display image in tileView, etc.
//.............
if (img) {
[img release];
}
NSLog(#"block %d: %d",i,[self.thumbnailContainer.subviews count]);
//});
} failureBlock:^(NSError *error) {
NSLog(#"failed to load image");
}];
i++;
}
NSLog(#"outside block %d",[self.thumbnailContainer.subviews count]);
[library release];
In my code self.thumbnailContainer is a UIScrollView and inside that I add my custom views to display the thumbnail images and it works as expected.
The real dilemma comes when I try to select the very last view added to self.thumbnailContainer. I cant find any way to determine when all the asynchronous blocks of assetForURL methods completed so that self.thumbnailContainer actually contains some subviews. So if I log count of subviews of self.thumbnailContainer just after the loop completes it shows 0. And after that I find all the block codes get executed increasing count of subviews. It is very expected behavior but contradicts my requirements. I have tried dispatch_group_ and dispatch_wait methods from GCD but without any success.
Can anyone please suggest a workaround or an alternative coding pattern to overcome the situation. Any help would be highly appreciated. Thanks.
You might utilize a dispatch group, as you likely had in mind:
- (void) loadViewsWithCompletion:(completion_t)completionHandler {
ALAssetsLibrary* library = [ALAssetsLibrary new];
dispatch_group_t group = dispatch_group_create();
while ([rs next]) {
dispatch_group_enter(group);
[library assetForURL:url resultBlock:^(ALAsset *asset) {
UIImage* img = [[UIImage imageWithCGImage:asset.aspectRatioThumbnail] retain];
dispatch_async(dispatch_get_main_queue(), ^{
//create views and add to container view
...
dispatch_group_leave(group);
});
} failureBlock:^(NSError *error) {
NSLog(#"failed to load image");
dispatch_group_leave(group);
}];
i++;
}
[library release];
if (completionHandler) {
dispatch_group_notify(group, ^{
completionHandler(someResult);
});
}
... release dispatch group if not ARC
}
The code might have a potential issue though:
Since you asynchronously load images, they may be loaded all in parallel. This might consume a lot of system resources. If this is the case, which depends on the implementation of the asset loader method assetForURL:resultBlock:failureBlock:, you need to serialize your loop.
Note: Method assetForURL:resultBlock:failureBlock: may already ensure that access to the asset library is serialized. If the execution context of the completion block is also a serial queue, [UIImage imageWithCGImage:asset.aspectRatioThumbnail] will be executed in serial. In this case, your loop just enqueues a number of tasks - but processes only one image at a time and you are safe.
Otherwise, if method assetForURL:resultBlock:failureBlock: runs in parallel, and/or the block executes an a concurrent queue - images might be loaded and processed in parallel. This can be a bad thing if those images are large.
Two ways you can fix this issue. They are
1.NSLock. Specifically NSConditionLock. Basically you create a lock with the condition “pending tasks”. Then in the completion and error blocks of assetForURL, you unlock it with the “all complete” condition. With this in place, after you call assetForURL, simply call lockWhenCondition using your “all complete” identifier, and you’re done (it will wait until the blocks set that condition)!
Check this for more detials
2.Manually track the count in resultBlock & failureBlock
Note:
I have mentioned few important things regarding the performance of ALAsset libraries in my answer.

sendAsynchronousRequest makes UI freezes

downloadImages is a button and whenever I press on it, a spinner should start rolling, an async request should ping Google (to make sure there is a connection) and after a response is received, I start to synchronically downloading images.
Somehow the spinner won't go and it seems as if the request is sync and not async.
- (IBAction)downloadImages:(id)sender {
NSString *ping=#"http://www.google.com/";
GlobalVars *globals = [GlobalVars sharedInstance];
[self startSpinner:#"Please Wait."];
NSURL *url = [[NSURL alloc] initWithString:ping];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:5.0];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data) {
for(int i=globals.farmerList.count-1; i>=0;i--)
{
//Definitions
NSString * documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
//Get Image From URL
NSString *urlString = [NSString stringWithFormat:#"https://myurl.com/%#",[[globals.farmerList objectAtIndex:i] objectForKey:#"Image"]];
UIImage * imageFromURL = [self getImageFromURL:urlString];
//Save Image to Directory
[self saveImage:imageFromURL withFileName:[[globals.farmerList objectAtIndex:i] objectForKey:#"Image"] ofType:#"jpg" inDirectory:documentsDirectoryPath];
}
[self stopSpinner];
}
}];
}
The spinner code:
//show loading activity.
- (void)startSpinner:(NSString *)message {
// Purchasing Spinner.
if (!connectingAlerts) {
connectingAlerts = [[UIAlertView alloc] initWithTitle:NSLocalizedString(message,#"")
message:nil
delegate:self
cancelButtonTitle:nil
otherButtonTitles:nil];
connectingAlerts.tag = (NSUInteger)300;
[connectingAlerts show];
UIActivityIndicatorView *connectingIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
connectingIndicator.frame = CGRectMake(139.0f-18.0f,50.0f,37.0f,37.0f);
[connectingAlerts addSubview:connectingIndicator];
[connectingIndicator startAnimating];
}
}
//hide loading activity.
- (void)stopSpinner {
if (connectingAlerts) {
[connectingAlerts dismissWithClickedButtonIndex:0 animated:YES];
connectingAlerts = nil;
}
// [self performSelector:#selector(showBadNews:) withObject:error afterDelay:0.1];
}
As asked: the getImageFromURL code
-(UIImage *) getImageFromURL:(NSString *)fileURL {
UIImage * result;
NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:fileURL]];
result = [UIImage imageWithData:data];
return result;
}
-(void) saveImage:(UIImage *)image withFileName:(NSString *)imageName ofType:(NSString *)extension inDirectory:(NSString *)directoryPath {
if ([[extension lowercaseString] isEqualToString:#"png"]) {
[UIImagePNGRepresentation(image) writeToFile:[directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.%#", imageName, #"png"]] options:NSAtomicWrite error:nil];
} else if ([[extension lowercaseString] isEqualToString:#"jpg"] || [[extension lowercaseString] isEqualToString:#"jpeg"]) {
[UIImageJPEGRepresentation(image, 1.0) writeToFile:[directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.%#", imageName, #"jpg"]] options:NSAtomicWrite error:nil];
} else {
NSLog(#"Image Save Failed\nExtension: (%#) is not recognized, use (PNG/JPG)", extension);
}
}
That's because you're creating an asynchronous operation and then telling it to execute on the main thread by using [NSOperationQueue mainQueue];.
Instead, create a new instance of NSOpeartionQueue and pass that as the parameter.
NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
This is an asynchronous problem. Asynchronism is infectious. That means, if any small part of the problem is asynchronous, the whole problem becomes asynchronous.
That is, your button action would invoke an asynchronous method like this (and itself becomes "asynchronous" as well):
- (IBAction)downloadImages:(id)sender
{
self.downloadImagesButton.enabled = NO;
[self asyncLoadAndSaveImagesWithURLs:self.urls completion:^(id result, NSError* error){
if (error != nil) {
NSLog(#"Error: %#", error);
}
dispatch_async(dispatch_get_main_queue(), ^{
self.downloadImagesButton.enabled = YES;
};
}];
}
So, your asynchronous problem can be described as this:
Given a list of URLs, asynchronously load each URL and asynchronously save them to disk. When all URLs are loaded and saved, asynchronously notify the call-site by calling a completion handler passing it an array of results (for each download and save operation).
This is your asynchronous method:
typedef void (^completion_t)(id result, NSError* error);
- (void) asyncLoadAndSaveImagesWithURLs:(NSArray*)urls
completion:(completion_t) completionHandler;
Asynchronous problems can be solved properly only by finding a suitable asynchronous pattern. This involves to asynchronize every part of the problem.
Lets start with your getImageFromURL method. Loading a remote resource is inherently asynchronous, so your wrapper method ultimately will be asynchronous as well:
typedef void (^completion_t)(id result, NSError* error);
- (void) loadImageWithURL:(NSURL*)url completion:(completion_t)completionHandler;
I leave it undefined how that method will be eventually implemented. You may use NSURLConnection's asynchronous convenient class method, a third party helper tool or your own HTTPRequestOperation class. It doesn't matter but it MUST be asynchronous for achieving a sane approach.
Purposefully, you can and should make your saveImage method asynchronous as well. The reason for making this asynchronous is, that this method possibly will get invoked concurrently, and we should *serialize* disk bound (I/O bound) tasks. This improves utilization of system resources and also makes your approach a friendly system citizen.
Here is the asynchronized version:
typedef void (^completion_t)(id result, NSError* error);
-(void) saveImage:(UIImage *)image fileName:(NSString *)fileName ofType:(NSString *)extension
inDirectory:(NSString *)directoryPath
completion:(completion_t)completionHandler;
In order to serialize disk access, we can use a dedicated queue disk_queue where we assume it has been properly initialized as a serial queue by self:
-(void) saveImage:(UIImage *)image fileName:(NSString *)fileName ofType:(NSString *)extension
inDirectory:(NSString *)directoryPath
completion:(completion_t)completionHandler
{
dispatch_async(self.disk_queue, ^{
// save the image
...
if (completionHandler) {
completionHandler(result, nil);
}
});
}
Now, we can define an asynchronous wrapper which loads and saves the image:
typedef void (^completion_t)(id result, NSError* error);
- (void) loadAndSaveImageWithURL:(NSURL*)url completion:(completion_t)completionHandler
{
[self loadImageWithURL:url completion:^(id image, NSError*error) {
if (image) {
[self saveImage:image fileName:fileName ofType:type inDirectory:directory completion:^(id result, NSError* error){
if (result) {
if (completionHandler) {
completionHandler(result, nil);
}
}
else {
DebugLog(#"Error: %#", error);
if (completionHandler) {
completionHandler(nil, error);
}
}
}];
}
else {
if (completionHandler) {
completionHandler(nil, error);
}
}
}];
}
This loadAndSaveImageWithURL method actually performs a "continuation" of two asynchronous tasks:
First, asynchronously load the image.
THEN, if that was successful, asynchronously save the image.
It's important to notice that these two asynchronous tasks are sequentially processed.
Up until here, this all should be quite comprehensive and be straight forward. The tricky part follows now where we try to invoke a number of asynchronous tasks in an asynchronous manner.
Asynchronous Loop
Suppose, we have a list of URLs. Each URL shall be loaded asynchronously, and when all URLs are loaded we want the call-site to be notified.
The traditional for loop is not that appropriate for accomplishing this. But imagine we would have a Category for a NSArray with a method like this:
Category for NSArray
- (void) forEachApplyTask:(task_t)transform completion:(completion_t)completionHandler;
This basically reads: for each object in the array, apply the asynchronous task transform and when all objects have been "transformed" return a list of the transformed objects.
Note: this method is asynchronous!
With the appropriate "transform" function, we can "translate" this to your specific problem:
For each URL in the array, apply the asynchronous task loadAndSaveImageWithURL and when all URLS have been loaded and saved return a list of the results.
The actual implementation of the forEachApplyTask:completion: may appear a bit tricky and for brevity I don't want to post the complete source here. A viable approach requires about 40 lines of code.
I'll provide an example implementation later (on Gist), but lets explain how this method can be used:
The task_t is a "block" which takes one input parameter (the URL) and returns a result.
Since everything must be treated asynchronously, this block is asynchronous as well, and the eventual result will be provided via a completion block:
typedef void (^completion_t)(id result, NSError* error);
typedef void (^task_t)(id input, completion_t completionHandler);
The completion handler may be defined as follows:
If the tasks succeeds, parameter error equals nil. Otherwise, parameter error is an NSError object. That is, a valid result may also be nil.
We can quite easily wrap our method loadAndSaveImageWithURL:completion: and create a block:
task_t task = ^(id input, completion_t completionHandler) {
[self loadAndSaveImageWithURL:input completion:completionHandler];
};
Given an array of URLs:
self.urls = ...;
your button action can be implemented as follows:
- (IBAction)downloadImages:(id)sender
{
self.downloadImagesButton.enabled = NO;
task_t task = ^(id input, completion_t completionHandler) {
[self loadAndSaveImageWithURL:input completion:completionHandler];
};
[self.urls forEachApplyTask:task ^(id results, NSError*error){
self.downloadImagesButton.enabled = YES;
if (error == nil) {
... // do something
}
else {
// handle error
}
}];
}
Again, notice that method forEachApplyTask:completion: is an asynchronous method, which returns immediately. The call-site will be notified via the completion handler.
The downloadImages method is asynchronous as well, there is no completion handler though. This method disables the button when it starts and enables it again when the asynchronous operation has been completed.
The implementation of this forEachApplyTask method can be found here: (https://gist.github.com/couchdeveloper/6155227).
From your code what I can understand is its not due to assyncronous call to load url. but the following code may heavy.
For assynchronous image loading try https://github.com/rs/SDWebImage
//Get Image From URL
NSString *urlString = [NSString stringWithFormat:#"https://myurl.com/%#",[[globals.farmerList objectAtIndex:i] objectForKey:#"Image"]];
UIImage * imageFromURL = [self getImageFromURL:urlString];
//Save Image to Directory
[self saveImage:imageFromURL withFileName:[[globals.farmerList objectAtIndex:i] objectForKey:#"Image"] ofType:#"jpg" inDirectory:documentsDirectoryPath];
Happy coding :)

MKNetworkKit doesn't retrieve any images

NSString *pictureUrl = [[[oneUserDict objectForKey:#"picture"]objectForKey:#"data"]objectForKey:#"url"];
[[AppEngine sharedEngine]imageAtURL:[NSURL URLWithString:pictureUrl] onCompletion:^(UIImage *fetchedImage, NSURL *url, BOOL isInCache)
{
int index = [usersArray indexOfObject:oneUserDict];
NSString *loadName = [NSString stringWithFormat:#"%d of %d",index,[usersArray count]];
NSLog(#"%i",usersArray.count);
int temp=[usersArray count]-10;
if (index!=temp)
{
[[LoadingIndicator currentIndicator]displayActivity:loadName];
NSLog(#"inside loading indicator");
}
else
{
[[LoadingIndicator currentIndicator]displayCompleted:#"Done"];
NSLog(#"finally done");
}
aPerson.image = UIImagePNGRepresentation(fetchedImage);
[appDelegate.managedObjectContext save:nil];
}];
AppEngine is the subclass of MKNetworkEngine which uses a method called imageAtURL:onCompletion:
what I am currently doing is retrieving all the images from a particular url and and storing them in aPerson.image,basically the above code is in a FOR loop(i.e the for the count of users).
Issues
The above code which is in the completion block never gets executed,i dont know why but i have put a breakpoint inside the block but still the compiler wont run the statements inside the completion block.
Api imageAtURL:onCompletion: is deprecated. Use imageAtURL:completionHandler:errorHandler: instead. Also MKNetworkKit provides for UIImageView+MKNetworkKitAdditions category which provides simple API for image download like setImageFromURL: placeHolderImage:
Cheers!
Amar.

iCloud - UIDocument saveToUrl does not execute completion block

I was testing with it the other day and it was working. Today, after calling this method, it never calls the completion block.
Is there a case where the completion block is not called? I remember Apple said that it's always called regardless of what happened.
Here's the code block:
dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
// Get ubiquitous url
NSURL *ubiq = [[NSFileManager defaultManager]
URLForUbiquityContainerIdentifier:nil];
NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:
#"Documents"] URLByAppendingPathComponent:fileName];
// Create new CloudFile
CloudFile *file = [[CloudFile alloc]initWithFileURL:ubiquitousPackage];
file.data = data;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onDocumentStateChanged:) name:UIDocumentStateChangedNotification object:file];
dispatch_async (dispatch_get_main_queue (), ^(void) {
[file saveToURL:file.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
NSLog(#"test");
if (success) {
[self sendEvent:ICLOUD_FILE_SAVE_SUCCESSFUL object:file];
} else {
[self sendEvent:ICLOUD_FILE_SAVE_FAILED];
NSLog(#"kaiCloud: Saving failed. (%#)",fileName);
}
}];
});
});
EDIT: Note that I added NSLog(#"test") and I'm not seeing it getting logged.
I encountered the same problem (which is how I found your question).
The reason is simple. The test code is not waiting for the completion handler to be called and certainly not for it to finish executing.
Your test may work sometimes if they are long enough or happen to get stalled. But that's not reliable.
Use the WAIT macros provided and explained here and problem solved.
https://github.com/hfossli/AGAsyncTestHelper

Resources