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.
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 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);
}];
I'm making an application in which I have to call some webservices. I chose to work with AFNetworking.
I followed the Twitter example provided in the library. Everything works well except that I have permanently the little "processing circle" in the notification bar (see the image below).
Here's the code I have for my request :
- (id)initWithAttributes:(NSDictionary *)attributes
{
self = [super init];
if (!self) {
return nil;
}
_name = [attributes valueForKeyPath:#"name"];
return self;
}
+ (void)itemsListWithBlock:(void (^)(NSArray *items))block
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *user = [defaults objectForKey:#"user"];
NSDictionary *company = [defaults objectForKey:#"company"];
NSMutableDictionary *mutableParameters = [NSMutableDictionary dictionary];
/*
** [ Some stuff to set the parameters in a NSDictionnary ]
*/
MyAPIClient *client = [MyAPIClient sharedClient];
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
[[AFNetworkActivityIndicatorManager sharedManager] incrementActivityCount];
NSURLRequest *request = [client requestWithMethod:#"POST" path:#"getMyList" parameters:mutableParameters];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSMutableArray *mutableItems = [NSMutableArray arrayWithCapacity:[JSON count]];
for (NSDictionary *attributes in JSON) {
ListItem *item = [[ListItem alloc] initWithAttributes:attributes];
[mutableItems addObject:item];
}
if (block) {
block([NSArray arrayWithArray:mutableItems]);
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON){
[[[UIAlertView alloc] initWithTitle:#"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:nil otherButtonTitles:#"Ok", nil] show];
if (block) {
block(nil);
}
}];
[operation start];
}
Does this means my request isn't finished ? I'm not really getting what I'm doing wrong here...
If someone could help, I'd really appreciate. Thanks.
Don't call [[AFNetworkActivityIndicatorManager sharedManager] incrementActivityCount]; this increase the activity count with 1 and the [operation start]; will call it also. now the activity count is 2 and will get decreased when the operation is done. But since you called the incrementActivityCount it will bring it back to 1 and not 0.
Just call [[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES]; once, for example place it in the application:applicationdidFinishLaunchingWithOptions: method of your applications appDeletage.
Also I would suggest to add the operation to a NSOperationQueue and not just call start on it.
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]];
}