Why does VerificationController crash inside of didReceiveData - ios

Here is the function where it crashes. The exact line of code is the one with: removeObjectForKey. Even when the test function is completely empty it crashes on removeObjectForKey. Note: I'm just passing in an empty function callback. currently, I have ARC off, do i need to turn it on? If possible, i would like to do it with ARC off, because turning it on would mean dealing with alot of compile issues.
the function does say something about non-retained objects, hence could be a memory issue.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// So we got some receipt data. Now does it all check out?
BOOL isOk = [self doesTransactionInfoMatchReceipt:responseString];
VerifyCompletionHandler completionHandler = _completionHandlers[[NSValue valueWithNonretainedObject:connection]];
[_completionHandlers removeObjectForKey:[NSValue valueWithNonretainedObject:connection]];
if (isOk)
{
//Validation suceeded. Unlock content here.
NSLog(#"Validation successful");
completionHandler(TRUE);
} else {
NSLog(#"Validation failed");
completionHandler(FALSE);
}
}
Here is the verificationController usage:
[[VerificationController sharedInstance] verifyPurchase:transaction completionHandler:^(BOOL success) {
if (success) {
NSLog(#"Hi, its success.");
[self testMethod];
} else {
NSLog(#"payment not authorized.");
}
}];
}
- (void) testMethod {
}
I could use __weak but then I would have to turn on ARC, which i'm trying to avoid. Note: the verificaitionController works when I put it inside other Classes/Objects, but as soon as I put it in the InAppPurchaseManager it blows up anytime it tries to access self. Self points to an instance of InAppPurchaseManager as defined like so (its a phonegap plugin):
#interface InAppPurchaseManager : CDVPlugin <SKPaymentTransactionObserver> {
}

I also ran into this problem i fixed this issue this way
main issue is when you are setting value to completion handler in verifyPurchase method it is setting nil value so find this line in verifyPurchase method
_completionHandlers[[NSValue valueWithNonretainedObject:conn]] = completionHandler;
and replace it with
[_completionHandlers setObject:[completionHandler copy] forKey:[NSValue valueWithNonretainedObject:conn]];
and find the connectionDidReceivedata method and replace it with
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// So we got some receipt data. Now does it all check out?
BOOL isOk = [self doesTransactionInfoMatchReceipt:responseString];
if (_completionHandlers && [_completionHandlers respondsToSelector:#selector(removeObjectForKey:)])
{
VerifyCompletionHandler completionHandler = _completionHandlers[[NSValue valueWithNonretainedObject:connection]];
[_completionHandlers removeObjectForKey:[NSValue valueWithNonretainedObject:connection]];
if (isOk)
{
//Validation suceeded. Unlock content here.
NSLog(#"Validation successful");
completionHandler(TRUE);
} else {
NSLog(#"Validation failed");
completionHandler(FALSE);
}
}
//[_completionHandlers removeObjectForKey:[NSValue valueWithNonretainedObject:connection]];
}
Hope this may help you and saves alot of time.

Is _completionHandlers nil? You might do something like this -
if (_completionHandlers && [_completionHandlers respondsToSelector:#selector(removeObjectForKey:)]) {
[_completionHandlers removeObjectForKey:[NSValue valueWithNonretainedObject:connection]];
}
Good luck.

Find following string:
[_completionHandlers setObject:completionHandler forKey:[NSValue valueWithNonretainedObject:conn]];
And change to:
[_completionHandlers setObject:[completionHandler copy] forKey:[NSValue valueWithNonretainedObject:conn]];

I don't know if you found the answer yet, but I just realized that _completionHandlers is never allocated (and if you po _completionHandlers after setting a breakpoint you will notice it is nil). Hope this helps!
// in VerificationController.m
- (id)init
{
self = [super init];
if (self != nil)
{
transactionsReceiptStorageDictionary = [NSMutableDictionary dictionary];
_completionHandlers = [NSMutableDictionary dictionary];
}
return self;
}

Related

What is the proper way of designing this viewModel with blocks in MVVM design?

I am developing a register function for my app using MVVM design.
I've created a sharedInstance class and implemented the register method using block like this:
- (void)registerUserWithName:(NSString *)name
phone:(NSString *)phone
password:(NSString *)password
completion:(CompletionBlock)aCallback
{
UserModel *user = [[UserModel alloc] init];
user.name = name;
user.phone = phone;
user.password = password;
[[self.client registerUser:user].task continueWithBlock:^id(BFTask *task) {
if (task.error) {
aCallback(NO, task.error);
return nil;
}
aCallback(YES, nil);
return nil;
}];
}
In MVC design, I would just do this to call this method:
GlobalFunctions *function = [GlobalFunctions sharedInstance];
[function registerUserWithName:name phone:number password:password completion:^(BOOL isSuccess, NSError *error) {
if(isSuccess){
ViewController *vc = [[ViewController alloc]init];
[self.navigationController pushViewController:vc animated:YES];
}
}];
I tried to apply this code to the MVVM design. In my viewModel, I have:
-(BOOL)registerUserWithUser:(NSString*)name withPhone: (NSString*)number withPassword: (NSString*)password{
RBCFunctions *function = [RBCFunctions sharedInstance];
[function registerUserWithName:name phone:number password:password completion:^(BOOL isSuccess, NSError *error) {
return isSuccess;
}];
}
However, this doesn't work because I get a compiler error:
Incompatible block pointer types sending BOOL
How can I implement this properly?
The error you're getting is because you're trying to return a boolean value from a block with return type void. Look at my comments in this code,
-(BOOL)registerUserWithUser:(NSString*)name withPhone: (NSString*)number withPassword: (NSString*)password{
// Begin method that returns BOOL
RBCFunctions *function = [RBCFunctions sharedInstance];
// Call function that takes a completion handler that returns VOID!
[function registerUserWithName:name phone:number password:password completion:^(BOOL isSuccess, NSError *error) {
// Returning BOOL in block that should return VOID!
return isSuccess;
}];
// NO RETURN VALUE!
}
If you are dealing with blocks (AKA asynchronous programming) and you want to pass values around, you need to create more blocks so your code would look like this:
-(void)registerUserWithUser:(NSString*)name withPhone: (NSString*)number withPassword: (NSString*)password handler:(void(^)(BOOL success))handler{
RBCFunctions *function = [RBCFunctions sharedInstance];
[function registerUserWithName:name phone:number password:password completion:^(BOOL isSuccess, NSError *error) {
!handler ?: handler(isSuccess);
// The above line is equivalent to:
/*
if (handler) {
handler(isSuccess);
}
*/
}];
}
That way you would call it like this:
[self registerUserWithUser:#"Badass" withPhone:#"01234567891" withPassword:#"password" handler:^(BOOL OK) {
if (OK) {
// Boss!
} else {
// Ahh NO!
}
}];
A few things to be aware of:
1) If your block variable is nil and you call it (i.e. myBlock(args)) then your app will crash. You need to test for existence before calling blocks; the easiest way to do this is with the ternary operator: !myBlock ?: myBlock(args);
2) Are you sure your RCBFunctions class needs to be a singleton? It is easy once you know about singletons to use them all over the place when they're not needed (I've done it myself before). Might it not be easier to add the registration code to the UserModel class? I.e. :
[myUser registerWithCallback:^(BOOL success) {
// Blah blah
}];

Completion Handler crash in AutoRenewable In-Appurchase

I'm making an application in which,I have to make my Inappurchase product auto renewable,for this, after reading Apple documents i came to know that after every transaction for autorenewable product ,our app receives a transaction receipt for every purchase and i need to verify that receipt from Apple server after verifying my transaction receipt app has to save that transaction date.
But after purchasing product when i am trying to verifying that transaction receipt from Apple server ,through the Apple Classes -Verification Controller, my app crashing at completion handler ,its showing completion handler NIL.
my _completionHandlers is released when execution reaches in any of these methods what to now??
Please guide me to solve this issue
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// So we got some receipt data. Now does it all check out?
BOOL isOk = [self doesTransactionInfoMatchReceipt:responseString];
VerifyCompletionHandler completionHandler = _completionHandlers[[NSValue valueWithNonretainedObject:connection]];
NSValue *key = [NSValue valueWithNonretainedObject:connection];
NSLog(#"%#",_completionHandlers);
[_completionHandlers removeObjectForKey:key];
if (isOk)
{
//Validation suceeded. Unlock content here.
NSLog(#"Validation successful");
completionHandler(TRUE);
} else {
NSLog(#"Validation failed");
completionHandler(FALSE);
}
}
I also ran into this problem i fixed this issue this way
main issue is when you are setting value to completion handler in verifyPurchase method it is setting nil value so find this line in verifyPurchase method
_completionHandlers[[NSValue valueWithNonretainedObject:conn]] = completionHandler;
and replace it with
[_completionHandlers setObject:[completionHandler copy] forKey:[NSValue valueWithNonretainedObject:conn]];
changing these two lines will resolve your crash but to be double sure do these steps also
and find the connectionDidReceivedata method and replace it with
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// So we got some receipt data. Now does it all check out?
BOOL isOk = [self doesTransactionInfoMatchReceipt:responseString];
if (_completionHandlers && [_completionHandlers respondsToSelector:#selector(removeObjectForKey:)])
{
VerifyCompletionHandler completionHandler = _completionHandlers[[NSValue valueWithNonretainedObject:connection]];
[_completionHandlers removeObjectForKey:[NSValue valueWithNonretainedObject:connection]];
if (isOk)
{
//Validation suceeded. Unlock content here.
NSLog(#"Validation successful");
completionHandler(TRUE);
} else {
NSLog(#"Validation failed");
completionHandler(FALSE);
}
}
//[_completionHandlers removeObjectForKey:[NSValue valueWithNonretainedObject:connection]];
}

Return parent method from within block

Hi I'm new to using blocks in Objective-C
What I think I want is the following:
- (void) iNeedAToken {
NSString *token = [self theMethodThatShouldReturnTheToken];
}
- (NSString) theMethodThatShouldReturnTheToken {
[myAwesomeAsyncMethod success:^(id JSON) {
NSString *token = [JSON objectForKey:#"FOO"];
return token;
}]
}
Is this possible? Or is this the wrong logic all together?
Thanks!
You're mixing async with synchronous code. You're theMethodThatShouldReturnTheToken already returned (you're missing a return value) before the block passed to success finishes.
Best bet would be to continue your process from the success block.
- (void) tokenRequestContext1
{
[self requestToken:^(NSString *token) {
// do something with token
}];
}
- (void) requestToken:(void(^)(NSString *))tokenBlock
{
[myAwesomeAsyncMethod success:^(id JSON) {
NSString *token = [JSON objectForKey:#"FOO"];
if (tokenBlock) {
tokenBlock(token);
}
}];
}
You start by calling requestToken. This will start the async request for your token. Some time might pass, but eventually doSomethingWithToken will be called where you can use the received token.
There is a description of way to wait until complition block will be finished: http://omegadelta.net/2011/05/10/how-to-wait-for-ios-methods-with-completion-blocks-to-finish/
Regular version of this code is:
- (void) iNeedAToken {
[self theMethodThatShouldReturnTheToken:^(id res){ token = res;}];
NSString *token = [self theMethodThatShouldReturnTheToken];
}
- (void) theMethodThatShouldReturnTheToken:(void (^)(id res)result) {
[myAwesomeAsyncMethod success:^(id JSON) {
NSString *token = [JSON objectForKey:#"FOO"];
result(token);
}]
}

Creating a Wrapper for RestKit

I am trying to write a wrapper for RestKit, so that all requests call one function which in turn would trigger a request via RestKit.
Here's what I have so far:
A function would call my wrapper as follows:
NSDictionary *response = [Wrappers sendRequestWithURLString:url method:#"GET"];
And my wrapper methods:
+ (NSDictionary *)sendRequestWithURLString:(NSString *)request method:(NSString *)method
{
RKRequestDidFailLoadWithErrorBlock failBlock;
if ([method isEqualToString:#"GET"])
return [self sendGETRequestWithURLString:request withFailBlock:failBlock];
else if ([method isEqualToString:#"POST"])
return [self sendPOSTRequestWithURLString:request withFailBlock:failBlock];
return nil;
}
+ (NSDictionary *)sendGETRequestWithURLString:(NSString *)request withFailBlock:(RKRequestDidFailLoadWithErrorBlock)failBlock {
RKObjectManager *manager = [RKObjectManager sharedManager];
__block NSDictionary *responseDictionary;
[manager loadObjectsAtResourcePath:request usingBlock:^(RKObjectLoader *loader) {
loader.onDidLoadResponse = ^(RKResponse *response) {
[self fireErrorBlock:failBlock onErrorInResponse:response];
RKJSONParserJSONKit *parser = [RKJSONParserJSONKit new];
responseDictionary = [[NSDictionary alloc] initWithDictionary:[parser objectFromString:[response bodyAsString] error:nil]];
};
}];
return responseDictionary;
}
+ (void)fireErrorBlock:(RKRequestDidFailLoadWithErrorBlock)failBlock onErrorInResponse:(RKResponse *)response {
if (![response isOK]) {
id parsedResponse = [response parsedBody:NULL];
NSString *errorText = nil;
if ([parsedResponse isKindOfClass:[NSDictionary class]]) {
errorText = [parsedResponse objectForKey:#"error"];
}
if (errorText)
failBlock([self errorWithMessage:errorText code:[response statusCode]]);
}
}
+ (NSError *)errorWithMessage:(NSString *)errorText code:(NSUInteger)statusCode {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"Please make sure you are connected to WiFi or 3G."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
return nil;
}
The problem here is when responseDictionary returns, the value is nil since onDidLoadResponse would not have processed yet as it runs concurrently.
In this case, what would be the best approach in setting responseDictionary? I'm trying to avoid calling a setter method of another class. In this case, is my only option using delegates, which defeats the whole purpose of creating a wrapper class since RestKit calls require usage of delegate methods to return the response?
Would I be able to pass my wrapper a success block which would update some local ivar? How would I do that?
You pass a success block as you have said. Here is an example of how to do that:
.h
typedef void (^kServiceCompleteBlock)(NSDictionary* responseDictionary);
...
+ (NSDictionary *)sendGETRequestWithURLString:(NSString *)request withFailBlock:(RKRequestDidFailLoadWithErrorBlock)failBlock completion: (kServiceCompleteBlock) completion ;
...
.m
+ (NSDictionary *)sendGETRequestWithURLString:(NSString *)request withFailBlock:(RKRequestDidFailLoadWithErrorBlock)failBlock completion: (kServiceCompleteBlock) completion {
...
loader.onDidLoadResponse = ^(RKResponse *response) {
[self fireErrorBlock:failBlock onErrorInResponse:response];
RKJSONParserJSONKit *parser = [RKJSONParserJSONKit new];
responseDictionary = [[NSDictionary alloc] initWithDictionary:[parser objectFromString:[response bodyAsString] error:nil]];
if( completion )
completion(responseDictionary);
};
...
}
However, let me warn you of a potential design flaw. You should design your app so that the UI is driven by your data and not your web service. This means your UI should automatically update when your data model is updated and not when your web service returns. Be careful how you use this response dictionary. If it is to update UI then you are running down a dangerous road.

Run multiple instances of NSOperation with NSURLConnection?

We have a large project that needs to sync large files from a server into a 'Library' in the background. I read subclassing NSOperation is the most flexible way of multithreading iOS tasks, and attempted that. So the function receives a list of URLs to download & save, initialises an instance of the same NSOperation class and adds each to an NSOperation queue (which should download only 1 file at a time).
-(void) LibSyncOperation {
// Initialize download list. Download the homepage of some popular websites
downloadArray = [[NSArray alloc] initWithObjects:#"www.google.com",
#"www.stackoverflow.com",
#"www.reddit.com",
#"www.facebook.com", nil];
operationQueue = [[[NSOperationQueue alloc]init]autorelease];
[operationQueue setMaxConcurrentOperationCount:1]; // Only download 1 file at a time
[operationQueue waitUntilAllOperationsAreFinished];
for (int i = 0; i < [downloadArray count]; i++) {
LibSyncOperation *libSyncOperation = [[[LibSyncOperation alloc] initWithURL:[downloadArray objectAtIndex:i]]autorelease];
[operationQueue addOperation:libSyncOperation];
}
}
Now, those class instances all get created fine, and are all added to the NSOperationQueue and begin executing. BUT the issue is when it's time to start downloading, the first file never begins downloading (using an NSURLConnection with delegate methods). I've used the runLoop trick I saw in another thread which should allow the operation to keep running until the download is finished. The NSURLConnection is established, but it never starts appending data to the NSMutableData object!
#synthesize downloadURL, downloadData, downloadPath;
#synthesize downloadDone, executing, finished;
/* Function to initialize the NSOperation with the URL to download */
- (id)initWithURL:(NSString *)downloadString {
if (![super init]) return nil;
// Construct the URL to be downloaded
downloadURL = [[[NSURL alloc]initWithString:downloadString]autorelease];
downloadData = [[[NSMutableData alloc] init] autorelease];
NSLog(#"downloadURL: %#",[downloadURL path]);
// Create the download path
downloadPath = [NSString stringWithFormat:#"%#.txt",downloadString];
return self;
}
-(void)dealloc {
[super dealloc];
}
-(void)main {
// Create ARC pool instance for this thread.
// NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init]; //--> COMMENTED OUT, MAY BE PART OF ISSUE
if (![self isCancelled]) {
[self willChangeValueForKey:#"isExecuting"];
executing = YES;
NSURLRequest *downloadRequest = [NSURLRequest requestWithURL:downloadURL];
NSLog(#"%s: downloadRequest: %#",__FUNCTION__,downloadURL);
NSURLConnection *downloadConnection = [[NSURLConnection alloc] initWithRequest:downloadRequest delegate:self startImmediately:NO];
// This block SHOULD keep the NSOperation from releasing before the download has been finished
if (downloadConnection) {
NSLog(#"connection established!");
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!downloadDone);
} else {
NSLog(#"couldn't establish connection for: %#", downloadURL);
// Cleanup Operation so next one (if any) can run
[self terminateOperation];
}
}
else { // Operation has been cancelled, clean up
[self terminateOperation];
}
// Release the ARC pool to clean out this thread
//[pool release]; //--> COMMENTED OUT, MAY BE PART OF ISSUE
}
#pragma mark -
#pragma mark NSURLConnection Delegate methods
// NSURLConnectionDelegate method: handle the initial connection
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse*)response {
NSLog(#"%s: Received response!", __FUNCTION__);
}
// NSURLConnectionDelegate method: handle data being received during connection
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[downloadData appendData:data];
NSLog(#"downloaded %d bytes", [data length]);
}
// NSURLConnectionDelegate method: What to do once request is completed
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"%s: Download finished! File: %#", __FUNCTION__, downloadURL);
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex:0];
NSString *targetPath = [docDir stringByAppendingPathComponent:downloadPath];
BOOL isDir;
// If target folder path doesn't exist, create it
if (![fileManager fileExistsAtPath:[targetPath stringByDeletingLastPathComponent] isDirectory:&isDir]) {
NSError *makeDirError = nil;
[fileManager createDirectoryAtPath:[targetPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:&makeDirError];
if (makeDirError != nil) {
NSLog(#"MAKE DIR ERROR: %#", [makeDirError description]);
[self terminateOperation];
}
}
NSError *saveError = nil;
//NSLog(#"downloadData: %#",downloadData);
[downloadData writeToFile:targetPath options:NSDataWritingAtomic error:&saveError];
if (saveError != nil) {
NSLog(#"Download save failed! Error: %#", [saveError description]);
[self terminateOperation];
}
else {
NSLog(#"file has been saved!: %#", targetPath);
}
downloadDone = true;
}
// NSURLConnectionDelegate method: Handle the connection failing
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"%s: File download failed! Error: %#", __FUNCTION__, [error description]);
[self terminateOperation];
}
// Function to clean up the variables and mark Operation as finished
-(void) terminateOperation {
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
finished = YES;
executing = NO;
downloadDone = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
#pragma mark -
#pragma mark NSOperation state Delegate methods
// NSOperation state methods
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isFinished {
return finished;
}
NOTE: If that was too unreadable, I set up a QUICK GITHUB PROJECT HERE you can look through. Please note I'm not expecting anyone to do my work for me, simply looking for an answer to my problem!
I suspect it has something to do with retaining/releasing class variables, but I can't be sure of that since I thought instantiating a class would give each instance its own set of class variables. I've tried everything and I can't find the answer, any help/suggestions would be much appreciated!
UPDATE: As per my answer below, I solved this problem a while ago and updated the GitHub project with the working code. Hopefully if you've come here looking for the same thing it helps!
In the interests of good community practice and helping anyone else who might end up here with the same problem, I did end up solving this issue and have updated the GitHub sample project here that now works correctly, even for multiple concurrent NSOperations!
It's best to look through the GitHub code since I made a large amount of changes, but the key fix I had to make to get it working was:
[downloadConnection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
This is called after the NSURLConnection is initialized, and just before it is started. It attaches the execution of the connection to the current main run loop so that the NSOperation won't prematurely terminate before the download is finished. I'd love to give credit to wherever first posted this clever fix, but it's been so long I've forgotten where, apologies. Hope this helps someone!

Resources