I have the following code:
[[AHPinterestAPIClient sharedClient] getPath:requestURLPath parameters:nil
success:^(AFHTTPRequestOperation *operation, id response) {
[weakSelf.backgroundQueue_ addOperationWithBlock:^{
[self doSomeHeavyComputation];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[weakSelf.collectionView_ setContentOffset:CGPointMake(0, 0)];
[weakSelf.collectionView_ reloadData];
[weakSelf.progressHUD_ hide:YES];
[[NSNotificationCenter defaultCenter] performSelector:#selector(postNotificationName:object:) withObject:#"UIScrollViewDidStopScrolling" afterDelay:0.3];
[weakSelf.progressHUD_ hide:YES];
}];
}];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[weakSelf.progressHUD_ hide:YES];
[weakSelf.collectionView_.pullToRefreshView stopAnimating];
NSLog(#"Error fetching user data!");
NSLog(#"%#", error);
}];
For some reason this worked just fine in iOS 5, but not iOS 6 (it crashes). Now I am not going to ask about iOS 6 because it's still under NDA. What I want to know is, whether the code above is wrong? If yes how do I fix it.
If I put the code inside the block outside of the mainQueue then it's fine.
What I am trying to do here is to do the NSOperationQueue mainQueue only after the [self doSomeHeavyComputation] is done. So this is a dependency, how should I add this dependency?
Update:
Here's the crash log if it helps:
It is recommended to “unwind” weak references in the block, so please try this:
__weak id weakSelf = self;
[client getPath:path parameters:nil success:^(id op, id response) {
id strongSelf = weakSelf;
if (strongSelf == nil) return;
__weak id internalWeakSelf = strongSelf;
[strongSelf.backgroundQueue_ addOperationWithBlock:^{
id internalStrongSelf = internalWeakSelf;
if (internalStrongSelf == nil) return;
[internalStrongSelf doSomeHeavyComputation];
__weak id internalInternalWeakSelf = internalStrongSelf;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
id internalInternalStrongSelf = internalInternalWeakSelf;
if (internalInternalStrongSelf == nil) return;
[internalInternalStrongSelf reloadCollectionView];
}];
}];
}
failure:^(id op, NSError *error) {
id strongSelf = weakSelf;
if (strongSelf == nil) return;
[strongSelf stopProgress];
NSLog(#"Error fetching user data: %#", error);
}];
Related
I am trying to display tweets in UITableView.
Code :-
- (void)requestTimeline:(int)count
{
NSURL *url = [NSURL URLWithString:#"http://api.twitter.com/1/statuses/user_timeline.json"];
NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
[parameters setObject:#"abc" forKey:#"screen_name"];
[parameters setObject:#"20" forKey:#"count"];
[parameters setObject:#"1" forKey:#"include_entities"];
TWRequest *request = [[TWRequest alloc] initWithURL:url parameters:parameters requestMethod:TWRequestMethodGET];
[request performRequestWithHandler:^(NSData *responseData1, NSHTTPURLResponse *urlResponse, NSError *error)
{
if (responseData1 != nil)
{
NSError *error = nil;
self.itemsToDisplay = [NSJSONSerialization JSONObjectWithData:responseData1 options:NSJSONReadingMutableLeaves error:&error];
if (self.itemsToDisplay != nil)
{
[self.tableView reloadData];
}
else
{
NSLog(#"Error serializing response data %# with user info %#.", error, error.userInfo);
}
}
else
{
NSLog(#"Error requesting timeline %# with user info %#.", error, error.userInfo);
}
}];
}
self is UITableViewController's subclass.
Due to block, twitter request will be performed on another thread not on main thread. Thats why it get crashed.
//TWRequest.h
// Issue the request. This block is not guaranteed to be called on any particular thread.
- (void)performRequestWithHandler:(TWRequestHandler)handler;
Crash reort:-
bool _WebTryThreadLock(bool), 0xa8e4f50: 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...
How resolve this issue ?
Thanks...
the web view must only ever be used from the main thread. much of UIKit -- the tableView reloadData command is also not thread-safe
dispatch_async(dispatch_get_main_queue(), ^{
//all you ever do with UIKit.. in your case the reloadData call
[self.tableView reloadData];
});
Solved :
Also below code works :
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.tableView reloadData];
}];
try:
[self.tableView performSelectorInMainThread:#selector(reloadData)];
instead of:
[self.tableView reloadData];
Insert this, may be it helps:
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
I have the following method which retrieves an ALAsset from the UIImagePicker after it snaps a photo. It then attempts to send this ALAsset to another one of my methods via the main thread:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
NSURL *imageURL = [[info valueForKey:UIImagePickerControllerReferenceURL] retain];
__block ALAsset *result;
[self.assetsLibrary assetForURL:imageURL resultBlock:^(ALAsset *asset)
{
result = [asset retain];
dispatch_async(dispatch_get_main_queue(), ^
{
[self loadPhotoImageViewWithAsset:result];
[self dismissModalViewControllerAnimated:YES];
[imageURL release];
[result release];
});
}
failureBlock:^(NSError *error)
{
}];
}
When I get into the dispatch_async(dispatch_get_main_queue(), ^ block, result is showing as nil. Anyone know what I'm doing wrong here?
Refer UIImagePickerController to use for a UIImageView for result is showing as nil.
Use like this:
[self.assetsLibrary assetForURL:imageURL resultBlock:^(ALAsset *asset)
{
result = [asset retain];
[self loadPhotoImageViewWithAsset:result];
[self dismissModalViewControllerAnimated:YES];
[imageURL release];
[result release];
}
failureBlock:^(NSError *error)
{
}];
Change your loadPhotoImageViewWithAsset Method
-(void)loadPhotoImageViewWithAsset:(ALAsset *)asset
{
//dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
//dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^
{
//here code for loading image
});
// });
}
I have this method, which downloads full size image for UIImageView when clicked on cell.
- (void)configureView
{
NSURLRequest *URLRequest = [[NSURLRequest alloc] initWithURL:self.imageURL];
AFImageRequestOperation *operation = [[AFImageRequestOperation alloc] initWithRequest:URLRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[SVProgressHUD showSuccessWithStatus:#"Done!"];
[self.imageView setImage:responseObject];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
float percentage = (totalBytesRead / (totalBytesExpectedToRead * 1.0f) * 100);
NSLog(#"Percentage: %f", percentage);
if (percentage != 100.0)
{
[SVProgressHUD showWithStatus:[NSString stringWithFormat:#"Downloading... %0.0f%%", percentage]];
}
}];
[operation start];
}
So when download completes, [SVProgressHUD showSuccessWithStatus:#"Done!"]; is called.
but when you go back to tableViewController and click on the same cell, [SVProgressHUD showSuccessWithStatus:#"Done!"]; is called again even though the image is cached.
How can I check if the image is cached and only call [SVProgressHUD showSuccessWithStatus:#"Done!"]; when it is not?
AFImageRequestOperation doesn't cache images. I think you are confusing AFNetworking's category "UIImageView+AFNetworking" with "AFImageRequestOperation". The category does cache images, AFImageRequestOperation does not.
I having trouble with my threads.
After i segue a couple of times between 2 screen when the thread is busy. The thread don't perform every line.., The breakpoint just disappear when it has to return to the main thread.
Can somebody please help me ?
I release the thread when the view is unload.
Thanks,
- (void)fetchFeedDataIntoDocument
{
NSString * labelString = [NSString stringWithFormat:#"Feed Fetcher %#", self.pageTitle];
const char *label = [labelString UTF8String];
self.fetchF = dispatch_queue_create(label, NULL);
dispatch_async(self.fetchF, ^{
NSArray *feeds = [FeedFetcher getDataForJson:self.pageTitle downloadBy:#"up"];
NSDictionary *lastfeed;
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate getManagedObjectContext];
if ([feeds count] > 0)
{
lastfeed = [feeds objectAtIndex:0];
[FeedFetcher setLastUpdateIdToCatgorie:self.pageTitle WithId:[lastfeed objectForKey:#"id"] AndPulishDate:[lastfeed objectForKey:#"publish_up"]];
}
for (NSDictionary *feedInfo in feeds) {
[Feed FeedWithInfo:feedInfo InManageObject:context];
}
NSError *error = nil;
[context save:&error];
if (error){
NSLog(#"Error save : %#", error);}
dispatch_async(dispatch_get_main_queue(), ^{
[self setupFetchedResultsController];
[self.tableView reloadData];
[self downloadImagesForFeeds:feeds];
});
});
You are accessing the managedObjectContext from a different thread from where it was created. This is Core Data Threading Rule #1.
You are getting the MOC from the app delegate. If it's the normal Xcode-generated MOC, then it is created with thread-confinement concurrency. You can't even call performBlock with it. You can only access that MOC from the main thread. Period. Anything else is playing with fire, at best.
If you want to do all the work in a separate thread, you need a separate MOC as well. Like this (just typed - not compiled)...
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = appDelegate.managedObjectContext;
[moc performBlock:^{
// Go get your remote data and whatever you want to do
// Calling save on this MOC will push the data up into the "main" MOC
// (though it is now in the main MOC it has not been saved to the store).
[moc save:&error];
}];
Which would translate into something like this...
- (void)fetchFeedDataIntoDocument
{
NSString * labelString = [NSString stringWithFormat:#"Feed Fetcher %#", self.pageTitle];
const char *label = [labelString UTF8String];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *mainContext = [appDelegate getManagedObjectContext];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.parentContext = mainContext;
[context performBlock:^{
NSArray *feeds = [FeedFetcher getDataForJson:self.pageTitle downloadBy:#"up"];
NSDictionary *lastfeed;
if ([feeds count] > 0)
{
lastfeed = [feeds objectAtIndex:0];
[FeedFetcher setLastUpdateIdToCatgorie:self.pageTitle WithId:[lastfeed objectForKey:#"id"] AndPulishDate:[lastfeed objectForKey:#"publish_up"]];
}
for (NSDictionary *feedInfo in feeds) {
[Feed FeedWithInfo:feedInfo InManageObject:context];
}
NSError *error = nil;
[context save:&error];
if (error){
NSLog(#"Error save : %#", error);}
DO you really want to continue on error?
dispatch_async(dispatch_get_main_queue(), ^{
// Data has been pushed into main context from the background
// but it still needs to be saved to store...
// Do not forget to perform error handling...
NSError *error = nil;
[mainContext save:&error];
[self setupFetchedResultsController];
[self.tableView reloadData];
[self downloadImagesForFeeds:feeds];
});
});
EDIT
The code generated by Xcode for creating the MOC uses init, which uses NSConfinementConcurrencyType. You can replace it with MainConcurrency, without any problems, but get several benefits.
In your app delegate file, replace...
__managedObjectContext = [[NSManagedObjectContext alloc] init];
with this...
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
Now, your main MOC can be "parented" and you can also call performBlock on it as well.
what about doing this...
-(void)functionToCallFetch {
[self performSelectorInBackground:#selector(fetchFeedDataIntoDocument) withObject:nil];
}
- (void)fetchFeedDataIntoDocument
{
NSArray *feeds = [FeedFetcher getDataForJson:self.pageTitle downloadBy:#"up"];
NSDictionary *lastfeed;
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate getManagedObjectContext];
if ([feeds count] > 0)
{
lastfeed = [feeds objectAtIndex:0];
[FeedFetcher setLastUpdateIdToCatgorie:self.pageTitle WithId:[lastfeed objectForKey:#"id"] AndPulishDate:[lastfeed objectForKey:#"publish_up"]];
}
for (NSDictionary *feedInfo in feeds) {
[Feed FeedWithInfo:feedInfo InManageObject:context];
}
NSError *error = nil;
[context save:&error];
if (error){
NSLog(#"Error save : %#", error);}
//dispatch_async(dispatch_get_main_queue(), ^{
// [self setupFetchedResultsController];
// [self.tableView reloadData];
// [self downloadImagesForFeeds:feeds];
//});
[self performSelectorOnMainThread:#selector(setupFetchedResultsController) withObject:nil waitUntilDone:NO];
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
[self performSelectorOnMainThread:#selector(downloadImagesForFeeds:) withObject:feeds waitUntilDone:NO];
}
Maybe that would work better?
Change following code..
dispatch_queue_t queue1 = dispatch_queue_create("com.MyApp.AppTask",NULL);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(queue1,
^{
dispatch_async(main,
^{
[self setupFetchedResultsController];
[self.tableView reloadData];
[self downloadImagesForFeeds:feeds];
});
});
Hope, this will help you
Did you try building a method like :
- (void)methodToBePerformedOnMainThread{
[self setupFetchedResultsController];
[self.tableView reloadData];
[self downloadImagesForFeeds:feeds];
}
and call it with
[self performSelectorOnMainThread:#selector(methodToBePerformedOnMainThread) withObject:nil waitUntilDone:NO];
at the end of fetchFeedDataIntoDocument
Edit :
Did you try to wrap it with NSOperationQueue in place of dispatch_queue ?
Like :
NSOperationQueue *operationQueue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(fetchFeedDataIntoDocument) object:nil];
if(operation != nil){
[operationQueue addOperation:operation];
I would like to wait this code to be executed before to continue but as these blocks are called assynchronously I don't know how to do???
NSURL *asseturl;
NSMutableArray *tmpListAsset = [[NSMutableArray alloc] init];
ALAssetsLibrary *library = [[[ALAssetsLibrary alloc] init] autorelease];
NSMutableArray *objectsToRemove = [[NSMutableArray alloc] init];
for (NSDictionary *dico in assetsList) {
asseturl = [NSURL URLWithString:[dico objectForKey:#"assetUrl"]];
NSLog(#"asset url %#", asseturl);
// Try to load asset at mediaURL
[library assetForURL:asseturl resultBlock:^(ALAsset *asset) {
// If asset doesn't exists
if (!asset){
[objectsToRemove addObject:dico];
}else{
[tmpListAsset addObject:[asseturl absoluteString]];
NSLog(#"tmpListAsset : %#", tmpListAsset);
}
} failureBlock:^(NSError *error) {
// Type your code here for failure (when user doesn't allow location in your app)
}];
}
GCD semaphore approach:
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
for (NSURL *url in self.assetUrls) {
dispatch_async(queue, ^{
[library assetForURL:url resultBlock:^(ALAsset *asset) {
[self.assets addObject:asset];
dispatch_semaphore_signal(sema);
} failureBlock:^(NSError *error) {
dispatch_semaphore_signal(sema);
}];
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
dispatch_release(sema);
/* Check out ALAssets */
NSLog(#"%#", self.assets);
The solution is here
http://omegadelta.net/2011/05/10/how-to-wait-for-ios-methods-with-completion-blocks-to-finish/
Note that assetForURL:resultBlock:failureBlock: will stuck if the main thread is waiting without RunLoop running. This is alternative ( cleaner :-) ) solution:
#import <libkern/OSAtomic.h>
...
ALAssetsLibrary *library;
NSMutableArray *assets;
...
__block int32_t counter = 0;
for (NSURL *url in urls) {
OSAtomicIncrement32(&counter);
[library assetForURL:url resultBlock:^(ALAsset *asset) {
if (asset)
[assets addObject:asset];
OSAtomicDecrement32(&counter);
} failureBlock:^(NSError *error) {
OSAtomicDecrement32(&counter);
}];
}
while (counter > 0) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
The easiest thing to do is to move your code to inside (at the end of) the resultBlock or the failureBlock. That way, your code will run in the correct order, and you will also retain asynchronous behaviour.
This is an easy way to do it. Maybe not as elegant as using GCD but it should get the job done ... This will make your method blocking instead of non-blocking.
__block BOOL isFinished = NO;
NSURL *asseturl;
NSMutableArray *tmpListAsset = [[NSMutableArray alloc] init];
ALAssetsLibrary *library = [[[ALAssetsLibrary alloc] init];
NSMutableArray *objectsToRemove = [[NSMutableArray alloc] init];
for (NSDictionary *dico in assetsList) {
asseturl = [NSURL URLWithString:[dico objectForKey:#"assetUrl"]];
NSLog(#"asset url %#", asseturl);
// Try to load asset at mediaURL
[library assetForURL:asseturl resultBlock:^(ALAsset *asset) {
// If asset doesn't exists
if (!asset){
[objectsToRemove addObject:dico];
}else{
[tmpListAsset addObject:[asseturl absoluteString]];
NSLog(#"tmpListAsset : %#", tmpListAsset);
}
if (objectsToRemove.count + tmpListAsset.count == assetsList.count) {
isFinished = YES;
}
} failureBlock:^(NSError *error) {
// Type your code here for failure (when user doesn't allow location in your app)
isFinished = YES;
}];
}
while (!isFinished) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01f]];
}