basically I'm using firebase to query a user's 'status' property and doing so in a do/while loop.
If the status property is free then i want to break the loop and continue with the rest of the method. If the status property is not free then I want to query firebase again for a new user until a free user is found.
My firebase code works fine outside the loop but doesn't seem to be called inside of it. Here is the loop:
__block uint32_t rando;
self.freedom = #"about to check";
do {
//check if free
[self checkIfFree:^(BOOL finished) {
if (finished) {
if ([self.freedom isEqualToString:#"free"]) {
//should break loop here
}
else if ([self.freedom isEqualToString:#"matched"]){
//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 objectAtIndex:rando]];
self.freeUser = [users objectAtIndex:0];
//should repeat check here but doesn't work
}
else{
NSLog(#"error!");
}
}
else{
NSLog(#"not finished the checking yet");
}
}];
} while (![self.freedom isEqual: #"free"]);
And here's my firebase code:
-(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";
}
else{
self.userIsFree = NO;
self.freedom = #"matched";
}
compblock(YES);
}];
}
Thanks!
I am not sure I understand correctly your question.
If you want to run your completion code again till some condition is matched (in this case [self.freedom isEqualToString:#"free"]) you can do the following (removing the do - while):
void( ^ myResponseBlock)(BOOL finished) = ^ void(BOOL finished) {
if (finished) {
if ([self.freedom isEqualToString: #"free"]) {
return;
} else if ([self.freedom isEqualToString: #"matched"]) {
//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;
// Schedule another async check
[self checkIfFree: myResponseBlock];
} else {
NSLog(#"error!");
}
} else {
NSLog(#"not finished the checking yet");
}
};
[self checkIfFree: myResponseBlock];
Related
This is my Sign In button :
- (IBAction)signInButtonAction:(id)sender
{
if ([self.phoneNumberTextField.text length] == 0 || [self.passwordTextField.text length] == 0) {
[self initializeAlertControllerForOneButtonWithTitle:#"Alert!" withMessage:kEmptyTextFieldAlertMSG withYesButtonTitle:#"Ok" withYesButtonAction:nil];
} else {
if ([self alreadyRegisteredPhoneNumber] == YES) {
if ([self.password isEqualToString:self.passwordTextField.text]) {
[self goToHomeViewController];
} else {
[self initializeAlertControllerForOneButtonWithTitle:#"Wrong Password!" withMessage:kWrongPasswordMSG withYesButtonTitle:#"Ok" withYesButtonAction:nil];
}
} else {
[self initializeAlertControllerForOneButtonWithTitle:#"Alert!" withMessage:kSignInPhoneNumberDoesnotExistMSG withYesButtonTitle:#"Ok" withYesButtonAction:nil];
}
}
}
And this is my alreadyRegisteredPhoneNumber method, where I am just checking if the given phone number is registered or not.
- (BOOL)alreadyRegisteredPhoneNumber
{
[self.activityIndicator startAnimating];
__block BOOL isThisPhoneNumberRegistered = NO;
BlockWeakSelf weakSelf = self;
SignInViewController *strongSelf = weakSelf;
dispatch_sync(dispatch_queue_create("mySyncQueue", NULL), ^{
NSString *PhoneNumberWithIsoCountryCode = [NSString stringWithFormat:#"%#%#", strongSelf.countryCode, strongSelf.phoneNumberTextField.text];
strongSelf.userModelClass = [[RetrieveDataClass class] retrieveUserInfoForPhoneNumber:PhoneNumberWithIsoCountryCode];
if ([strongSelf.userModelClass.phone_number length] != 0) {
strongSelf.phoneNumber = strongSelf.userModelClass.phone_number;
strongSelf.password = strongSelf.userModelClass.password;
isThisPhoneNumberRegistered = YES;
[strongSelf.activityIndicator stopAnimating];
}
});
return isThisPhoneNumberRegistered;
}
With this method the problem is that the activityIndicator is not appearing when I press Sign In button. Otherwise it works synchronize way, which is perfect.
The reason activityIndicator is not appearing is that I have to update UI related change in dispatch_async(dispatch_get_main_queue() (Or not??). But If I rearrange my code like this :
- (BOOL)alreadyRegisteredPhoneNumber
{
[self.activityIndicator startAnimating];
__block BOOL isThisPhoneNumberRegistered = NO;
BlockWeakSelf weakSelf = self;
SignInViewController *strongSelf = weakSelf;
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_sync(dispatch_queue_create("mySyncQueue", NULL), ^{
NSString *PhoneNumberWithIsoCountryCode = [NSString stringWithFormat:#"%#%#", strongSelf.countryCode, strongSelf.phoneNumberTextField.text];
strongSelf.userModelClass = [[RetrieveDataClass class] retrieveUserInfoForPhoneNumber:PhoneNumberWithIsoCountryCode];
if ([strongSelf.userModelClass.phone_number length] != 0) {
strongSelf.phoneNumber = strongSelf.userModelClass.phone_number;
strongSelf.password = strongSelf.userModelClass.password;
isThisPhoneNumberRegistered = YES;
[strongSelf.activityIndicator stopAnimating];
}
});
});
return isThisPhoneNumberRegistered;
}
The activityIndicator is showing perfectly, but it always return isThisPhoneNumberRegistered = NO, because it works asynchronize way, where my update isThisPhoneNumberRegistered = YES return lately.
So If I want, this scenario where:
User press Sign In button
Then the activityIndicator shows up
My server call (strongSelf.userModelClass = [[RetrieveDataClass class] retrieveUserInfoForPhoneNumber:PhoneNumberWithIsoCountryCode];) retrieve data and set isThisPhoneNumberRegistered = YES or isThisPhoneNumberRegistered = NO according to it
Then return the value to If else condition
if ([self alreadyRegisteredPhoneNumber] == YES) {
if ([self.password isEqualToString:self.passwordTextField.text]) {
[self goToHomeViewController];
} else {
[self initializeAlertControllerForOneButtonWithTitle:#"Wrong Password!" withMessage:kWrongPasswordMSG withYesButtonTitle:#"Ok" withYesButtonAction:nil];
}
How can I do it?
A lot of Thanks in advance.
In general, you want to use Asynch processes so you can "do other stuff" such as updating the UI to let the user know something is going on.
In order to do that, you need to separate your logic. Try to think of it in these terms:
signInButtonAction {
if no phone or password entered
show alert
else
showAnimatedSpinner()
checkForRegisteredPhone()
}
checkForRegisteredPhone {
// start your async check phone number process
// on return from the async call,
if !registered
hide spinner
show kSignInPhoneNumberDoesnotExistMSG message
else
checkForMatchingPassword()
}
checkForMatchingPassword {
if !passwordsMatch
hide spinner
show "passwords don't match" message
else
hide spinner
goToHomeViewController
}
In other words, don't write your code so it gets "locked into" a process, preventing anything else from happening.
This below code is fired when I press delete button after selecting messages I want to delete in chat room.
- (void)deleteButtonPressed:(id)sender {
if (arrayToDelete.count) {
for (NSString *str in arrayToDelete) {
NSLog(#"msgID --> %#",str);
[self.chatModel.dataSource removeObject:str]; //??? Remove data from the screen
[[FMDBManager sharedInstance] deleteMessageByMessageId:str]; //??? Delete data from database
}
[arrayToDelete removeAllObjects];
[self.chatTableView reloadData];
}
}
This line successfully removes selected messages from the chat room.
[self.chatModel.dataSource removeObject:str]; //??? Remove data from the screen
When I go out the chat room and re-enter, those messages still exist, so I have this line below.
[[FMDBManager sharedInstance] deleteMessageByMessageId:str]; //??? Delete data from database
I think the above line should delete those selected messages from the database but when I re-enter the chat room I still see those messages. Here below are related code to that.
- (void)deleteMessageByMessageId:(NSString *)messageId {
FMDatabase *db = [self getterDataBase];
[db open];
NSString *sqlString = [NSString stringWithFormat:#"DELETE FROM message WHERE messageId = '%#'",messageId];
BOOL status = [db executeUpdate:sqlString];
NSLog(#"Delete MessageById:%# Status:%d",messageId,status);
[db close];
}
I've found that when chat room calls viewDidLoad it will eventually call the method callBackGetChannelLogNew where server will sync-up data with chat room tableview and local database.
- (void)callBackGetChannelLogNew:(NSDictionary *)resultDataDic status:(enumAPI_STATUS)eAPI_STATUS {
if (isFirstTimeUpdate) {
}
if (eAPI_STATUS == API_STATUS_SUCCEE) {
NSString *readString=[NSString stringWithFormat:#"%#",resultDataDic[#"read_arr"]];
if ([readString isEqualToString:#""]) {
// NSLog(#"read_arr is empty");
}
else {
NSArray *read_arr=resultDataDic[#"read_arr"];
// Copy read_arr
self.readArray=[read_arr mutableCopy];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
[self dealWithReadArray:read_arr];
});
}
NSArray *data = [resultDataDic objectForKey:#"msg"];
if (data.count > 0) {
apiHaveData = YES;
} else {
apiHaveData = NO;
self.loadIngView.hidden = YES;
isLoadingData = NO;
return;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
// Reverse order of data
NSArray* reversedArray = [[data reverseObjectEnumerator] allObjects];
NSMutableArray *messageFromOtherArray = [NSMutableArray new];
NSMutableArray *messageAllArray = [NSMutableArray new];
for (int i = 0; i < reversedArray.count; i++) {
NSDictionary *_dic = reversedArray[i];
NSString *fromId = [_dic objectForKey:#"fid"];
NSString *message = [NSString stringWithFormat:#"%#",[_dic objectForKey:#"say"]];
if ([ObjectManager getChatMessageKindWithString:message] == MessageTypeText) {
message = [ObjectManager decryptWithString:message];
}
NSString *messageId = [_dic objectForKey:#"mid"];
NSString *toId = [_dic objectForKey:#"tid"];
NSDateFormatter *_formatter = [[NSDateFormatter alloc] init];
_formatter.dateFormat = #"yyyy-MM-dd HH:mm:ss.SSS";
NSDate *date_t = [NSDate dateWithTimeIntervalSince1970:[[_dic objectForKey:#"t"] doubleValue]/1000.0]; //換算成日期
NSString *stringDate = [_formatter stringFromDate:date_t];
NSString *sendDate = stringDate;
NSString *lid = _dic[#"lid"];
NSMutableDictionary *myDic = [NSMutableDictionary dictionaryWithObjectsAndKeys:
fromId,#"fromId",
message,#"message",
messageId,#"messageId",
sendDate,#"sendDate",
toId,#"toId",
lid,#"lid",
nil];
NSString *isRead;
if (_chatRoomType == ChatRoomTypePrivate) {
if ([_dic[#"r"] intValue]) {
isRead = #"1";
myDic[#"isRead"] = isRead;
lastReadMessageId = [NSString stringWithFormat:#"%#",messageId];
}
}
if (i == 0) {
if (lidForAPI != [_dic[#"lid"] intValue]) {
lidForAPI = [_dic[#"lid"] intValue];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
apiHaveData = NO;
self.loadIngView.hidden = YES;
isLoadingData = NO;
});
return ;
}
}
if (![myDic[#"fromId"] isEqualToString:[User sharedUser].account]) {
[messageFromOtherArray addObject:myDic];
}
if (_chatRoomType == ChatRoomTypeGroup) {
[myDic setObject:#"1" forKey:#"isGroupMessage"];
}
[myDic setObject:#"1" forKey:#"did_I_Read"];
[messageAllArray addObject:myDic];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self setupViewWithMessageArray:messageAllArray]; //???? Here server sync-up data with tableview
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
if (_chatRoomType == ChatRoomTypePrivate) {
if (messageFromOtherArray.count > 0 && isUplaodLastRead == NO) {
isUplaodLastRead = YES;
NSDictionary *lastReadMsgDic = messageFromOtherArray.lastObject;
[self callMsgReadAPI:lastReadMsgDic];
}
} else {
if (messageAllArray.count > 0 && isUplaodLastRead == NO) {
isUplaodLastRead = YES;
NSDictionary *lastReadMsgDic = messageAllArray.lastObject;
[self callMsgReadAPI:lastReadMsgDic];
}
}
self.chatModel.channelTopic = _topic;
NSArray *read_arr=resultDataDic[#"read_arr"];
[self dealMySendMessageReadedWithReadArray:read_arr AndMessageArray:messageAllArray];
[self saveMessageWithArray:messageAllArray]; //???? Here server sync-up data with local db
});
});
}
}
This lines will sync-up data from server to tableview
dispatch_async(dispatch_get_main_queue(), ^{
[self setupViewWithMessageArray:messageAllArray]; //???? Here server sync-up data with tableview
});
Here below is the method setupViewWithMessageArray
- (void)setupViewWithMessageArray:(NSArray *)messageAllArray {
if (!isFirstTimeUpdate) {
isFirstTimeUpdate = YES;
self.chatModel.dataSource = nil;
[self.chatTableView reloadData];
self.chatModel.dataSource = [[NSMutableArray alloc] init];
[self addMessageWithArray:messageAllArray];
[self.chatTableView reloadData];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.chatModel.dataSource.count-1 inSection:0];
[self.chatTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];
} else {
[self addMessageWithArray:messageAllArray];
[self reloadTableViewWithoutMove];
}
self.loadIngView.hidden = YES;
isLoadingData = NO;
if (_chatRoomType == ChatRoomTypePrivate) {
if (lastReadMessageId) {
[self.chatModel setPrivateChatListAllReadFormMessageId:lastReadMessageId];
}
}
}
This line will sync-up data from server to local db
[self saveMessageWithArray:messageAllArray]; //???? Here server sync-up data with local db
Here below is the method saveMessageWithArray
- (void)saveMessageWithArray:(NSArray *)messageArray {
for (NSDictionary *myDic in messageArray) {
if (![[FMDBManager sharedInstance] didMessageExistWithMessageID:[myDic objectForKey:#"messageId"]]) {
[[FMDBManager sharedInstance] SaveMessage:myDic];
}
else {
NSString *mid=[NSString stringWithFormat:#"%#",myDic[#"messageId"]];
NSString *isRead = myDic[#"isReaed"];
if (isRead) {
[[FMDBManager sharedInstance] UpdateisReadWithMessageID:mid];
}
}
}
}
So I think now my question is how I can update messageAllArray with arrayToDelete before server sync-up?
So I have successfully turned a button into an off and on switch that changes the label.
I was also able to have it start a timed processed set off when that is to occur, and it have the ability to shut off the timed process.
Anyways I need to way to shut down the timed process I was wondering if there was a way to stop it without using the disposable. With a second takeUntil signal.
Edit I think what I was trying to do was slightly misleading let me show my current solution that works.
-(RACSignal*) startTimer {
return [[RACSignal interval:1.0
onScheduler:[RACScheduler mainThreadScheduler]]
startWith:[NSDate date]];
}
-(void) viewWillAppear:(BOOL)animated {}
-(void) viewDidLoad {
self.tableView.delegate = self;
self.tableView.dataSource = self;
RACSignal* pressedStart = [self.start rac_signalForControlEvents:UIControlEventTouchUpInside];
#weakify(self);
RACSignal* textChangeSignal = [pressedStart map:^id(id value) {
#strongify(self);
return [self.start.titleLabel.text isEqualToString:#"Start"] ? #"Stop" : #"Start";
}];
// Changes the title
[textChangeSignal subscribeNext:^(NSString* text) {
#strongify(self);
[self.start setTitle:text forState:UIControlStateNormal];
}];
RACSignal* switchSignal = [[textChangeSignal map:^id(NSString* string) {
return [string isEqualToString:#"Stop"] ? #0 : #1;
}] filter:^BOOL(id value) {
NSLog(#"Switch %#",value);
return [value boolValue];
}];
[[self rac_signalForSelector:#selector(viewWillAppear:)]
subscribeNext:^(id x) {
}];
static NSInteger t = 0;
// Remake's it self once it is on finished.
self.disposable = [[[textChangeSignal filter:^BOOL(NSString* text) {
return [text isEqualToString:#"Stop"] ? [#1 boolValue] : [#0 boolValue];
}]
flattenMap:^RACStream *(id value) {
NSLog(#"Made new Sheduler");
#strongify(self);
return [[self startTimer] takeUntil:switchSignal];
}] subscribeNext:^(id x) {
NSLog(#"%#",x);
#strongify(self);
t = t + 1;
NSLog(#"%zd",t);
[self updateTable];
}];
[[self rac_signalForSelector:#selector(viewWillDisappear:)] subscribeNext:^(id x) {
NSLog(#"viewWillAppear Dispose");
[self.disposable dispose];
}];
}
-(BOOL) isGroupedExcercisesLeft {
BOOL isGroupedLeft = NO;
for (int i =0;i < [self.excercises count]; i++) {
Excercise* ex = [self.excercises objectAtIndex:i];
if(ex.complete == NO && ex.grouped == YES) {
isGroupedLeft = YES;
break;
}
}
return isGroupedLeft;
}
-(void) updateTable {
// Find the
NSInteger nextRow;
if (([self.excercises count] > 0 || self.excercises !=nil) && [self isGroupedExcercisesLeft]) {
for (int i =0;i < [self.excercises count]; i++) {
Excercise* ex = [self.excercises objectAtIndex:i];
if(ex.complete == NO && ex.grouped == YES) {
nextRow = i;
break;
}
}
NSIndexPath* path = [NSIndexPath indexPathForItem:nextRow inSection:0];
NSArray* indexPath = #[path];
// update //
Excercise* ex = [self.excercises objectAtIndex:nextRow];
[self.tableView scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionTop animated:YES];
if (ex.seconds <= 0) {
RLMRealm* db = [RLMRealm defaultRealm];
[db beginWriteTransaction];
ex.complete = YES;
[db commitWriteTransaction];
}
else {
// Update Seconds
RLMRealm* db = [RLMRealm defaultRealm];
[db beginWriteTransaction];
ex.seconds = ex.seconds - 1000;
NSLog(#"Seconds: %zd",ex.seconds);
[db commitWriteTransaction];
// Update table
[self.tableView reloadRowsAtIndexPaths:indexPath withRowAnimation:UITableViewRowAnimationNone];
}
} else {
NSLog(#"Done");
SIAlertView *alertView = [[SIAlertView alloc] initWithTitle:#"Deskercise" andMessage:#"Excercises Complete"];
[alertView addButtonWithTitle:#"Ok"
type:SIAlertViewButtonTypeDefault
handler:^(SIAlertView *alert) {
}];
alertView.transitionStyle = SIAlertViewTransitionStyleBounce;
[alertView show];
NSLog(#"Dispose");
[self.disposable dispose];
}
}
The issue with using takeUntil:self.completeSignal is that when you change completeSignal to another value, it isn't passed to any function that was already waiting for the variable that completeSignal was previously holding.
- (RACSignal*) startTimer {
#weakify(self)
return [[[RACSignal interval:1.0
onScheduler:[RACScheduler mainThreadScheduler]]
startWith:[NSDate date]]
takeUntil:[[self.start rac_signalForControlEvents:UIControlEventTouchUpInside]
merge:[[RACObserve(self, completeSignal) skip:1] flattenMap:
^RACStream *(RACSignal * signal) {
#strongify(self)
return self.completeSignal;
}]]
];
}
The signal is now observing and flattening completeSignal, which will give the desired effect. Signals that complete without sending next events are ignored by takeUntil:, so use self.completedSignal = [RACSignal return:nil], which sends a single next event and then completes.
However, this code is anything but ideal, let's look at a better solution.
#property (nonatomic, readwrite) RACSubject * completeSignal;
- (RACSignal*) startTimer {
return [[[RACSignal interval:1.0
onScheduler:[RACScheduler mainThreadScheduler]]
startWith:[NSDate date]]
takeUntil:[[self.start rac_signalForControlEvents:UIControlEventTouchUpInside]
merge:self.completeSignal]
];
}
- (void) viewDidLoad {
[super viewDidLoad];
self.completeSignal = [RACSubject subject];
self.tableView.delegate = self;
self.tableView.dataSource = self;
RACSignal * pressedStart = [self.start rac_signalForControlEvents:UIControlEventTouchUpInside];
#weakify(self);
RACSignal* textChangeSignal = [[pressedStart startWith:nil] scanWithStart:#"Stop" reduce:^id(id running, id next) {
return #{#"Start":#"Stop", #"Stop":#"Start"}[running];
}];
[self.start
rac_liftSelector:#selector(setTitle:forState:)
withSignals:textChangeSignal, [RACSignal return:#(UIControlStateNormal)], nil];
[[[pressedStart flattenMap:^RACStream *(id value) { //Using take:1 so that it doesn't get into a feedback loop
#strongify(self);
return [self startTimer];
}] scanWithStart:#0 reduce:^id(NSNumber * running, NSNumber * next) {
return #(running.unsignedIntegerValue + 1);
}] subscribeNext:^(id x) {
#strongify(self);
[self updateTable];
NSLog(#"%#", x);
}];
}
- (void) updateTable {
//If you uncomment these then it'll cause a feedback loop for the signal that calls updateTable
//[self.start sendActionsForControlEvents:UIControlEventTouchUpInside];
//[self.completeSignal sendNext:nil];
if ([self.excercises count] > 0 || self.excercises !=nil) {
} else {
}
}
Let's run through the list of changes:
completeSignal is now a RACSubject (a manually controlled RACSignal).
For purity and to get rid of the #weakify directive, textChangeSignal now uses the handy scanWithStart:reduce: method, which lets you access an accumulator (this works well for methods that work with an incrementing or decrementing number).
start's text is now being changed by the rac_liftSelector function, which takes RACSignals and unwraps them when all have fired.
Your flattenMap: to replace pressedStart with [self startTimer] now uses scanWithStart:reduce, which is a much more functional way to keep count.
I'm not sure if you were testing by having updateTable contain completion signals but it definitely causes a logic issue with your flattenMap: of pressedButton, the resulting feedback loop eventually crashes the program when the stack overflows.
I have followed a great tutorial online on how to use Game Center in your iOS apps. It can be found here: http://code.tutsplus.com/tutorials/ios-sdk-game-center-achievements-and-leaderboards-part-2--mobile-5801
However the code for submitting an achievement seems to unlock achievements which have already been unlocked and I don't understand why. Here is my method which deals with a achievements:
-(void)submitAchievement:(NSString*)identifier percentComplete:(double)percentComplete {
if (self.earnedAchievementCache == NULL) {
[GKAchievement loadAchievementsWithCompletionHandler: ^(NSArray *scores, NSError *error) {
if (error == NULL) {
NSMutableDictionary *tempCache = [NSMutableDictionary dictionaryWithCapacity: [scores count]];
for (GKAchievement *score in tempCache) {
[tempCache setObject: score forKey: score.identifier];
}
self.earnedAchievementCache = tempCache;
[self submitAchievement:identifier percentComplete: percentComplete];
}
else {
// Something broke loading the achievement list. Error out, and we'll try again the next time achievements submit.
[self callDelegateOnMainThread: #selector(achievementSubmitted:error:) withArg: NULL error: error];
}
}];
}
else {
// Search the list for the ID we're using...
GKAchievement *achievement = [self.earnedAchievementCache objectForKey:identifier];
if (achievement != NULL) {
if ((achievement.percentComplete >= 100.0) || (achievement.percentComplete >= percentComplete)) {
// Achievement has already been earned so we're done.
achievement = NULL;
}
achievement.percentComplete = percentComplete;
}
else {
achievement = [[[GKAchievement alloc] initWithIdentifier:identifier] autorelease];
achievement.percentComplete = percentComplete;
// Add achievement to achievement cache...
[self.earnedAchievementCache setObject:achievement forKey:achievement.identifier];
}
if (achievement != NULL) {
// Submit the Achievement...
[achievement reportAchievementWithCompletionHandler: ^(NSError *error) {
[self callDelegateOnMainThread:#selector(achievementSubmitted:error:) withArg:achievement error:error];
}];
}
}
}
Thanks for your time, Dan.
Can you try this code and see the log. I added NSLog statements to see if the code detects achievement is completed and sets the achievement to nil. Also delete NULL from the code. Let me know how it works out.
-(void)submitAchievement:(NSString*)identifier percentComplete:(double)percentComplete {
if (!self.earnedAchievementCache) {
[GKAchievement loadAchievementsWithCompletionHandler: ^(NSArray *scores, NSError *error) {
if (!error) {
NSMutableDictionary *tempCache = [NSMutableDictionary dictionaryWithCapacity: [scores count]];
for (GKAchievement *score in scores) { // the error is here
[tempCache setObject: score forKey: score.identifier];
}
self.earnedAchievementCache = tempCache;
[self submitAchievement:identifier percentComplete: percentComplete];
}
else {
// Something broke loading the achievement list. Error out, and we'll try again the next time achievements submit.
[self callDelegateOnMainThread: #selector(achievementSubmitted:error:) withArg: NULL error: error];
}
}];
}else {
// Search the list for the ID we're using...
GKAchievement *achievement = [self.earnedAchievementCache objectForKey:identifier];
NSLog(#"achievement %f",achievement.percentComplete);
if (achievement) {
if ((achievement.percentComplete >= 100.0) || (achievement.percentComplete >= percentComplete)) {
NSLog(#"Achievement has already been earned so we're done.");
achievement = nil;
}else{
achievement.percentComplete = percentComplete;
}
}else {
achievement = [[[GKAchievement alloc] initWithIdentifier:identifier] autorelease];
achievement.percentComplete = percentComplete;
// Add achievement to achievement cache...
[self.earnedAchievementCache setObject:achievement forKey:achievement.identifier];
}
if (achievement) {
NSLog(#"Submit the Achievement...");
[achievement reportAchievementWithCompletionHandler: ^(NSError *error) {
[self callDelegateOnMainThread:#selector(achievementSubmitted:error:) withArg:achievement error:error];
}];
}
}
}
NSDictionary *tokenData = [[responseDict objectForKey:#"data"] objectForKey:#"multipass"];
dispatch_async(dispatch_get_main_queue(), ^{
NSString *card = [NSString stringWithFormat:#"%#-%#",
[[UserAccount sharedInstance] cardNumber],
[tokenData objectForKey:#"CardPhoneToken"]]; //<-- problem
});
I've been fighting with this line of code since yesterday.I'm geting a JSON feed from the server nad My problem is that one time out of ten tokenData returns with 0 instead of a Dictionary and I can't figure out why. When it returns as 0 it is also treated as a boolean by the compiler (can't figure out why either) and i'm getting the error -[NSCFBoolean objectForKey:]: unrecognized selector sent to instance 0x81aa20
I've tried doing something like this
if(!tokenData)
{
NSLog(#"This is going to crash");
} But when tokenData returns 0 the if isn't getting called.
esponseDict JSON I'm getting :
{ code = 200; data = {
multipass = {
CardPhoneBarcodeToken = 562431;
CardPhoneToken = 23221;
errorMessage = "";
operationResult = 0;
};
};
}
This is how it looks right before it crashes: { code = 200; data = { multipass = 0; }; }
Update: I've managed to take action if tokenData is not NSDictionary now I'm facing another problem with the else section. How can I reload the tokenData if it's not a NSDictionary ? I tried calling *tokenData = [[responseDict objectForKey:#"data"] objectForKey:#"multipass"]; inside the else but I'm keep getting tokenData = 0
Thanks
I would check if tokenData is a NSDictionary before parsing it.
-(void)loadData
{
NSDictionary *tokenData = [[responseDict objectForKey:#"data"] objectForKey:#"multipass"];
dispatch_async(dispatch_get_main_queue(), ^{
NSString *cardPhoneToken = #"";
if( [tokenData isKindOfClass:[NSDictionary class]] ){
retryCount = 0;
cardPhoneToken = [tokenData objectForKey:#"CardPhoneToken"];
} else {
NSLog(#"failed to load data, retrying...");
retryCount++;
if( retryCount < 5 ){
[self loadData];
return;
} else {
NSLog(#"failed after 5 retries");
}
}
NSString *card = [NSString stringWithFormat:#"%#-%#",
[[UserAccount sharedInstance] cardNumber],
cardPhoneToken];
});
}
Sounds familiar. :)
Change the block to:
^{
NSString *card;
if ([tokenData isKindOfClass [NSDictionary class]) {
card = [NSString stringWithFormat:#"%#-%#",
[[UserAccount sharedInstance] cardNumber],
[tokenData objectForKey:#"CardPhoneToken"]]; // <<-- No Problem anymore
} else {
// deal with the situation. Probably do:
card = [NSString stringWithFormat:"%#-no phone number given", [UserAccount sharedInstance] cardNumber]];
}
Alternative:
^{
id cardPhoneToken;
if ([tokenData]) {
cardPhoneToken = [tokenData objectForKey:#"CardPhoneToken"];
} else {
cardPhoneToken = #"no Phone Token"; // Or what ever you think is appropriate
}
NSString *card = [NSString stringWithFormat:#"%#-%#",
[[UserAccount sharedInstance] cardNumber],
cardPhoneToken]; // <<-- No Problem anymore
}