Accessing NSMutableArray from inside for loop - objective-c

I am very new at iOS and objective-c development, so I am struggling with an understanding of how I do this.
First my code:
-(NSMutableArray *)messagesGetList
{
NSMutableArray *messageList = [[NSMutableArray alloc] init];
NSURL *url = [NSURL URLWithString:#"http://xxxx/"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
for (NSDictionary * dataDict in JSON) {
[messageList addObject: dataDict];
}
}
failure:^(NSURLRequest *request , NSURLResponse *response , NSError *error , id JSON)
{
NSLog(#"Failed: %#", error);
}];
[operation start];
return messageList;
}
What I am having a problem with, is that I can not access the NSMutableArray *messageList inside my for (NSDictionary * dataDict in JSON) loop.
I.e. nothing is added to the array while executing my loop.
How do I access the array from within my loop?
Thanks in advance for your help,
fischer

Since +JSONRequestOperationWithRequest: takes blocks to be called on success and on failure, it's a good guess that this method runs asynchronously. So, if you're checking the array that's returned from your -messagesGetList right away, it's likely to be empty. If you wait a while before checking, you may see it fill up.

I think you need to add the __block storage modifier to your NSMutableArray variable, in order for it to be mutable inside the block response.
__block NSMutableArray *messageList = [[NSMutableArray alloc] init];

You should pass a delegate object to this method, and send a message to the delegate when the network request has completed. This is a best practice on the iOS platform.

Related

Setting object property inside a block trouble on Objective-C

