I'm currenly working on a tiny project which amis to loading ALL gallery photos into my app to show some fancy effect. Unfortunately, these default thumbnail provided by system cannot meet my requirement. So I try to create my own thumbnails using "fullScreenImage". To speed up the process, I load fullScreenImage using background operations. The main methods are:
- (void)getFullScreenImage:(NSURL *)url success:(void(^)(UIImage *))callback
{
NSLog(#"Requesting %#", url);
[assetsLibraryInstance assetForURL:url resultBlock:^(ALAsset *asset) {
callback(asset.defaultRepresentation.fullScreenImage);
}
failureBlock:nil];
}
- (void)processURLs:(NSArray *)urls
{
for (NSURL *url in urls) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) ^{
[self getFullScreenImage:url success:^(UIImage *img) {
NSLog(#"Got image %#", img);
}] ;
});
}
}
Only the "Requesting..." log is printed in console, the "getFullScreenImage" method is locked, no any output.
I tried below methods to work around this issue:
Not sharing assetsLibraryInstance (Didn't work)
Don't dispatch_async when enumeate urls in "processURLs". (Did work, but I don't want to use a signle thread to process all URLs)
Not using global queue, using main queue (Did work, but all these "fullScreenImage" works were doing on UI thread, making UI non-responsive)
Using a private queue created with "dispatch_queue_create". (Didn't work)
So, is ALAssetsLibrary thread safe? I guess it's not... Or, is there any better way I can use to:
Load fullScreenImage in background
Multiple threading
Thanks!
Related
I have an app written in Objective-C that needs to autoload some data from a node.js server.
Application 1 sends a message to my server that Application 2 then needs to receive. Application 2 needs to load this message automatically (no refresh buttons). These messages are common data and not Remote Notifications with APN.
I currently use the following:
- (void) checkForNewMessages {
// call the method on a background thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self->dataParser getMessages:^(NSArray *arr, NSError *error) {
// If there's an error
if (error) {
return;
}
// otherwise
[self->messageArray removeAllObjects];
self-> messageArray = [NSMutableArray arrayWithArray:arr];
if (self-> messageArray) { // is not nil
// update UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self->newMessageTimer invalidate];
[self tableRefresh];
});
}
}];
});
}
which uses a GET request to pull the messages (controlled by a repeating 10-second timer that invalidates when messages are found).
I figured this would work as it runs in the background - but this has become quite buggy, and relies on a timer - it also often freezes the UI while the request is taking place, even though it should be running in the background.
Essentially, is there a better solution to this type of functionality - or does this seem perfectly valid for a production app.
With DISPATCH_QUEUE_PRIORITY_DEFAULT you allow the system to select a queue which to use and sometimes it can decide to choose main queue, as it is free enough. So in your case it is better approach to specify explicitly that you need background queue, as below
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[self->dataParser getMessages:^(NSArray *arr, NSError *error) {
...
I want to do the following:
1)DownLoad a series of files whose URLs are stored in an NSMutableArray.
2)During the download process a MBProgressHUD shows the download status.
3)At any point of download I want to cancel the download, when the user touches the screen.
-(void)singleTap:(UITapGestureRecognizer*)sender
{
NSLog(#"%#",#"tapped");
self.downLoadHud.detailsLabelText=#"";
self.downLoadHud.labelText=[SAGlobal stringForValue:#"CANCELLINGDOWNLOAD"];
SharedAppDelegatee.downLoadCancelFlag=YES;
}
-(void) startFileDownLoadingWithHUD
{
self.downLoadHud=[MBProgressHUD showHUDAddedTo:[SharedAppDelegatee window] animated:YES];
self.downLoadHud.mode = MBProgressHUDModeIndeterminate;
UITapGestureRecognizer *HUDSingleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(singleTap:)];
[self.downLoadHud addGestureRecognizer:HUDSingleTap];
self.downLoadHud.labelText = #"Initialising..";
self.downLoadHud.detailsLabelText =#"";
[self.downLoadHud setColor:[UIColor blackColor]];
dispatch_queue_t dispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(dispatchQueue, ^(void)
{
[self startAllFilesInArrayDownload];
//downloadcancelled or downloadfinished
while (!(SharedAppDelegatee.downLoadCancelFlag)||(SharedAppDelegatee.SAdownloadMode==0)) {
self.downLoadHud.labelText =[NSString stringWithFormat:#"Downloading..[%d/%d]",self.self.downloadErrorCount+self.downloadSuccessCount,[filesToDownLoad count]];
self.downLoadHud.detailsLabelText =[SAGlobal stringForValue:#"TAPTOCANCEL"];
//NSLog(#"DOWNLOADING------+");
}
////////////////////////////////
dispatch_sync(dispatch_get_main_queue(), ^{
[self.downLoadHud hide:YES];
//downLoadHud
});
});
}
The download is done with
for (downDict in filesToDownLoad)
{
//[adm downloadURL:[downDict objectForKey:#"url"] destPath:[downDict objectForKey:#"toFile"]];
NSURL *aUrl = [NSURL URLWithString:[downDict objectForKey:#"url"]];
[self.downloadManager addDownloadWithFilename:[downDict objectForKey:#"toFile"] URL:aUrl];
//[urlStringsArray addObject:[downDict objectForKey:#"url"]];
}
The "downloadManager" is an object of class "DownloadManager" which is obtained
https://github.com/robertmryan/download-manager
I could succesively download all files. I am NOT able to cancel the download in the middle of download. When the user taps the button, it waits a long time, and after some time, the "singleTap" method is called.
The number of files downloaded and failed are CORRECTLY shown. What is wrong with my code?. Can any one suggest me a better example or way to handle "Showing a busy hud + downloading+ tap to cancel feature similar as shown below.
While I am certain that this is a great utility and a lot of work went into it I was immediately concerned when i saw that the repo was two years old.
These types of things are a great help and i can't begin to express my appreciation for the authors and their generosity in sharing so much hard work. Unfortunately, if they are not maintained then they can become difficult for the user to update.
Apple has more recently introduced new functionality with NSURLSession.
This is pretty easy to use and is much more powerful than NSURLConnection.
It specifically includes the ability to pause, resume and cancel network downloads.
I'm uploading multiple photo's to a server with AFNetworking (POST). It works great, except on iPhone 4/4S where I run into memory issues. The problem is all the payloads are built in advance and fill up memory; request are build much faster then they are sent.
So rater than executing my AFNetworking calls serially, I need to wait for each call to:
self.manager POST:parameters:success:failure: to complete before calling it again. My code is something like this:
iterate over a group of images
[self sendTheImageToTheServer:image completion:{ ok, I'm done. send me the next one now. }];
// sendTheImageToTheServer:image calls self.manager POST:parameters:success:failure:
done
Ideally I'd like to use a dispatch group so I can a be alerted when the last block is done.
Any suggestions would be great.
EDIT: I can't use setMaxConcurrentOperationCount to help with this because all my payloads are built in advance and fill up memory; I use AFNetworking constructingBodyWithBlock to construct the body of my POST. Also, I don't want this to block so I need to be notified when the last image is sent.
Fixed. Here is what worked:
dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MyQueue", NULL);
for (FBPhoto *photo in page.images) {
dispatch_async(queue, ^{
#autoreleasepool {
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[self addPhotoToUploadQueue:photo callback:^{
dispatch_semaphore_signal(sem);
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
});
}
This bit of code can be used to deterring when the last image is sent or rather the last item in the queue is processed:
dispatch_barrier_async(queue, ^{
// all done
});
I just noticed that webViewDidFinishLoad method blocks an entire application, so i can't even touch any buttons.
I need to parse the resulting page of the UIWebView and it can take a lot of time. So what's the best way to parse it without blocking an entire application? Maybe create another thread?
Parse it in the background using GCD:
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// Get the contents from the UIWebView (in the main thread)
NSString *data = [webView stringByEvaluatingJavaScriptFromString:#"document.documentElement.textContent"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Parse the data here
dispatch_sync(dispatch_get_main_queue(), ^{
// Update the UI here
});
});
}
It's normal -webViewDidFinishLoad to be called on the main thread. What you need to do is to get the html and do the parsing operation by dispatching it to another queue.
Just learning how to allocate tasks among threads, or dispatch asynchronously. I understand that any operation that "touches" a view must be done on the main thread. What about: UIImageWriteToSavedPhotosAlbum? I would assume this could be done on a background thread, but am I mistaken?
Also, if it should be done on a background thread, is there a difference between these two calls below, as one saves a UIImage and the other saves a UIImage from a view?
UIImageWriteToSavedPhotosAlbum(_someUIImage ,nil,nil,nil);
UIImageWriteToSavedPhotosAlbum(_imageView.image ,nil,nil,nil);
By the way I am using this setup to run an HUD in the main thread and to tasks in the background, that is my intention.
[HUD_code showMessage:#"saving image"];
dispatch_queue_t concurrentQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
UIImageWriteToSavedPhotosAlbum(someUIImage ,nil,nil,nil);
dispatch_async(dispatch_get_main_queue(), ^{
[HUD_code dismiss];
});
});
UIKit classes are documented to be usable from the main thread only, except where documented otherwise. (For example, UIFont is documented to be thread-safe.)
There's no explicit blanket statement about the thread safety of UIKit functions (as distinct from classes), so it's not safe to assume they generally thread-safe. The fact that some UIKit functions, like UIGraphicsBeginImageContext, are explicitly documented to be thread-safe, implies that UIKit functions are not generally thread-safe.
Since UIImageWriteToSavedPhotosAlbum can send an asynchronous completion message, you should just call it on the main thread and use its completion support to perform your [HUD_code dismiss].
Here is my latest code after reading the answers, if anyone cares to know, or to comment (appreciated).
-(void)saveToLibrary {
if (_imageView.image != NULL) {
messageHUD = #"Saving Image...";
[SVProgressHUD showWithStatus:messageHUD];
UIImageWriteToSavedPhotosAlbum(_imageView.image, self, #selector(image:didFinishSavingWithError:contextInfo:), nil);
}
}
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
UIAlertView *alert;
// Unable to save the image
if (error) {
alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"Unable to save image to Photo Album."
delegate:self cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
}else {// All is well
messageHUD = #"Success!\nImage Saved.";
[SVProgressHUD showSuccessWithStatus:messageHUD];
[self myPerformBlock:^{[SVProgressHUD dismiss];} afterDelay:0.5];
}
}
The myPerformBlock is from the following link https://gist.github.com/955123
I would assume this could be done on a background thread, but am I mistaken?
Honestly, I would assume it too, since this has absolutely nothing to do with updating the UI, it's just some file operation. However, Apple's documentation says that every call to UIKit needs to be performed on the main thread (Except where something else is explicitly stated). This function is no exception, you have to call it on the main thread.
By the way, this function is asynchronous itself. It will notify the callback object/selector supplied as its 2nd and 3rd arguments when the image is saved, and thus it doesn't block the UI.