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
});
Related
In my app I use the following method to check for values of certain variables which are meant to be accessed on the main thread only.
Now that I began to implement APNs and when my app is woken by APNs it seems that code execution (in background) is always stuck at the point indicated using comments:
- (void) xttSyncOnMainThread:(void (^)(void))prmBlock {
if (![NSThread isMainThread]) {
dispatch_queue_t mtQueue = dispatch_get_main_queue(); // will be executed
// execution is stuck here
dispatch_sync(mtQueue, prmBlock); // won't be executed
} else {
prmBlock();
}
}
Do I need to move all code to non-MT queues or am I missing something else?
Thanks a lot!
Because dispatch_sync on main queue cause deadlock.
More information about dispatch_sync and main queue is for example here:
dispatch_sync on main queue hangs in unit test
Why dispatch_sync( ) call on main queue is blocking the main queue?
Can you just use dispatch_async method ?
why are you passing prmBlock to dispatch_sync
usually it is like
dispatch_sync(dispatch_get_main_queue(), ^(void) {
// write the code that is to be executed on main thread
});
But if you use disptch_sync it will wait for the block to complete execution and then return. If you don't want to block the execution use
dispatch_async(dispatch_get_main_queue(), ^(void) {
// write the code that is to be executed on main thread
});
Ok, after some more testing I found that in my case (while the code in the question works just fine) the problem came from accidently calling the completionhandler from the APNs delegate too soon.
- (void) xttSyncOnMainThread:(void (^)(void))prmBlock {
dispatch_async(dispatch_get_main_queue(), ^{
//code here to perform
});
}
I have two methods as loadTopicPostsFromDB and loadTopicPosts. In the loadTopicPostsFromDB method I am updating the value of a global NSString called strLastTimeStamp which should use in the loadTopicPosts. Thus, I want to execute loadTopicPostsFromDB first and after it finished(global string updated) I want to execute loadTopicPosts method.
This is how I did it. But, currently loadTopicPosts method executes before updating the global strLastTimeStamp, so always I get a wrong strLastTimeStamp.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
[self performSelectorOnMainThread:#selector(loadTopicPostsFromDB) withObject:nil waitUntilDone:NO];
});
dispatch_group_notify(group, queue, ^{
NSLog(#"LoadDBCompleted");
[self loadTopicPosts];
});
How can I do this, please advice me on what is the wrong in this implementation.
performSelectorOnMainThread: is finished as soon as iOS has put the task into a queue. The selector has most likely not even started running when the call returns. And really, you shouldn't be using performSelectorOnMainThread at all - the function isn't available in Swift, for good reason. The solution is a lot easier (fix the problems yourself):
dispatch_async (dispatch_get_main_queue (), ^{
[self loadTopicsFromDB];
[self loadTopicPosts];
});
You probably want to perform loadTopicsFromDB on a background thread though.
When you are doing something using network connection I advice you to use blocks to handle the endpoint of the call.
It is pretty simple to write in this code
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self loadTopicsFromDB: ^(BOOL success, NSError *error) {
[self loadTopicPosts];
}];
});
In my app I need to load up data from multiple sources and put them together in a table view. Gathering each of the sources one after another would take forever. To get around this I need to run all of the download operations together. Since they are download tasks, in theory I could just run them, but the issue is that only part of the code on the thread runs asynchronously, which means it will need the main thread to complete the operation.
So in order to get ALL of it running in the background, I need to use GCD, which I don't have much experience with.
//DataLoader.m
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
[self.webLoader getFeedWithCompletion:self.thatOtherCompletionBlock];
[self.otherDataLoader getDataWithCompletion:self.completionBlock];
[self.thatDataLoader getThatDataWithCompletion:self.anotherCompletionBlock]
dispatch_async(dispatch_get_main_queue(), ^(void){
});
});
However, since part of the task is already asynchronous, I need to figure out where to put GCD code.
I could put it before starting the task, like I did above. This could work, however, since the tasks are already partially run in the background (in some cases I cannot change that), it seems wasteful to be running a task that already runs partially in the background in the background. Why run something that already runs in a background thread in another thread?
Another option would be to use GCD in the actual class that gets the feed (ex. webloader), putting it on all code that isn't running in the background
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
.......
});
Which way is better?
There is also another problem. Since part of the tasks are asynchronous, they use completion blocks. Not only do I need to also run the completion blocks in the background, I need to figure out which one is the last one to finish, so I can run some code to clean up and neatly package and ship the data to the view controller.
The way I thought of would be to use a BOOL for each task, simply changing it to true when it's done. Then in my completion blocks I can check if all the other tasks are complete, and if so, run the clean up code. However, this may not be the most elegant solution.
What would be the best way to deal with these tasks, ensuring that it all happens in the background?
GCD groups could easily be used for this. Groups allow you to track arbitrary "members" of the group, and hook a block up to run when all members of the group have finished. It's quite handy. For example (using your code):
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group); // + 1
[self.webLoader getFeedWithCompletion: ^{
self.thatOtherCompletionBlock();
dispatch_group_leave(group); // - 1
}];
dispatch_group_enter(group); // + 1
[self.otherDataLoader getDataWithCompletion:^{
self.completionBlock();
dispatch_group_leave(group); // - 1
}];
dispatch_group_enter(group); // + 1
[self.thatDataLoader getThatDataWithCompletion:^{
self.anotherCompletionBlock();
dispatch_group_leave(group); // - 1
}];
dispatch_group_notify(group, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// This will get executed once all three of the prior completion blocks have been run.
// i.e. when the group "count" goes to zero.
});
dispatch_release(group);
});
You could also, albeit a bit circuitously, use NSOperation's inter-operation dependency feature to achieve this. Like this:
NSOperationQueue* q = [[[NSOperationQueue alloc] init] autorelease];
NSOperation* completionA = [NSBlockOperation blockOperationWithBlock: self.thatOtherCompletionBlock];
NSOperation* completionB = [NSBlockOperation blockOperationWithBlock: self.completionBlock];
NSOperation* completionC = [NSBlockOperation blockOperationWithBlock: self.anotherCompletionBlock];
NSBlockOperation* afterAllThree = [[[NSBlockOperation alloc] init] autorelease];
[afterAllThree addDependency: completionA];
[afterAllThree addDependency: completionB];
[afterAllThree addDependency: completionC];
[afterAllThree addExecutionBlock:^{
// This will get executed once all three of the prior completion blocks have been run.
}];
// Kick off the tasks
[q addOperationWithBlock:^{
[self.webLoader getFeedWithCompletion: ^{ [q addOperation: completionA];}];
[self.otherDataLoader getDataWithCompletion:^{ [q addOperation: completionB]; }];
[self.thatDataLoader getThatDataWithCompletion:^{ [q addOperation: completionC]; }];
}];
I personally prefer the dispatch_group method, but they would both get the job done.
This is what I am doing.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData* data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://myurl"]]];
dispatch_sync(dispatch_get_main_queue(), ^{
if(!data) {
// data not recieved or bad data. Initiate reachability test
// I have built a wrapper for Reachability class called ReachabilityController
// ReachabilityController also notifies the user for avaibility, UI
ReachabilityController *reachability = [[ReachabilityController alloc] init];
[reachability checkReachability];
return;
}
//update table
});
});
My problem is the reachability test is being done in the main queue, which often freezes the UI. I want to run in a background mode.
I want to process the ReachabilityTest in a background mode or in a low priority mode. But again, my reachability controller does notify user of the current net avaibility, so at some point i will have to use main queue again.
I strongly believe that there must be a better way.
This is, however, a correct way. It doesn't look entirely pretty, but that doesn't mean it's incorrect. If you want your code to look 'cleaner' you might wanna take a look at NSThread and work your way through it, but this is a far easier approach.
To make it look easier in my project we made a simple class called dispatcher that uses blocks:
+ (void)dispatchOnBackgroundAsync:(void(^)(void))block {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), block);
}
+ (void)dispatchOnMainAsync:(void(^)(void))block {
dispatch_async(dispatch_get_main_queue(), block);
}
used like this:
[Dispatcher dispatchOnBackgroundAsync:^{
// background thread code
[Dispatcher dispatchOnMainAsync:^{
// main thread code
}];
}];
I have an app that I'm accessing a remote website with NSURLConnection to run some code and then save out some XML files. I am then accessing those XML Files and parsing through them for information. The process works fine except that my User Interface isn't getting updated properly. I want to keep the user updated through my UILabel. I'm trying to update the text by using setBottomBarToUpdating:. It works the first time when I set it to "Processing Please Wait..."; however, in the connectionDidFinishLoading: it doesn't update. I'm thinking my NSURLConnection is running on a separate thread and my attempt with the dispatch_get_main_queue to update on the main thread isn't working. How can I alter my code to resolve this? Thanks! [If I need to include more information/code just let me know!]
myFile.m
NSLog(#"Refreshing...");
dispatch_sync( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self getResponse:#"http://mylocation/path/to/file.aspx"];
});
[self setBottomBarToUpdating:#"Processing Please Wait..."];
queue = dispatch_queue_create("updateQueue", DISPATCH_QUEUE_CONCURRENT);
connectionDidFinishLoading:
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Contacts..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Emails..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}
In my connectionDidFinishLoading: I would try something like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^ {
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Contacts..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Emails..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}
});
Then all that file access is happening in a background queue so the main queue is not locked up. The main queue will also complete this call to connectionDidFinishLoading much more quickly, since you're throwing all the hard work onto the default queue instead, which should leave it (and the main thread) ready to accept your enqueuing of the updates to the UI which will be done by the default queue as it processes the block you just enqueued to it.
The queue handover becomes
main thread callback to connectionDidFinishLoad:
rapid handoff to default global queue releasing main thread
eventual hand off to main queue for setBottomBarToUpdating: calls
performing main queue blocks on main thread to properly update UI
eventual completion of blocks on main queue
eventual completion of blocks on default queue
You've increased concurrency (good where you've good multi-core devices) and you've taken the burden of I/O off the main thread (never a good place for it) and instead got it focused on user interface work (the right place for it).
Ideally you woud run the NSURLConnection run loop off the main thread too, but this will might be enough for you to get going.
Which run loop are you running the NSURLConnection in? If it's the main loop, you're queueing up the setBottomBarToUpdating: calls behind the work you're already doing, hence the probable reason why you're not seeing the UI update.
You could also give performSelectorOnMainThread try like so:
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
[self performSelectorOnMainThread:#selector(setBottomBarToUpdating) withObject:#"Updating Contacts..." waitUntilDone:false];
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
[self performSelectorOnMainThread:#selector(setBottomBarToUpdating) withObject:#"Updating Emails..." waitUntilDone:false];
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}