Uploading Multiple Files with AFNetworking - UIViewController not Deallocating - ios

I have to upload multiple files, track their progress & subscribe to completion & failure blocks in order to show relevant message at the end of operation.
I wrote my own AFHTTPClient wrapper and created following method.
- (void) uploadFiles:(NSArray*)files
path:(NSString*)path
parameters:(NSDictionary*)parameters
progressBlock:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block
success:(void (^)(AFHTTPRequestOperation *, id))success failure:(void (^)(AFHTTPRequestOperation *, NSError *))failure
{
NSMutableURLRequest *request =
[self multipartFormRequestWithMethod:#"POST"
path:path
parameters:parameters
constructingBodyWithBlock:
^(id <AFMultipartFormData>formData) {
for (CRLMultiPartFile *file in files) {
NSAssert(file.name, #"Name cannot be nil");
NSAssert(file.file, #"Nothing found to upload");
NSAssert(file.filename, #"FileName cannot be nil");
NSAssert(file.mimeType, #"Must set Mime-Type for %#", file.filename);
[formData appendPartWithFileData:file.file name:file.name fileName:file.filename mimeType:file.typeString];
}
}];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setUploadProgressBlock:block];
[operation setCompletionBlockWithSuccess:success failure:failure];
[self enqueueHTTPRequestOperation:operation];
}
The view controller that calls this method does not get deallocated and therefore all the images contained are retained in memory as well thus resulting in memory leaks and eventually memory warning.
Doing the profiling reveals that at the end of the whole operation, the view controller has a refCount of 1.
When I comment out the call to uploading the files, all works fine.
Here is the code in the controller. It uses the progress block to update elements on the UI.
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
ContactModel *model = (ContactModel*)[self.contacts lastObject];
[params setObject:model.phone forKey:#"receiver"];
__block typeof(self) sSelf = self;
[[JMClient sharedClient] uploadFiles:files
path:#"picture_share/"
parameters:params
progressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
CGFloat progPercent = ceilf(((CGFloat)totalBytesWritten / (CGFloat)totalBytesExpectedToWrite) * 100);
CGFloat widthToCut = (progPercent * sSelf.progWidth) / 100;
CGRect frame = sSelf.progresViewBG.frame;
frame.size.width = (sSelf.progWidth - widthToCut);
frame.origin.x = (sSelf.progOrigin + widthToCut);
sSelf.progresViewBG.frame = frame;
sSelf.progLabel.text = [NSString stringWithFormat:#"%i%%", (int)progPercent];
frame = sSelf.progTipView.frame;
frame.origin.x = (sSelf.progresViewBG.frame.origin.x - frame.size.width/2);
sSelf.progTipView.frame = frame;
frame = sSelf.progLabel.frame;
frame.origin.x = (sSelf.progresViewBG.frame.origin.x - frame.size.width/2);
sSelf.progLabel.frame = frame;
} success:^(AFHTTPRequestOperation *success, id reponse) {
CGRect frame = sSelf.progresViewBG.frame;
frame.size.width = 0;
frame.origin.x = sSelf.progOrigin;
sSelf.progresViewBG.frame = frame;
[sSelf.cancelButton setImage:[UIImage imageNamed:#"trnsfr_prgss_complt.png"] forState:UIControlStateNormal];
[sSelf performSelector:#selector(hideAwayProgressBars) withObject:nil afterDelay:3];
} failure:^(AFHTTPRequestOperation *failure, NSError *error) {
[Mediator showMessage:TGLocalizedString(kMessageKeyForUploadingFailed)];
[sSelf performSelector:#selector(hideAwayProgressBars) withObject:nil afterDelay:3];
}];
self.operation = [[self.client.sharedClient.operationQueue operations] lastObject];
- (void) hideAwayProgressBars
{
[[NSNotificationCenter defaultCenter] postNotificationName:kNotifcationKeyForPhotoUploadComplete object:nil];
}
The notification is received by the parent controller, that removes this controller's view from superview and sets it to nil.
P.S. CRLMultiPartFile is a custom class to hold attributes of the files to be uploaded

if you are using ARC, you should use __weak instead of __block, so you don't capture self inside the block.

Related

Image downloading progress bar not smooth using afnetworking in iOS

I am currently downloading the image using afnetworking, but the progress bar is not smooth in the first time, but when I run this code second time, the progress bar is smooth, here is my code to download images.
progress bar works like go up, down than smooth, but when I run code the second time it works smooth
progressBar.progress = 0.0;
self.imageDownloads=[[NSMutableArray alloc]init];
[self.imageDownloads addObject:[[ImageDownload alloc] initWithURL:[NSURL URLWithString:#""]];
for (int i=0; i < self.imageDownloads.count; i++)
{
ImageDownload *imageDownload = self.imageDownloads[i];
imageDownload.filename = [NSString stringWithFormat:#"MyImage%d",i];
[self downloadImageFromURL:imageDownload];
}
Here is my code to download images
- (void)downloadImageFromURL:(ImageDownload *)imageDownload
{
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *filePath = [docsPath stringByAppendingPathComponent:imageDownload.filename];
NSURLRequest *request = [NSURLRequest requestWithURL:imageDownload.url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
imageDownload.totalBytesRead = totalBytesRead;
imageDownload.totalBytesExpected = totalBytesExpectedToRead;
[self updateProgressView];
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSAssert([responseObject isKindOfClass:[NSData class]], #"expected NSData");
NSData *responseData = responseObject;
[responseData writeToFile:filePath atomically:YES];
// Because totalBytesExpected is not entirely reliable during the download,
// now that we're done, let's retroactively say that total bytes expected
// was the same as what we received.
imageDownload.totalBytesExpected = imageDownload.totalBytesRead;
[self updateProgressView];
NSLog(#"finished %#", imageDownload.filename);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error %#", imageDownload.filename);
}];
[operation start];
}
- (void)updateProgressView
{
double totalTotalBytesRead = 0;
double totalTotalBytesExpected = 0;
for (ImageDownload *imageDownload in self.imageDownloads)
{
// note,
// (a) totalBytesExpected is not always reliable;
// (b) sometimes it's not present at all, and is negative
//
// So, when estimating % complete, we'll have to fudge
// it a little if we don't have total bytes expected
if (imageDownload.totalBytesExpected >= 0)
{
totalTotalBytesRead += imageDownload.totalBytesRead;
totalTotalBytesExpected += imageDownload.totalBytesExpected;
}
else
{
totalTotalBytesRead += imageDownload.totalBytesRead;
totalTotalBytesExpected += (imageDownload.totalBytesRead > kDefaultImageSize ? imageDownload.totalBytesRead + kDefaultImageSize : kDefaultImageSize);
}
}
if (totalTotalBytesExpected > 0)
[progressBar setProgress:totalTotalBytesRead / totalTotalBytesExpected animated:YES];
else
[progressBar setProgress:0.0 animated:NO];
}
This code is from an answer back in 2013. I would suggest
Don’t use the deprecated AFHTTPRequestOperation, instead use NSURLSession download task-based solution. If you want to use AFNetworking, they have a mechanism to do that.
Don’t update/calculate percentages yourself, but rather nowadays you’d use NSProgress for the individual downloads which are children to some parent NSProgress. You can have your UIProgressView observe that. The net effect is that you end up just updating the child NSProgress instances, and your parent’s progress view is updated automatically.
For example, imagine that I have a parent UIProgressView called totalProgressView and I have a NSProgress that it is observing:
#interface ViewController () <UITableViewDataSource>
#property (nonatomic, strong) NSProgress *totalProgress;
#property (nonatomic, strong) NSMutableArray <ImageDownload *> *imageDownloads;
#property (nonatomic, weak) IBOutlet UIProgressView *totalProgressView;
#property (nonatomic, weak) IBOutlet UITableView *tableView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.totalProgress = [[NSProgress alloc] init];
self.totalProgressView.observedProgress = self.totalProgress;
self.tableView.estimatedRowHeight = 50;
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.imageDownloads = [NSMutableArray array];
}
...
#end
Then to start the downloads, I create a series of image downloads, add their individual NSProgress instances as children of the above totalProgress:
- (IBAction)didTapStartDownloadsButton {
NSArray <NSString *> *urlStrings = ...
NSURL *caches = [[[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:true error:nil] URLByAppendingPathComponent:#"images"];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
self.totalProgress.totalUnitCount = urlStrings.count;
for (NSInteger i = 0; i < urlStrings.count; i++) {
NSURL *url = [NSURL URLWithString:urlStrings[i]];
NSString *filename = [NSString stringWithFormat:#"image%ld.%#", (long)i, url.pathExtension];
ImageDownload *imageDownload = [[ImageDownload alloc] initWithURL:url filename:filename];
[self.imageDownloads addObject:imageDownload];
[self.totalProgress addChild:imageDownload.progress withPendingUnitCount:1];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDownloadTask *task = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
[imageDownload updateProgressForTotalBytesWritten:downloadProgress.completedUnitCount
totalBytesExpectedToWrite:downloadProgress.totalUnitCount];
} destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
return [caches URLByAppendingPathComponent:filename];
} completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
//do whatever you want here
}];
[task resume];
}
[self.tableView reloadData];
}
Where
// ImageDownload.h
#import Foundation;
NS_ASSUME_NONNULL_BEGIN
#interface ImageDownload : NSObject
#property (nonatomic, strong) NSURL *url;
#property (nonatomic, strong) NSString *filename;
#property (nonatomic) NSProgress *progress;
#property (nonatomic) NSUInteger taskIdentifier;
- (id)initWithURL:(NSURL *)url
filename:(NSString * _Nullable)filename;
/**
Update NSProgress.
#param totalBytesWritten Total number of bytes received thus far.
#param totalBytesExpectedToWrite Total number of bytes expected (may be -1 if unknown).
*/
- (void)updateProgressForTotalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
#end
NS_ASSUME_NONNULL_END
and
static const long long kDefaultImageSize = 1000000; // what should we assume for totalBytesExpected if server doesn't provide it
#implementation ImageDownload
- (id)initWithURL:(NSURL *)url filename:(NSString *)filename {
self = [super init];
if (self) {
_url = url;
_progress = [NSProgress progressWithTotalUnitCount:kDefaultImageSize];
_filename = filename ?: url.lastPathComponent;
}
return self;
}
- (void)updateProgressForTotalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
int64_t totalUnitCount = totalBytesExpectedToWrite;
if (totalBytesExpectedToWrite < totalBytesWritten) {
if (totalBytesWritten <= 0) {
totalUnitCount = kDefaultImageSize;
} else {
double written = (double)totalBytesWritten;
double percent = tanh(written / (double)kDefaultImageSize);
totalUnitCount = written / percent;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
self.progress.totalUnitCount = totalUnitCount;
self.progress.completedUnitCount = totalBytesWritten;
});
}
#end
That yields individual progress bars for the individual downloads, and the progress bar associated with the totalProgress is updated automatically for you, yielding:
Now, obviously, you don't need both the children UIProgressView and the parent one, too, so that's up to you. But the idea is
set up a hierarchy of NSProgress;
tell the UIProgressView to observe whatever NSProgress you want; and
just have your download update the child NSProgress values and the rest will happen automatically for you.

Objective-C: Get a result of a singleton-class method after finish executing the method?

I have a singleton class that checks the login status of the app.
There's a method named attemptToLogin in the singleton class that makes an http request with parameters and returns with json data about the login status. (true or false)
Now in the main TabBarViewController i did the following:
#interface CustomTabBarController ()
#end
#implementation CustomTabBarController{
LoginCheckSingleton *loginSingleton;
dispatch_queue_t myCustomQueue;
}
-(void)viewDidAppear:(BOOL)animated{
myCustomQueue = dispatch_queue_create("com.myDomain.appName", NULL);
loginSingleton = [LoginCheckSingleton sharedInstance];
dispatch_sync(myCustomQueue, ^{
[loginSingleton attemptToLogin];
});
if([loginSingleton.loginstat isEqual: #"true"]){
NSLog(#"Logged In");
}else{
NSLog(#"Not Logged In");
[self performSegueWithIdentifier:#"goToLoginView" sender:self];
}
}
The dispatch_sync is not working properly here, i want to execute the function inside the dispatch_sync and get the results before executing the if statement under it. But the if statement is executed before the block inside the dispatch_sync has finished.
This is the Singleton Class:
#import "LoginCheckSingleton.h"
#import "AFNetworking.h"
#import "SSKeychain.h"
#import "CustomTabBarController.h"
static LoginCheckSingleton *sharedInstance = nil;
#interface LoginCheckSingleton (){
NSString *serverURLString;
NSURL *serverURL;
NSString *userId;
NSString *password;
NSString *email;
NSMutableArray *jsonContents;
NSMutableDictionary *dictionary;
NSString *loggedInStatus;
bool loggedInTF;
}
#end
#implementation LoginCheckSingleton{
}
+ (LoginCheckSingleton*) sharedInstance {
static dispatch_once_t _singletonPredicate;
static LoginCheckSingleton *_singleton = nil;
dispatch_once(&_singletonPredicate, ^{
_singleton = [[super allocWithZone:nil] init];
});
return _singleton;
}
+ (id) allocWithZone:(NSZone *)zone {
return [self sharedInstance];
}
-(void)attemptToLogin{
// Retrieve credentials from Keychain
userId = [SSKeychain passwordForService:#"com.lazemni.iFresh"
account:#"ifreshUserId"];
password = [SSKeychain passwordForService:#"com.lazemni.iFresh"
account:#"ifreshPassword"];
email = [SSKeychain passwordForService:#"com.lazemni.iFresh"
account:#"ifreshEmail"];
if(email == nil || password == nil){
NSLog(#"empty username or password");
}else{
NSLog(#"not empty username or password");
serverURLString = #"http://www.lazemni.com/demo/ifresh/api/login/";
serverURLString = [serverURLString stringByAppendingString:email];
serverURLString = [serverURLString stringByAppendingString:#"/"];
serverURLString = [serverURLString stringByAppendingString:password];
NSLog(#"%#",serverURLString);
serverURL = [NSURL URLWithString:serverURLString];
NSURLRequest *request = [NSURLRequest requestWithURL:serverURL];
//AFNetworking asynchronous url request
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]
initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
jsonContents = [responseObject objectForKey:#"login"];
NSLog(#"%#",jsonContents);
dictionary = [jsonContents objectAtIndex:0];
loggedInStatus = [dictionary objectForKey:#"status"];
if([loggedInStatus isEqual: #"true"]){
NSLog(#"Succefully loggedin!");
[SSKeychain setPassword:[dictionary objectForKey:#"user_id"] forService:#"com.lazemni.iFresh" account:#"ifreshUserId"];
[SSKeychain setPassword:[dictionary objectForKey:#"email"] forService:#"com.lazemni.iFresh" account:#"ifreshEmail"];
[SSKeychain setPassword:[dictionary objectForKey:#"password"] forService:#"com.lazemni.iFresh" account:#"ifreshPassword"];
self.loginstat = #"true";
loggedInTF = true;
}else if([loggedInStatus isEqual: #"false"]){
NSLog(#"Wrong email/password combination!");
self.loginstat = #"false";
loggedInTF = false;
}
} failure:nil];
[operation start];
}
}
#end
I never understood how dispatch_sync really works,
Any idea how to wait for the [loginSingleton attemptToLogin]; to finish ?
attemptToLogin is probably an asynchronous method. You want to write attemptToLogin in such a way that it gives you a callback when the HTTP Request finishes executing. You can do that by means of a completion block, or a delegate, or a notification.
If you wait for the request to finish, you'll end up blocking the main thread, which will freeze user interactions, leading to a terrible UX.
- (void)attemptToLogin:(void(^)(BOOL login))complete {
...
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
....
// After you get login status
complete(your_login_status_here);
}
}
And in CustomTabBarController
-(void)viewDidAppear:(BOOL)animated{
myCustomQueue = dispatch_queue_create("com.myDomain.appName", NULL);
loginSingleton = [LoginCheckSingleton sharedInstance];
[loginSingleton attemptToLogin:^(loginStatus){
if([loginStatus isEqual: #"true"]){
NSLog(#"Logged In");
}else{
NSLog(#"Not Logged In");
dispatch_async(dispatch_get_main_queue(), {
[self performSegueWithIdentifier:#"goToLoginView" sender:self];
});
}
}];
}

Recursive Blocks and a Bad Memory Leak

I am using recursive blocks to fetch tweets via the Twitter API and do encounter a bad memory leak: 50 recursive fetches lead to a memory footprint > 500 MB.
This method is called from inside my view controller and controls the lists of which the timelines should be fetched:
- (void)fetchTweetsForUsersInList:(List*)list withCompletionHandler:
(simpleCompletionBlock)completionBlock
{
[self.managedDocument.managedObjectContext performBlock:^{
NSFetchRequest *request = [NSFetchRequest
fetchRequestWithEntityName:#"User"];
request.predicate = predicate;
request.sortDescriptors = sortDescriptors;
request.shouldRefreshRefetchedObjects = YES;
NSError *error;
NSArray *users = [self.managedDocument.managedObjectContext
executeFetchRequest:request error:&error];
__weak __block void (^weakCompletion)(NSError*);
__block void (^completion)(NSError*);
__block NSUInteger i = 0;
__block User *user = users[i];
unsigned long long since_id = ....;
unsigned long long maxId = ....;
completion = ^(NSError *error) {
if (i == users.count - 1 ) {
completionBlock(nil);
}
else {
i++;
user = users[i];
[self fetchTimelineOfUser:user
sinceId:since_id maxId:maxId
withCompletionHandler:weakCompletion];
}
};
weakCompletion = completion;
[self fetchTimelineOfUser:user
sinceId:since_id maxId:maxId
withCompletionHandler:completion];
}];
}
And this is fetchTimelineOfUser which is called recursively:
- (void)fetchTimelineOfUser:(User*)user
sinceId:(unsigned long long)since_id
maxId:(unsigned long long)maxId
withCompletionHandler:(simpleCompletionBlock)completionBlock
{
TwitterRequest *twitterRequest = self.twitterRequest;
NSUInteger count = 2000;
__weak __block void (^weakCompletion)(NSError*, unsigned long long, NSArray*);
__block void (^completion)(NSError*, unsigned long long, NSArray*);
completion = ^(NSError *error, unsigned long long minId, NSArray *tweets) {
[self.managedDocument.managedObjectContext performBlockAndWait:^{
[tweets enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// Create new Tweet object
Tweet *tweet = [NSEntityDescription
insertNewObjectForEntityForName:#"Tweet"
inManagedObjectContext:self.managedDocument.managedObjectContext];
tweet.text = [obj valueForKey:#"text"];
NSError *saveError;
[self.managedDocument.managedObjectContext save:&saveError];
// (1) If no more tweets above since_id or the minimum
// tweet id has reached an existing tweet id, recursion is complete:
if (tweets.count == 0 || minId - 1 == since_id) {
NSError *saveError;
[self.managedDocument.managedObjectContext
save:&saveError];
[self.managedDocument.managedObjectContext
performBlockAndWait:^{
[self.managedDocument saveToURL:self.managedDocument.fileURL
forSaveOperation:UIDocumentSaveForOverwriting
completionHandler:^(BOOL success) {
completionBlock(nil);
return;
}];
}];
}
// (2) Otherwise RECURSIVELY call ...
else {
[twitterRequest
fetchTimelineForUser:user.screen_name
sinceId:since_id maxId:minId-1
count:count
withCompletion:weakCompletion];
}
}];
}
};
weakCompletion = completion;
[twitterRequest fetchTimelineForUser:user.screen_name
sinceId:since_id maxId:maxId
count:count
withCompletion:completion];
}
Question:
Is it possible that I have a retain cycle or other memory leak?
Do you have any idea why my memory footprint is so high?
Thank you!

Show/Hide Activity indicator when i load url image from a Json using Afnetworking

This is my method to load my json with afnetworking:
-(void) connectPhp {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager GET:#"http://XXXXXXXXXXXXX.php" parameters:nil success:^(AFHTTPRequestOperation *operation, NSArray * responseObject) {
i=0;
test = (NSDictionary *)responseObject;
compteur =test[#"images"][#"items"];
total=[compteur count];
NSLog(#"%d TOTO", total);
// AFFICHER LE TOTAL D'URL DANS TOTAL NSLog(#"%d", total);
NSLog(#"%#", test[#"images"][#"items"][i][#"title"]);
NSString *urltest = test[#"images"][#"items"][i][#"url"];
self.label.text = test[#"images"][#"items"][i][#"title"];
url = [NSURL URLWithString:urltest];
data = [NSData dataWithContentsOfURL:url];
img = [[UIImage alloc] initWithData:data];
//Je stocke le total d'images
totalImg = [NSString stringWithFormat:#"%d", total];
//Je stocke l'ID de l'image
idImg = [NSString stringWithFormat:#"%d", i+1];
NSLog(#"%#", idImg);
//je donne au compteur la valeur idImg
self.compteurID.text=idImg;
self.compteurTotal.text=totalImg;
if (total<9){
[self.pageControl setNumberOfPages:total];
[self.pageControl setCurrentPage:i];
}
else if (total>9){
[self.pageControl setNumberOfPages:10];
NSLog(#"ICI TOTAL>10");
}
}
failure:nil];
}
This is my method to switch my images on drag:
-(void) scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (self.scrollView.contentOffset.x<-20){
if (i==0){[self resizeImages];}
else{
i--;
[self resizeImages];
}
}
else if ((self.scrollView.bounds.size.width)-(self.scrollView.contentOffset.x)==25){
if (i==(total)-1){i=0;}
i++;
[self resizeImages];
}
else if (self.scrollView.contentOffset.x>(self.imgView.frame.size.width)/3){
if (i==(total)-1){i=0;}
i++;
[self resizeImages];
}
}
It works.
When i drag, i load the next url of image in my json. It works.
However, I would like to show an activity indicator when i drag and when my next image is loading. How can i do that?
When you start loading the image, add a UIActivityIndicatorView to your view hierarchy and start the animation.
When your image finishes downloading, send the activity indicator a removeFromSuperview message.

block execution iOS and assigning variable

I am using the drupal-ios-sdk (based on AFNetworking) and my app has a Tab Bar Controller created with storyboard. When loading one of the view controllers I am creating a request in initWithCoder with drupal-ios-sdk and assigning an instance variable in the success block. Later in viewDidLoad I try to print this variable and I'm interested in why I have to retain the instance variable in the success block, even if I define the variable with autorelease.
This is not ARC!
Not using retain in the success block
My VC.h
#interface AboutViewController : UIViewController {
#private
NSDictionary *response;
NSString *aboutPageHTML;
}
#end
My VC.m
-(id) initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
NSDictionary *viewData = [NSMutableDictionary new];
[viewData setValue:#"aboutse" forKey:#"view_name"];
aboutPageHTML = [[[NSString alloc]init] autorelease];
void(^successBlock)(AFHTTPRequestOperation*, id) =
^(AFHTTPRequestOperation *operation, id responseObject) {
response = [responseObject copy];
aboutPageHTML = [response valueForKey:#"body"];
NSLog(#"%s - %#", __PRETTY_FUNCTION__, aboutPageHTML);
[aboutPageHTML retain]; // critical!
};
[DIOSView viewGet:viewData success:successBlock
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%s, %#", __PRETTY_FUNCTION__, error);
}];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"%s - %#", __PRETTY_FUNCTION__, aboutPageHTML);
NSLog(#"%s - %f %f", __PRETTY_FUNCTION__, self.view.bounds.size.width, self.view.bounds.size.height);
}
Edit:
declaring the variable with __block does not seem to make a difference. How come?
There are many things wrong with this code. aboutPageHTML is an instance variable that you want a strong reference to (i.e. should be retained). In aboutPageHTML = [[[NSString alloc]init] autorelease]; you are not maintaining a strong reference to it. This is incorrect.
And later on, in your success block (it is irrelevant that it is a block; it's just any code that runs later), you do aboutPageHTML = [response valueForKey:#"body"]; [aboutPageHTML retain]; which does retain the object stored in that instance variable. So you are being inconsistent with the memory management for the same instance variable in two places.
Also, when you assign another value to a strongly-referenced variable, you should make sure to release the previous value, which you are not doing. This goes for both aboutPageHTML and response.
Finally, the object pointed to by viewData is leaked, as you have a strong reference to it (you use new) but don't release it.
Also, you should use setObject:forKey: and objectForKey: for dictionaries. And you need to declare a mutable dictionary when you want a dictionary you can change. (This is probably why you (incorrectly) decided to use setValue:forKey:)
So in conclusion, this would be more like it:
-(id) initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
NSMutableDictionary *viewData = [NSMutableDictionary new];
[viewData setObject:#"aboutse" forKey:#"view_name"];
aboutPageHTML = [[NSString alloc] init];
void(^successBlock)(AFHTTPRequestOperation*, id) =
^(AFHTTPRequestOperation *operation, id responseObject) {
[response release];
response = [responseObject copy];
[aboutPageHTML release];
aboutPageHTML = [[response objectForKey:#"body"] retain];
};
[DIOSView viewGet:viewData success:successBlock
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%s, %#", __PRETTY_FUNCTION__, error);
}];
[viewData release];
}
return self;
}

Resources