I am facing something I cannot sort out. I am downloading json data and instantiating Core Data objects with the returned value (inside a dispatch_async(get_main_queue)). I try to present an other view (with tableView containing these objects) after everything is completed but my tableviewcontrollers are not called. However everything works fine if I present my viewController from a method outside this block (which is inside connectionDidFinishing NSURLConnection).
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
id jsonObject = [NSJSONSerialization JSONObjectWithData:_downloadedData options:NSJSONReadingAllowFragments error:&error];
if ([jsonObject isKindOfClass:[NSArray class]]) {
NSArray *deserializedArray = (NSArray *)jsonObject;
if (deserializedArray.count > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
for (NSDictionary *jsonElement in deserializedArray)
{
// Create a new location object and set its props to JsonElement properties
Patient *syncPatient = [[PatientStore sharedStore] createPatient];
syncPatient.firstName = jsonElement[#"firstname"];
syncPatient.lastName = jsonElement[#"lastname"];
}
});
[self login]; // --> does not work.
}
}
}
[self login] does not work. But it works if I call it from outside the didFinishLoading method.
It has to do I think with the dispatch_async() but I don't know how to call this method automatically from outside (for example with a "I have finish" notification).
Does anyone could help?
Thanks a lot!
UPDATE
It does not work either inside the block. It looks like [self login] happened before the for loop instructions.
if (deserializedArray.count > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
for (NSDictionary *jsonElement in deserializedArray)
{
// Create a new location object and set its props to JsonElement properties
Patient *syncPatient = [[PatientStore sharedStore] createPatient];
syncPatient.firstName = jsonElement[#"firstname"];
syncPatient.lastName = jsonElement[#"lastname"];
}
[self login]; // --> does not work either here. As if it was executed before what is inside the for loop.
});
}
}
}
[self login] is executed immediately after the core data save starts because it is outside the block. If you put it inside, it will execute after the loop finishes.
You can test this with a simple command line project, which this content in main.swift:
import Foundation
dispatch_async(dispatch_get_main_queue()) {
var j = 0
for _ in 1...1000 { j++ }
println("The value of j is \(j).")
}
dispatch_main()
Output: The value of j is 1000.
The println is clearly executed after the loop.
After creating your objects, do not forget to save:
[managedObjectContext save:nil];
Related
I've the following code below (example code) that sends an API GET request multiple times.
- (void)listOfPeople:(NSArray *)array {
for (int i = 0; i < array.count; i++) {
Person *person = [array objectAtIndex:i];
[personClient getPersonData:person.fullName onSuccess:^(id result) {
// change data here
} onFailure:^(NSError *error) {
}];
}
}
The code doesn't work very well because the API requests finishes in a different order every time. I need to complete each api request in order. I believe I need to wait until either the completion block or the failure block is finished before continuing the for loop. Can someone point me in the right direction unless there is a better way to accomplish this task. I've tried dispatch group, but it didn't complete each request in order.
Get rid of the for loop, and instead make a recursive function that calls itself from the completion handler to get the next Person. That way when each call completes, it will make the call to get the next one.
Something like this:
- (void)getPersonFromArray:(NSArray *)array atIdx:(NSInteger)idx {
if (idx < array.count)
{
Person *person = [array objectAtIndex:idx];
[personClient getPersonData:person.fullName onSuccess:^(id result)
{
// Do something useful with Person here...
// ...
[self getPersonFromArray:array atIdx(idx + 1)];
} onFailure:^(NSError *error) {
// Handle errors here
// ...
}];
}
}
I have an object like this:
typedef void (^ Completion) (Response *);
// Response class
#interface Response : NSObject {
NSDictionary * kdata;
}
- (id)initWithJson:(NSDictionary *)data;
#property (nonatomic, assign) NSDictionary * data;
#end
#implementation Response
- (id)initWithJson:(NSDictionary *)data { kdata = data; }
- (NSDictionary *) data { return kdata; }
- (void) setData: (NSDictionary *)data { kdata = data; }
- (NSDictionary *) msg { return kdata[#"msg"]; }
#end
// inside a networking class X implementation
- (void) doSomething:(completionBlock)completion {
NSDictionary * json = // get from networking function, which will always have key "msg".
Response * responseObj = [[Response alloc] initWithJson:json];
dispatch_async(dispatch_get_main_queue(), ^{
if (completion != nil) { completion (responseObj); }
});
}
// inside caller method
[X doSomething:^(Response * response) {
NSLog (#"%#", [response msg]);
}
This code will raise error on accessing kdata[#"msg"], even though I'm sure from the debug that the object was initialised properly with a dictionary contains key "msg". When I debug the object, on the watch window, it shows me that the kdata data type keeps changing, from NSArrayM, NSSet, NSDictionary, etc. And its contents also keep changing. I even add retain keyword when calling completion ([responseObj retain]); but still produce error.
But if the code in class X is changed into like this:
// inside a networking class X implementation
- (void) doSomething:(completionBlock)completion {
NSDictionary * json = // get from networking function, which will always have key "msg".
Response * responseObj = [[Response alloc] initWithJson:json];
if (completion != nil) { completion (responseObj); } // here is the change, no more switching to main thread
}
// inside caller method - no change here
[X doSomething:^(Response * response) {
NSLog (#"%#", [response msg]);
}
The code works perfectly. Why is that happened? This is built in Xcode without ARC.
EDIT: someone mentioned about the init. This is my mistake that what was written above is not exactly my code, and I copy the init method wrong. This is my init method:
- (instancetype) initWithData:(NSDictionary *)freshData {
NSParameterAssert(freshData); // make sure not nil
self = [super init];
if (self) {
kdata = freshData;
}
return self;
}
The problem is the object get's released right when you call the 'async' .
The way you declared your object is added to the autorelease pool since the control does not wait for 'async' to complete and the control return's by reaching the end of function 'doSomething' and releasing it's local objects which were added to the autorelease pool, and after that the memory location is used for other data and that's what you see confusing data.
I think by adding the __block specifier in front of your declaration you instruct the code to capture this object in following blocks strongly and release it when the block finished executing. Give it a try.
// inside a networking class X implementation
- (void) doSomething:(completionBlock)completion {
NSDictionary * json = // get from networking function, which will always have key "msg".
__block Response * responseObj = [[Response alloc] initWithJson:json];
dispatch_async(dispatch_get_main_queue(), ^{
if (completion != nil) { completion (responseObj); }
});
}
- (id)initWithJson:(NSDictionary *)data { kdata = data; }
You need call supers init here and return self.
Start to learn basics.
I have this code:
- (NSString *) login {
datos=#"";
NSString __block *variable;
NSString *sqlQueryExisteUsuario;
sqlQueryExisteUsuario = [[NSString alloc] initWithFormat:#"SELECT COUNT(*) FROM tableName WHERE field='value' AND field='value'"];
SQLClient* client = [SQLClient sharedInstance];
client.delegate = self;
[client connect:#"serverName" username:#"username" password:#"password" database:#"database" completion:^(BOOL success) {
[client execute:sqlQueryExisteUsuario completion:^(NSArray* results) {
variable = [self processLogin:results];
NSLog(#"In: %#",variable);
[client disconnect];
}];
}];
NSLog(#"Out: %#",variable);
return nil;
}
- (NSString *)processLogin:(NSArray*)data
{
existeArray = [NSMutableArray array];
for (NSArray* table in data)
for (NSDictionary* row in table)
for (NSString* column in row)
[existeArray addObject:row[column]];
NSString *existe=existeArray[0];
if([existe isEqualToString:#"1"])
{
datos=#"yes";
}else{
datos=#"no";
}
return datos;
}
In the first call to NSLog, which begins with In, the value shows. In the second call, which begins with Out, the value doesn't show. Why?
Your connect is async method, so NSLog... line will be executed earlier than completion block. So, you have to use blocks also:
- (NSString *) loginWithCompletion:(void(^)(NSString *result))handler
{
datos=#"";
NSString *sqlQueryExisteUsuario;
sqlQueryExisteUsuario = [[NSString alloc] initWithFormat:#"SELECT COUNT(*) FROM tableName WHERE field='value' AND field='value'"];
SQLClient* client = [SQLClient sharedInstance];
client.delegate = self;
[client connect:#"serverName" username:#"username" password:#"password" database:#"database" completion:^(BOOL success) {
if (success) {
[client execute:sqlQueryExisteUsuario completion:^(NSArray* results) {
NSString *variable = [self processLogin:results];
NSLog(#"In: %#",variable);
[client disconnect];
if (handler) {
handler (variable);
}
}];
} else {
//TODO: handle this
if (handler) {
handler (nil);
}
}
}];
}
Usage:
- (void)ff
{
[self loginWithCompletion:^(NSString *variable) {
//Do something with variable
}];
}
The problem is that your variable is being set inside a completion block: the variable variable (not a great name, BTW!) is set inside not only one but two blocks - that's the "completion" part of your code – both of which are best thought of a bit(!) like a miniature anonymous function: in this case, they get run when the system is ready for it, not straight away.
iOS will start execution of your connect code, then jump ahead to NSLog(#"Out: %#",variable), then execute the completion block of connect, which in turn starts more code (execute), which has its own completion block, which finally gets executed. As #rmaddy says in a comment below, the technical name for this is asynchronous code: the bit inside your block does not get executed immediately, which allows the system to continue doing other things while waiting for your task to complete.
So the running order will be:
1) You declare variable.
2) Your connection code starts.
3) variable gets printed out.
4) The connection completes.
5) Your execute code starts.
6) Your execute code completes.
7) variable gets set to the final value.
I have a block where I am checking a user's status property from firebase. If the status property is 'free' I want to return from the block, otherwise I want to search for another user and check their status and do so until a 'free' user has been found:
void( ^ myResponseBlock)(BOOL finished) = ^ void(BOOL finished) {
if (finished) {
if ([self.freedom isEqualToString: #"free"]) {
NSLog(#"free!");
return;
} else if ([self.freedom isEqualToString: #"matched"]) {
NSLog(#"get another user");
//get another user
do {
//picking another random user from array
rando = arc4random_uniform(arraycount);
}
while (rando == randomIndex && rando == [self.randString intValue]);
self.randString = [NSString stringWithFormat: #"%u", rando];
[users removeAllObjects];
[users addObject:usersArray[rando]];
self.freeUser = users.firstObject;
NSLog(#"set up another check");
//error is called after this block is called here, again
[self checkIfFree: myResponseBlock];
} else {
NSLog(#"error!");
}
} else {
NSLog(#"not finished the checking yet");
}
};
[self checkIfFree: myResponseBlock];
As shown, I'm getting an error of 'BAD ACCESS' when the block is called for a second time on the 'compblock(YES)' line below:
-(void)checkIfFree:(myCompletion) compblock{
self.freeUserFB = [[Firebase alloc] initWithUrl:[NSString stringWithFormat: #"https://skipchat.firebaseio.com/users/%#", self.freeUser.objectId]];
[self.freeUserFB observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot)
{
self.otherStatus = snapshot.value[#"status"];
NSLog(#"snapshot info %#", snapshot.value);
if ([self.otherStatus isEqualToString:#"free"]) {
self.userIsFree = YES;
self.freedom = #"free";
NSLog(#"user is free in the check method %#", self.freedom);
}
else{
self.userIsFree = NO;
self.freedom = #"matched";
NSLog(#"user is matched in the check method %#", self.freedom);
}
compblock(YES);
}];
}
Everything is fine if the block does not have to be recalled and the first user that's checked is already 'free'. I'm stuck as to why I'm getting this error/crash and wondering how I can solve it!
Thanks!
A block captures all variables passed in including itself, however the variable myResponseBlock has not been initialized yet inside the block. Because of this, you are calling checkIfFree method with a nil value which in turn causing app to crash.
One thing you can do to overcome this would be declaring your block as a __block variable.
__block __weak void(^weakResponseBlock)(BOOL);
void(^myResponseBlock)(BOOL);
weakResponseBlock = myResponseBlock = ^void(BOOL finished) {
...
if (weakResponseBlock) {
[self checkIfFree:weakResponseBlock];
}
}
Additionally, please note that blocks retain all variables passed into them. In this case, you are retaining self inside the block, so it will never get deallocated as long as block executes. So unless required otherwise, always pass a weak reference to blocks.
__weak UIViewController *weakSelf = self;
weakResponseBlock = myResponseBlock = ^void(BOOL finished) {
...
if (weakResponseBlock) {
[weakSelf checkIfFree:weakResponseBlock];
}
}
I think still you might have an error because all your blocks are being created on the stack. So if anything async should happen the myResponseBlock will go away.
What I'd recommend is your copy (using the a property with copy attribute) your myResponse block into a property and reuse it from there. That way your block lives on the heap and goes away when self is set to nil.
I have a method that should return an array which is populated in a background thread. I would like to wait with the return statement until the array is completely populated. How would I do that ? I know how to process data with no return type, but I would like to call this function and get the populated array.
Example (Here the array ends up empty because it returns before array is populated - I would like to do something to return the populated array) :
-(NSArray*)fetchSomeArray{
__block NSArray *arrayToReturn;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,(unsigned long)NULL), ^(void) {
NSArray *someArray = ...; // getting some array from database
arrayToReturn = [NSMutableArray new];
for(int i=0;i<someArray.count;i++){
[arrayToReturn addObject:...];
}
});
return arrayToReturn;
}
Use delegation, or blocks and take out the return arrayToReturn;. After your "for" and inside the "dispatch_async":
dispatch_async(dispatch_get_main_queue(),^{
[myDelegate passComputedArray:arrayToReturn];
});
- (void)someMethod:(void (^)(BOOL result))completionHandler {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//all your functionality
// Check that there was not a nil handler passed.
if( completionHandler ){
//your function to so that you can return
}
});
});
Pass the completionHandler and once done, do your function
If you want to wait until it returns, it is unnecessary to background it.
-(NSArray*)fetchSomeArray{
NSArray *someArray = ...; // getting some array from database
return someArray;
}
But I'll bet that you really want to do something with that returned value which takes some time to compute without blocking the main thread, so you can instead pass the array back to a method in the same object or elsewhere.
-(void)fetchSomeArray{
__block id bself = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,(unsigned long)NULL), ^(void) {
NSArray *someArray = ...; // getting some array from database
[bself arrayFetched:someArray];
});
return arrayToReturn;
}
-(void)arrayFetched:(NSArray*)array{
// do something with the returned array
}
The easiest way to deal with this would be to call a function once the array is complete and process the array there.
-(void)fetchSomeArray{
__block NSArray *arrayToReturn;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,(unsigned long)NULL), ^(void) {
NSArray *someArray = ...; // getting some array from database
arrayToReturn = [NSMutableArray new];
for(int i=0;i<someArray.count;i++){
[arrayToReturn addObject:...];
}
[self finishedArrayResult:arrayToReturn];
});
}
But if you wanted that return function to update anything n the UI you would need to run that function in the main thread and not the background. To do that you could either use performSelector: onThread: withObject: waitUntilDone: or you could use another dispatch_async using the main thread instead of global thread.