I'm implementing a simple twitter client for the iPhone using a UITableView. I fetch the picture of each twitter user in my feed when their cell appears in tableView: cellForRowAtIndexPath:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *profileImage = [tweet.user getProfileImageDataInContext:self.fetchedResultsController.managedObjectContext];
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = profileImage;
});
});
Here is the code to fetch the image:
if (!self.profileImage)
{
sleep(2);
self.profileImage = [NSData dataWithContentsOfURL:[NSURL URLWithString:self.profileImageURL]];
//// if we recently scheduled an autosave, cancel it
[TwitterUser cancelPreviousPerformRequestsWithTarget:self selector:#selector(autosave:) object:context];
// request a new autosave in a few tenths of a second
[TwitterUser performSelector:#selector(autosave:) withObject:context afterDelay:0.2];
}
return [UIImage imageWithData:self.profileImage];
Here is the error I'm getting:
twitterClient[10743:15803] bool _WebTryThreadLock(bool), 0x59bac90: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
I think it's also worth mentioning that this happens when I scroll through the tableview very quickly when it hasn't been populated yet.
I would like the main UI to update upon completion of the download. The actual twitter app for iPhone does this quite well.
Any suggestions?
What’s the crash? A pretty standard pattern for things like this is
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do background stuff
dispatch_async(dispatch_get_main_queue(), ^{
// do main-queue stuff, like updating the UI
});
});
You're using GCD fine, your crash is happening because you're calling dataWithContentsOfURL in a background thread, which is not thread safe.
See: Does -dataWithContentsOfURL: of NSData work in a background thread?
Related
I have this dispatch_queue code that I'm using to make 3 different data requests. Then I'm updating the tableView on the main thread. What else can I put in the main thread? I'm using this [self requestExtendedData]; to download data from a web service that is then parsed and set to UILabels etc...
My question is: if I have the [self requestExtendedData]; running in the background thread how do I update the UILabels with the content from this data in the main thread? Should I put everything else in the main thread area? All the UIView, UILabels and UIButton objects etc...
thanks for the help
dispatch_queue_t jsonParsingQueue = dispatch_queue_create("jsonParsingQueue", NULL);
// execute a task on that queue asynchronously
dispatch_async(jsonParsingQueue, ^{
[self requestUserData]; // request user comments data
[self requestExtendedData]; // request extended listing info data
[self requestPhotoData]; // request photo data
// once this is done, if you need to you can call
// some code on a main thread (delegates, notifications, UI updates...)
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
});
// release the dispatch queue
dispatch_release(jsonParsingQueue);
This is what apple document saying:
Note: For the most part, UIKit classes should be used only from an application’s main thread. This is particularly true for classes derived from UIResponder or that involve manipulating your application’s user interface in any way.
That means you should put all your UIView, UILabels and UIButton objects code in the main thread like this:
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
// Your UI update code here
});
For Swift3 :
DispatchQueue.main.async{
self.tableView.reloadData()
// any UI related code
self.label.text = "Title"
self.collectionView.reloadData()
}
Try something like this after your dispatch_async
[self performSelectorOnMainThread:#selector(doUIStuffOnMainThread:)
withObject:self.tableView.data
waitUntilDone:YES];
Try adding one main queue operation like this :
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.tableView reloadData];
}];
[your_tableview performSelectorOnMainThread:#selector(reloadData)
withObject:nil
waitUntilDone:YES];
I have the following code that attempts to load a row of thumbnails in a tableview asynchronously:
for (int i = 0; i < totalThumbnails; i++)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
__block GraphicView *graphicView;
__block Graphic *graphic;
dispatch_async(dispatch_get_main_queue(),
^{
graphicView = [[tableViewCell.contentView.subviews objectAtIndex:i] retain];
graphic = [[self.thumbnailCache objectForKey: [NSNumber numberWithInt:startingThumbnailIndex + i]] retain];
if (!graphic)
{
graphic = [[self graphicWithType:startingThumbnailIndex + i] retain];
[self.thumbnailCache setObject: graphic forKey:[NSNumber numberWithInt:startingThumbnailIndex + i]];
}
[graphicView setGraphic:graphic maximumDimension:self.cellDimension];
});
[graphicView setNeedsDisplay];
dispatch_async(dispatch_get_main_queue(),
^{
CGRect graphicViewFrame = graphicView.frame;
graphicViewFrame.origin.x = ((self.cellDimension - graphicViewFrame.size.width) / 2) + (i * self.cellDimension);
graphicViewFrame.origin.y = (self.cellDimension - graphicViewFrame.size.height) / 2;
graphicView.frame = graphicViewFrame;
});
[graphicView release];
[graphic release];
});
}
However when I run the code I get a bad access at this line: [graphicView setNeedsDisplay]; It's worth mentioning that the code works fine when I have it set up like this:
for (int i = 0; i < totalThumbnails; i++)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
dispatch_async(dispatch_get_main_queue(),
^{
//put all the code here
});
}
It works fine and the UITableView loads asynchronously when it's first called, however the scrolling is still really choppy.
So I'd like to get the to get the first bit of code to work so I can get the drawing done in the global thread instead of the main thread (which I assume will fix the choppy scrolling?).
Since iOS4 drawing is able to be done asynchronously so I don't believe that is the problem. Possibly I'm misusing the __Block type?
Anyone know how I can get this to work?
You completely misunderstand how to use GCD. Looking at your code:
__block GraphicView *graphicView;
Your variable here is not initialised to nil. It is unsafe to send messages to.
__block Graphic *graphic;
dispatch_async(dispatch_get_main_queue(),
^{
//statements
});
Your dispatch statement here returns immediately. The system works for you to spin this task off on a different thread. Before, or perhaps at the same time as, the above statements are executed we move on to the next line of execution here...
[graphicView setNeedsDisplay];
At this point graphic view may or may not have been initialised by your dispatch statement above. Most likely not as there wont have been time. As it still hasn't been initialised it points to random memory and hence trying to send messages to it causes EXC_BAD_ACCESS.
If you want to draw cell contents asynchronously (or pre-render images or whatever.) I thouroughly reccommend watching WWDC 2012 session 211 "Building Concurrent User Interfaces on iOS". They do almost exactly what you seem to be attempting to do and explain all the pitfalls you can run into.
I think the issue is because you are trying to re-draw the UIView on a working thread. You should move this:
[graphicView setNeedsDisplay];
To the main queue.
I am using the new CoreMotion framework to monitor some of the hardware devices. Here is the typical code to do that:
-(void)startAccelerometer{
self.motion.accelerometerUpdateInterval = 1/30.0f;
NSOperationQueue* accelerometerQueue = [[NSOperationQueue alloc] init];
CMAccelerometerHandler accelerometerHandler = ^(CMAccelerometerData *accelerometerData, NSError *error) {
NSLog(#"Accelerometer realtime values");
NSLog(#"x=%f", accelerometerData.acceleration.x);
NSLog(#"y=%f", accelerometerData.acceleration.y);
NSLog(#"z=%f", accelerometerData.acceleration.z);
NSLog(#" ");
};
[self.motion startAccelerometerUpdatesToQueue:accelerometerQueue withHandler:[[accelerometerHandler copy]autorelease]];
}
That works just fine. Now I want to print the values on a UILabel, but since the CoreMotion frameworks has you use blocks, this is not guaranteed to be in the main queue (and in fact isn't for my app). Is it is "wrong" to just run the label's setter on the main queue like this?
-(void)startAccelerometer{
self.motion.accelerometerUpdateInterval = 1/30.0f;
NSOperationQueue* accelerometerQueue = [[NSOperationQueue alloc] init];
CMAccelerometerHandler accelerometerHandler = ^(CMAccelerometerData *accelerometerData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
self.lblAccelerometer.text = [NSString stringWithFormat:#"Accelerometer:\nx = %f\ny = %f\nz = %f",
accelerometerData.acceleration.x,
accelerometerData.acceleration.y,
accelerometerData.acceleration.z];
});
};
[self.motion startAccelerometerUpdatesToQueue:accelerometerQueue withHandler:[[accelerometerHandler copy]autorelease]];
}
It works just fine and I don't really see any reason why this would be frowned upon. Any thoughts on that?
This is a common method that I use in many projects. UI updates must occur on the main thread.
//Dispatch on background thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//background processing goes here
//Dispatch on main thread
dispatch_async(dispatch_get_main_queue(), ^{
//update UI here
});
});
In your case, your UI updates are occurring on the main thread. So I wouldn't worry about changing anything.
You are missunderstanding the concept of blocks, to put it simple:
Blocks are small pieces of code that can be handled as variables and be executed at a certain time or thread.
All UI updates MUST be performed on the main thread so as long as you do this it will be fine.
Codes can be executed in different threads with different priorities in sync or async mode. On your code you are doing it perfectly fine, you not only dispatch it to the Main Queue which is where uiupdates should be executed, but you are also dispatching it async which is the safest way to update send to the main queue (from your code i cannot tell if you are running this specific piece of code from the main queue or a secondary queue but if u were to dispatch a sync block from the main queue to the main queue your program would stop working)
For iOS documentation:
Use the dispatch_get_main_queue function to get the serial dispatch
queue associated with your application’s main thread. This queue is
created automatically for Cocoa applications and for applications that
either call the dispatch_main function or configure a run loop (using
either the CFRunLoopRef type or an NSRunLoop object) on the main
thread.
Read this here http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW1
I'm making the app-book for ipad like app-magazine.
Now I'm using ScrollView and want to load many 1024*768 images(about 100 images), (As you know, If all images are loaded at once, It is impossible.)
so I load just 5 pages(current page & 2 pre pages & 2 next pages) and remove the other pages.
But, I have a question.
I made the method('loadTitlePage') for loading the page and I have to call this method when I want to load all pages.
So, I can't use dispatch_async but dispatch_sync.
Is there any difference between using dispatch_sync and writing code in line(non-block without dispatch_sync)?
It's my code.
[self loadTitlePage:currentPageNo];
dispatch_queue_t dqueue = dispatch_queue_create("scrollLoadTitlePage", NULL);
dispatch_sync(dqueue, ^{
[self loadTitlePage:currentPageNo-2]; });
dispatch_sync(dqueue, ^{
[self loadTitlePage:currentPageNo-1]; });
dispatch_sync(dqueue, ^{
[self loadTitlePage:currentPageNo+1]; });
dispatch_sync(dqueue, ^{
[self loadTitlePage:currentPageNo+2]; });
dispatch_sync(dqueue, ^{
[self removeTitlePage:currentPageNo-3 withNo:currentPageNo+3]; });
You can read here: using dispatch_sync in Grand Central Dispatch
In short.. dispatch_sync is equivalent to a mutex lock.. in your case I don't think there is any difference
The following code is within the cellForRowAtIndexPath. I need to edit this code to the following situations;
1.) If there was a problem to download the image this block should return a setFailedBlock block, How can i add it to my code ?
2.) While the image is downloading the user changes the view, then i want to stop executing this code (Stop the download). I think i should write the cancel the block in the viewdiddissapear or viewwilldissapear methods. But i don't know how to write the code to cancel the download. Can someone show me how to do this ?
(note: this block is inside the cellForRowAtIndexPath method so have to access it from viewdiddissapear or viewwilldissapear)
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this will start the image loading in bg
dispatch_async(concurrentQueue, ^{
NSData *someimageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:someimageURL]];
dispatch_async(dispatch_get_main_queue(), ^{
[cell.imageviewofsomeimage setImage:[UIImage imageWithData:someimageData ] ];
});
});
if you use NSURLConnection, this class has cancel
AsyncURLConnection discussion