I'm starting to learn Objective-C for iOS Development, and a got a issue that is driving me crazy.
All that I want is to do a request, retrieve e JSON and then set this JSON into an instance property.
-(NSArray *) retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat: endpointURL, fuegoWSURL]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSDictionary *dict = (NSDictionary *) JSON;
[self setJSONObjectsCollection: [dict objectForKey:rootNode]];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Communication Error: %#", error);
}];
[operation start];
return _JSONObjectsCollection;
}
-(void) setJSONOBjectsCollectionAttribute: (NSArray *) arrayWithCollection
{
NSLog(#"Outside Method %#", arrayWithCollection);
self.JSONObjectsCollection = arrayWithCollection;
}
However, my self.JSONObjectsCollection property are ok inside the block, but outside is always null.
Can you help me guys ?
It's because the setting of JSONObjectsCollection happens asynchronously. So your method is returning JSONObjectsCollection before it is set.
Thus, it might look like:
- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat: endpointURL, fuegoWSURL]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSDictionary *dict = (NSDictionary *) JSON;
[self setJSONObjectsCollection: [dict objectForKey:rootNode]];
// do here whatever you want to do now that you have your array, e.g.
//
// [self.tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Communication Error: %#", error);
}];
[operation start];
}
Note, retrieveAtEndpoint now has a void return type, but in the completion block, I'm invoking whatever code you want to perform once the JSON objects collection has been updated.
If this is a method inside your model object, but you want to provide an interface by which the view controller can supply a block of code that should be executed upon successful retrieval of the JSON, use a completion block:
- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode completion:(void (^)(NSError *error))completion
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat: endpointURL, fuegoWSURL]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSDictionary *dict = (NSDictionary *) JSON;
[self setJSONObjectsCollection: [dict objectForKey:rootNode]];
if (completion)
{
completion(nil);
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
if (completion)
{
completion(error);
}
}];
[operation start];
}
Or, if you want to simplify your use of a block parameter, you can define a type for the completion block at the start of your model object's .h file (before the #interface block):
typedef void (^RetrievalCompleteBlock)(NSError *);
And then the method is simplified a bit:
- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode completion:(RetrievalCompleteBlock)completion
{
// the code here is like it is above
}
Anyway, regardless of whether you use the typedef or not, the view controller could do something like:
ModelObject *object = ...
NSString *rootNode = ...
[object retrieveAtEndpoint:url withRootNode:rootNode completion:^(NSError *error) {
if (error)
{
// handle the error any way you want, such as
NSLog(#"%s: retrieveAtEndPoint error: %#", __FUNCTION__, error);
}
else
{
// do whatever you want upon successful retrieval of the JSON here
}
}];
The details here will vary based upon how your view controller is accessing the model object, knows that the root node should be, etc. I often will include another parameter to my completion block which is the data being retrieved, but given that you updated your model object and can access it that way, perhaps that's not necessary. I simply don't have enough details about your implementation to know what is right here, so I kept my implementation as minimalist as possible.
But hopefully this illustrates the idea. Give your retrieveAtEndpoint method a completion block parameter, which lets the view controller specify what it wants to do upon completion (or failure) of the communication with the server.

Detect real end of the for () loop in objective-c

I'm going to show in my app a sort of UIActivityIndicatorView while parsing several JSON objects, inside a for () loop. I can't figure WHERE I must place the [UIActivityIndicatorView startAnimating] and [UIActivityIndicatorView stopAnimating] to show the real END of parsing (at the end of the complete for () loop). This is the simplified code:
- (void)parseMethod {
// other stuff
for (int i=0; i < [arrayJSON count]; i++) {
NSURL *url = [NSURL URLWithString:
[NSString stringWithFormat:
#"http://WWW.REMOTESERVERWITHJSONSOURCE.NET/%#",[arrayJSON objectAtIndex:i]]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSMutableDictionary *arrayMain = [JSON objectForKey:#"main"];
NSMutableDictionary *arrayMain2 = [JSON objectForKey:#"main2"];
[arrayA1 addObject:[arrayMain valueForKey:#"A1"]];
[arrayA2 addObject:[arrayWind valueForKey:#"A2"]];
// HERE FINISH PARSING AFJSONREQUESTOPERATION
// IF I PUT HERE [UIActivityIndicatorView stopAnimating] IT SHOWS AT THE END OF FIRST for () LOOP
[table reloadData];
[table scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES]
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"%#", [error userInfo]);
}];
[operation start];
// HERE START PARSING AFJSONREQUESTOPERATION
[UIActivityIndicatorView startAnimating]
}
// HERE FINISH THE for () LOOP ??
// IF I PUT HERE [UIActivityIndicatorView stopAnimating] IT SHOWS AGAIN AT THE END OF FIRST for () LOOP
}
// HERE FINISH THE parseMethod ??
// IF I PUT HERE [UIActivityIndicatorView stopAnimating] IT SHOWS AGAIN AT THE END OF FIRST for () LOOP
}
As u can see, I can't find the true place to put [UIActivityIndicatorView stopAnimating], 'cos everywhere it stop at the end of the FIRST parsing (first for () loop): so, there is a way to WAIT the complete for () loop to call [UIActivityIndicatorView stopAnimating] (or another method) AFTER the ENTIRE cycle? Thanks!
ALTERNATIVE
Maybe the parsing is so fast that I can't see the UIActivityIndicatorView appearing and loading? In this case, WHERE I must put a sort of timer (or the sleep(unsigned int)) to wait a few seconds before it disappear?
Your problem is that the requests you are making in your loop are asynchronous, so your loop doesn't hang around for them to complete, it just fires off each one in quick succession and your block of code to load your arrays is executed once the data is received.
You could use a counter in your block that gets incremented every time a block finishes and then once the counter matches your [arrayJSON count] you know when to stop the animation. Just remember to use __block storage
http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/Blocks/Articles/bxVariables.html
Solution is to
Simplify the code so that is obvious what is being done there (declaring the blocks beforehand)
Count how many requests are running and perform the global actions only when all the requests are completed.
typedef void (^AFJSONSuccessBlock) (NSURLRequest *request, NSHTTPURLResponse *response, id JSON);
typedef void (^AFJSONFailureBlock) (NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON);
[UIActivityIndicatorView startAnimating];
__block int numRequests = [arrayJSON count];
AFJSONSuccessBlock onSuccess = ^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
[...]
numRequests--;
if (numRequests == 0) {
[UIActivityIndicatorView stopAnimating];
}
};
AFJSONFailureBlock onFailure = ^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
[...]
numRequests--;
if (numRequests == 0) {
[UIActivityIndicatorView stopAnimating];
}
};
for (NSString* jsonPath in arrayJSON) {
NSString* absolutePath = [NSString stringWithFormat:#"http://WWW.REMOTESERVERWITHJSONSOURCE.NET/%#", jsonPath];
NSURL *url = [NSURL URLWithString:absolutePath];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation;
operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:onSuccess
failure:onFailure];
[operation start];
}
I would generally say that if you are unable to determine where a loop ends in your code that's an indication your code is to convoluted. Your for-loop ends just before the else statement I think. To find out you can simply click the starting bracket and XCode (if that's what you're using) will highlight the code-block...

return string from function objective c

I have this function that will get xml through a request operation:
-(id)xmlRequest:(NSString *)xmlurl
{
AFKissXMLRequestOperation *operation = [AFKissXMLRequestOperation XMLDocumentRequestOperationWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:xmlurl]] success:^(NSURLRequest *request, NSHTTPURLResponse *response, DDXMLDocument *XMLDocument) {
NSLog(#"XMLDocument: %#", XMLDocument);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, DDXMLDocument *XMLDocument) {
NSLog(#"Failure!");
}];
[operation start];
return operation;
}
This is my code that calls this function:
Request *http=[[Request alloc] init];
NSString *data=[http xmlRequest:#"http://legalindexes.indoff.com/sitemap.xml"];
NSError *error;
DDXMLDocument *ddDoc=[[DDXMLDocument alloc] initWithXMLString:data options:0 error:&error];
NSArray *xmlItems=[ddDoc nodesForXPath:#"//url" error:&error];
NSMutableArray *returnArray = [[NSMutableArray alloc] initWithCapacity:[xmlItems count]];
for(DDXMLElement* itemElement in xmlItems){
DDXMLElement *element = [[itemElement nodesForXPath:#"loc" error:&error] objectAtIndex:0];
NSLog(#"valueasstring %#", element);
[returnArray addObject:element];
}
I need the xmlRequest to return a string so I can get the XML but the [operation start] creates correct output but I can't put it in a string. How can I direct the output into a string?
In that code, the network request happens asynchronously – there’s no way for you to return its result from that method.
The line NSLog(#"XMLDocument: %#", XMLDocument); is inside the success handler block – that will be called when the request actually finishes. You should replace the log statement with code to save your string somewhere, and only then call the remainder of your code.
There’s a few ways you could do this:
Create a property on the class like #property (strong) DDXMLDocument *XMLDocument;
You can then replace the log statement with self.XMLDocument = XMLDocument;
Then, make another method that does the rest of your processing.
Alternatively, just make another method like -processWithXMLDocument:(DDXMLDocument *)XMLDocument; that you can call from the block, simply passing it as an argument.
I can’t remember what dispatch queue the success handler will be called on, so you may have to be careful to run your code back on the main thread dispatch_async(dispatch_get_main_queue(), ^(){…

Waiting for completion block to complete in an AFNetworking request

I am making a JSON request with AFNetworking and then call [operation waitUntilFinished] to wait on the operation and the success or failure blocks. But, it seems to fall right though - in terms of the log messages, I get "0", "3", "1" instead of "0", "1", "3"
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://google.com"]];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
httpClient.parameterEncoding = AFFormURLParameterEncoding;
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:#"query", #"q", nil];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:[url path] parameters:params];
NSLog(#"0");
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *innerRequest, NSHTTPURLResponse *response, id JSON) {
NSLog(#"1");
gotResponse = YES;
} failure:^(NSURLRequest *innerRequest, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"2");
gotResponse = YES;
}];
NSLog(#"Starting request");
[operation start];
[operation waitUntilFinished];
NSLog(#"3");
This works by using AFNetworking to set up the requests, but making a synchronous call then handling the completion blocks manually. Very simple. AFNetworking doesn't seem to support this https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-FAQ, though the work around is simple enough.
#import "SimpleClient.h"
#import "AFHTTPClient.h"
#import "AFJSONRequestOperation.h"
#import "AFJSONUtilities.h"
#implementation SimpleClient
+ (void) makeRequestTo:(NSString *) urlStr
parameters:(NSDictionary *) params
successCallback:(void (^)(id jsonResponse)) successCallback
errorCallback:(void (^)(NSError * error, NSString *errorMsg)) errorCallback {
NSURLResponse *response = nil;
NSError *error = nil;
NSURL *url = [NSURL URLWithString:urlStr];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
httpClient.parameterEncoding = AFFormURLParameterEncoding;
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST" path:[url path] parameters:params];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if(error) {
errorCallback(error, nil);
} else {
id JSON = AFJSONDecode(data, &error);
successCallback(JSON);
}
}
#end
That should (almost) work. Your call to
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:[url path] parameters:params];
should probably not pass [url path] to the path: parameter. In AFNetworking land, that path is everything after the base url (for example the base url could be "http://google.com" and the path "/gmail" or whatever).
That being said, it's probably not a good idea to make the asynchronous operation into a thread-blocking synchronous operation with waitUntilFinished, but I'm sure you have your reasons... ;)
I just had the same problem and found a different solution. I had two operations that depend on each other, but can load in parallel. However, the completion block of the second operation can not be executed before the completion block of the first one has finished.
As Colin pointed out, it might be a bad choice to make a web request block. This was essential to me, so I did it asynchronously.
This is my solution:
// This is our lock
#interface SomeController () {
NSLock *_dataLock;
}
#end
#implementation
// This is just an example, you might as well trigger both operations in separate
// places if you get the locking right
// This might be called e.g. in awakeFromNib
- (void)someStartpoint {
AFJSONRequestOperation *operation1 = [AFJSONRequestOperation JSONRequestOperationWithRequest:[NSURLRequest requestWithURL:url1]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id data) {
// We're done, we unlock so the next operation can continue its
// completion block
[_dataLock unlock];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id data) {
// The request has failed, so we need to unlock for the next try
[_dataLock unlock];
}];
AFJSONRequestOperation *operation2 = [AFJSONRequestOperation JSONRequestOperationWithRequest:[NSURLRequest requestWithURL:url2]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id data) {
// The completion block (or at least the blocking part must be run in a
// separate thread
[NSThread detachNewThreadSelector:#selector(completionBlockOfOperation2:) toTarget:self withObject:data];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id data) {
// This second operation may fail without affecting the lock
}];
// We need to lock before both operations are started
[_dataLock lock];
// Order does not really matter here
[operation2 start];
[operation1 start];
}
- (void)completionBlockOfOperation2:(id)data {
// We wait for the first operation to finish its completion block
[_dataLock lock];
// It's done, so we can continue
// We need to unlock afterwards, so a next call to one of the operations
// wouldn't deadlock
[_dataLock unlock];
}
#end
Use Delegate method call
Put the method inside block which will call itself when downloading/uploading completes.

AFImageRequestOperation wait until finished

i have a little problem with this, I'm loading an Image from a Url like this:
+ (void)getImageFromURL:(NSString *)imageFilename urlMode:(NSString *)mode block:(id (^)(UIImage *responseImage))aImage {
NSURL *url = [NSURL URLWithString:[mainURL stringByAppendingString:mode]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"GET"];
AFImageRequestOperation *requestOperation = [AFImageRequestOperation imageRequestOperationWithRequest:request
imageProcessingBlock:nil
cacheName:nil
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image)
{
aImage(image);
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error)
{
// manage errors
}];
[[[NSOperationQueue alloc]init] addOperation:requestOperation];
}
I'm trying to set an iVar UIImage *userAvatar to the response from this request, but the problem is, since its an async request I'm not getting the iVar set before my Code moves on, so my iVar is empty when I'm accessing it and passing it to another method.
That's the nature of asynchronous programming! You are going to have to redesign the dependencies on userAvatar to take into account that it's availability is nondeterministic.
So, rather than having your operation's success block simply set the userAvatar ivar, it takes care of whatever needs to happen once that image is available. For example if you want to set a UIImageView's image, then in your success block:
dispatch_async(dispatch_get_main_queue(), ^{
myImageView.image = image;
});
(Without knowing the details of your goals and details of your implementation, this is just a "for example...")
You forgot to add [requestOperation start]; at the end.