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.
Related
I’ve not had much experience with semaphores, nor with blocks. I’ve seen various suggestions for how to turn an asynchronous call into a synchronous one. In this case I just want to wait to be sure the lens of the iPhone has changed focus before I snap another picture.
I’ve added a completion block (with a little routine to prove that I’m seeing it). But how to block the rest of my code (running on the main thread) until I get the completion callback?
- (void) changeFocusSettings
{
if ([SettingsController settings].useFocusSweep)
{
// increment the focus setting
float tmp = [SettingsController settings].fsLensPosition;
float fstmp =[[SettingsController settings] nextLensPosition: [SettingsController settings].fsLensPosition]; // get next lensposition
[SettingsController settings].fsLensPosition = fstmp ;
tmp = [SettingsController settings].fsLensPosition;
if ([self.captureDevice lockForConfiguration: nil] == YES)
{
__weak typeof(self) weakSelf = self;
[self.captureDevice setFocusModeLockedWithLensPosition:tmp
completionHandler:^(CMTime syncTime) {
NSLog(#"focus over..time = %f", CMTimeGetSeconds(syncTime));
[weakSelf focusCompletionHandler : syncTime];
}];
}
}
}
- (bool) focusCompletionHandler : (CMTime)syncTime
{
NSLog(#"focus done, time = %f", CMTimeGetSeconds(syncTime));
return true;
}
changeFocusSettings is called from another routine entirely. I image some kind of semaphore set just inside changeFocusSettings and then the focuscompletionHandler resets it. But the details are beyond me.
Thank you.
I worked through it myself, it wasn't hard at all and it appears to be working. Here's the code in case it helps someone else. And if you happen to spot an error, please let me know.
dispatch_semaphore_t focusSemaphore;
...
- (bool) focusCompletionHandler : (CMTime)syncTime
{
dispatch_semaphore_signal(focusSemaphore);
return true;
}
- (void) changeFocusSettings
{
focusSemaphore = dispatch_semaphore_create(0); // create semaphone to wait for focuschange to complete
if ([SettingsController settings].useFocusSweep)
{
// increment the fsLensposition
float tmp = [SettingsController settings].fsLensPosition;
float fstmp =[[SettingsController settings] nextLensPosition: [SettingsController settings].fsLensPosition]; // get next lensposition
[SettingsController settings].fsLensPosition = fstmp ;
tmp = [SettingsController settings].fsLensPosition;
NSLog(#"focus setting = %f and = %f", tmp, fstmp);
if ([self.captureDevice lockForConfiguration: nil] == YES)
{
__weak typeof(self) weakSelf = self;
[self.captureDevice setFocusModeLockedWithLensPosition:tmp
completionHandler:^(CMTime syncTime) {
[weakSelf focusCompletionHandler : syncTime];
}];
dispatch_semaphore_wait(focusSemaphore, DISPATCH_TIME_FOREVER);
}
}
}
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.
Overview : I am using Amazon DynamoDB for my login service. And my login method looks like this in some UserAccount.m. And I am calling this class method in some LoginViewController.m on login button tap:
+ (BOOL)loginWithUsername:(NSString *)username password:(NSString *)password{
AWSDynamoDBObjectMapper *dynamoDBObjectMapper = [AWSDynamoDBObjectMapper defaultDynamoDBObjectMapper];
BOOL __block isLoginSuccessful = NO;
[[dynamoDBObjectMapper load:[User class] hashKey:username rangeKey:#"key"]
continueWithBlock:^id(AWSTask *task) {
if (task.error) {
NSLog(#"The request failed. Error: [%#]", task.error);
}
if (task.exception) {
NSLog(#"The request failed. Exception: [%#]", task.exception);
}
if (task.result) {
//Do something with the result.
User *user = task.result; // User is a model I'm using
NSLog(#"pass: %#", user.password);
// Check returned password from DynamoDB with user-supplied password.
if ([user.password isEqualToString:password]) {
isLoginSuccessful = YES;
}
}
return nil;
}];
return isLoginSuccessful; // <-- Issue: function returns before block executes and isLoginSuccessful value is changed.
}
The issue is function returns before block executes. What I tried:
i) I read up about using dispatch groups on this SO question
ii) Tried to execute method - (AWSTask *)load:(Class)resultClass hashKey:(id)hashKey rangeKey:(id)rangeKey on main thread like this but still function returns before block execution finishes.:
dispatch_async(dispatch_get_main_queue(), ^{
[[dynamoDBObjectMapper load:[User class]
hashKey:username
rangeKey:#"key"] continueWithExecutor:[AWSExecutor mainThreadExecutor] withBlock:^id(AWSTask *task) {
if (!task.error) {
User *user = task.result;
NSLog(#"pass: %#", user.password);
//Do something with the result.
if ([user.password isEqualToString:password]) {
isLoginSuccessful = YES;
}
} else {
NSLog(#"Error: [%#]", task.error);
}
return nil;
}];
});
Am I missing out something here/ doing wrong in my approach? Any suggestion towards right direction would be really helpful. Thank you!
Edit 1: Including function from where loginWithUsername class method is being called:
#IBAction func login(sender: AnyObject) {
if(UserAccount.loginWithUsername(txtEmail.text, password: txtPassword.text)){
println("SUCCESS")
lblIncorrect.hidden = true
}
else{
println("Incorrect username/password")
lblIncorrect.hidden = false
}
}
It is the basic idea of GCD that blocks are executed "in background", so the control flow can return to the sender of the message before the task is finished. This is, because the task is a potential long runner and you do not want to block the sending control flow, esp. if it is the main thread.
If you want to execute code after finishing the operation, simply add it to the block. This is the code inside login(): Add the if to the block and remove the block variable and the return value. Make the method void.
To have a general pattern:
-(IBAction)doSomething:(id)sender
{
[self executeOperation]; // Method becomes void
// Remove code processing on result
}
-(void)executeOperation // Method becomes void
{
[receiver longRunnerWithBlock:^(id result)
{
…
// Add code processing on result
}
}
Please take care, that the block is potentially run on a different thread than the main thread, so you have to dispatch it on the main thread, if it is UI related.
OR you could just take a page from Googles iOS framework code and do it the easy way like this:
- (void)runSigninThenInvokeSelector:(SEL)signInDoneSel {
if (signInDoneSel) {
[self performSelector:signInDoneSel];
}
}
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];
I'm trying to use ReactiveCocoa to control binding and validation on a text field in my app. When I subscribe to a signal, it immediately does the binding from the text field to the model and runs the validation. Normally that wouldn't be an issue, but in this case the field is a 'password' input where the initial value from the model does not get copied to the text field. I want the binding and validation to trigger ONLY when the user actually types something in the field. Does anyone know of a way to do this?
Here is what I'm doing currently:
- (void)setUpBindings: forModel:(NSObject<ValidationModel> *)model {
NSString *property = #"password"
NSInteger throttleTime = 1.5;
[[[self.textField.rac_textSignal distinctUntilChanged]
throttle:throttleTime]
subscribeNext:^(id x) {
NSLog([NSString stringWithFormat:#"Model: %#, Value: %#", [model valueForKey:property], x]);
[model setValue:x forKey:property];
}];
[self bindValidator:[model.validators objectForKey:property]];
}
- (RACSignal *) passwordIsValid {
#weakify(self);
return [[RACObserve(self,password) distinctUntilChanged]
map:^id (NSString *newPassword) {
#strongify(self);
NSArray *errors = [self validatePassword];
return errors;
}];
}
-(void)bindValidator:(RACSignal *)validator
{
if(validator != nil)
{
[[[validator doNext:^(NSArray *errors) {
if(errors.count > 0)
{
NSError *error = [errors firstObject];
self.errorString =error.localizedDescription;
}
else
{
self.errorString = #"";
}
}]
map:^id(NSArray *errors) {
return errors.count <=0 ? #(1) : nil;
}] subscribeNext:^(id x) {
self.isValid = !!x;
}];
}
}
I have found a possible solution. ReactiveCocoa has a rac_signalForControlEvents method that lets you manually specify the event to observe. Using that, I was able to define an initial signal for the UIControlEventEditingChanged event of my text field. I then moved the setup for my binding and validation signals inside the subscribeNext, delaying their subscription until a change event is sent from the text field. My setup method from the OP would look like this:
- (void)setUpBindings:(NSString *)property forModel:(NSObject<ValidationModel> *)model {
NSInteger throttleTime = 1.5;
RACSignal *textChangeSignal = [[self.textField.rac_textSignal distinctUntilChanged]
throttle:throttleTime];
//wait to subscribe to the signal until the user actually makes changes to the field.
//the 'take:1' call ensures that the subscription only happens the first time the event
//is observed.
[[[self.textField rac_signalForControlEvents:UIControlEventEditingChanged]
take:1]
subscribeNext:^(id x) {
[self bindValidator:[model.validators objectForKey:self.reuseIdentifier]];
[textChangeSignal subscribeNext:^(id x) {
[model setValue:x forKey:property];
}];
}];
}
IMPORTANT: Notice the 'take:1' method in the outer chain. You MUST include this. Without that call, the outer 'subscribeNext' will run every time the editing event is fired, resulting in multiple subscribers for the same target and event. For more info see: How do I create a ReactiveCocoa subscriber that receives a signal only once, then unsubscribes/releases itself?
I'm going to leave this as open for now. This way works, but I am sure there must be a cleaner way of doing this.
You can use something like this :
#weakify(self);
RACSignal *validPasswordSignal = [self.passwordTextField.rac_textSignal map:^id(NSString *text) {
#strongify(self);
return #([self isValidPassword:text]);
}];
- (BOOL)isValidPassword:(NSString *)password
{
return ([password length] > 0);
}
You can change conditions in isValidPassword to anything you want to.