2

I have a remote hybrid app that uses a community login page. If you login successfully, it works fine. If you get your user/password wrong, sometimes you get the login page again, but sometimes you get a black screen with a popup message "The URL can’t be shown".

z

The xcode console shows more detail:

ERROR|SFAuthenticationManager|Error during authentication: Error Domain=WebKitErrorDomain Code=101 "The URL can’t be shown" UserInfo=0x7fa36523a060 {NSErrorFailingURLStringKey=gap://ready, NSLocalizedDescription=The URL can’t be shown, NSErrorFailingURLKey=gap://ready}

I haven't figured out why that happens, and now it's gotten worse- instead of showing the error, the app crashes. I believe the change is that it's running on iOS 8.3, although I don't have an 8.2 device available currently to test with.

I've determined that the crash is happening in SFOAuthCoordinator; it looks like SFOAuthCoordinator, acting as a delegate for the login web view, is receiving the message webView:didFailLoadWithError: and calling notifyDelegateOfFailure:authInfo:, which is calling self.authInfo = nil;, but at this point the object has been released (EXC_BAD_ACCESS in objc_msgSend). In the XCode console, the crash happens immediately after the above "Url can't be shown" error, so I suspect that fixing that would eliminate the crash. But I have no idea what causes this... the login page is just a modified version of the default SiteLogin page, and the login method is the default:

global PageReference login() {
    String startUrl = System.currentPageReference().getParameters().get('startURL');
    return Site.login(username, password, startUrl);
}
1

I was having the same issue today (how I found your post). I believe this crash is a memory management issue within the SDK, but I'll share my little work around. The problem is that the coordinator somehow becomes released before setting authInfo=nil;. This is all handled entirely in the SDK and crashes every time for me (not iOS 8.3 specific either). This began happening after I updated my SFSDK and converted login to use the success/Fail blocks instead of the delegate methods.

Basically, you just need to hold a reference to the coordinator so ARC won't let it go. I now have a strong reference that I hold in the view controller that manages the login web view.

@property(nonatomic, strong) SFOAuthCoordinator *coordinator; // Only store a reference to keep from going nil and crashing

Inside the .m file I implement the SFAuthenticationManagerDelegate delegate method.

- (void)authManagerDidFinishAuthWebViewLoad:(SFAuthenticationManager *)manager {
    // Set the delegate to prevent crash in default delegate.
    self.coordinator = manager.coordinator; // This is just to keep a reference.
}

I handle some other logic in here as well (activity indicator etc.). Obviously for this to be called you need to add your VC as a delegate with [[SFAuthenticationManager sharedManager]addDelegate:self];

Now when you set up your auth view handler like this.

SFAuthenticationViewHandler *authViewHandler = [[SFAuthenticationViewHandler alloc]initWithDisplayBlock:^(SFAuthenticationManager *authManager, UIWebView *webView) {...}

Your delegate method will be called and you can retain a reference to the coordinator (must wait to set this as it won't exist on SFAuthenticationManager until the login view is set up). This is preventing the crash for me. Then just clean up your reference count in dealloc when you don't need the login view anymore.

- (void)dealloc {
    self.coordinator = nil;
    [[SFAuthenticationManager sharedManager]removeDelegate:self];
}

Hope this helps, quite the frustrating little bug.

| improve this answer | |
  • Thanks @johnrechd, I suspected the problem was the coordinator being released too soon. I'm having trouble with your fix however; As noted, I've built a Hybrid Remote app, so I don't set up my own auth view handler; the SDK does that for me. Is there some way I can hook in to this process from my AppDelegate to get and store the coordinator reference? The only other class exposed is the MainViewController, which I suspect doesn't load until after oauth. – Jason Clark Apr 30 '15 at 14:57
  • After rereading your explanation, I tried adding the code to my app delegate instead of my view controller, and I believe it is working. Hard to tell as the error has been somewhat intermittent; but I added logging to -authManagerDidFinishAuthWebViewLoad and can see that it is receiving the coordinator reference. Unfortunately, I now getting the "Cannot connect to salesforce" popup a heartbeat after seeing the login page with the usual failed login message; clicking ok leaves me stuck on an SDK "Loading..." screen forever. But that's a problem for another question. – Jason Clark Apr 30 '15 at 15:28
  • I've accepted your answer, and will post another version with all of the changes I made, for other hybrid app builders who aren't very Obj-C savvy (like me). – Jason Clark Apr 30 '15 at 15:29
  • Thanks Jason, glad you're not crashing now. I handle my login page a little different than the sample hybrid I imagine (this app is going on 3 years in the wild). Sorry, I always assume people are iOS developers first, I should have added more detail on the setup. – johnrechd Apr 30 '15 at 19:03
0

I've accepted @johnrechd's answer, as it allowed me to solve my problem. I believe it is also the correct answer for anyone making a native iOS app.

I had to make a few adjustments to make this work with a Hybrid app; here are the steps I took. This is @johnrechd's code, just organized and explained for the hybrid app's generated container:

First, in AppDelegate.h, import needed headers:

 #import "SFOAuthCoordinator.h"
 #import "SFAuthenticationManager.h"

Then update the AppDelegate interface declaration to implement the SFAuthenticationManager protocol:

@interface AppDelegate : NSObject <UIApplicationDelegate, SFAuthenticationManagerDelegate>{}

Inside the @interface block, add the instance variable to store the reference:

@property(nonatomic, strong) SFOAuthCoordinator *coordinator; // Only store a reference to keep from going nil and crashing

Next, in AppDelegate.m, find the init() method. There's a comment in the code telling you where to add app-specific initialization; add the following code:

[[SFAuthenticationManager sharedManager]addDelegate:self];

Just after the init() method, add the dealloc method and the delegate method:

- (void)dealloc {
    self.coordinator = nil;
    [[SFAuthenticationManager sharedManager]removeDelegate:self];
}

#pragma mark SFAuthenticationManagerDelegate implementation

- (void)authManagerDidFinishAuthWebViewLoad:(SFAuthenticationManager *)manager {
    // Set the delegate to prevent crash in default delegate.
    self.coordinator = manager.coordinator; // This is just to keep a reference.
    NSLog(@"### AppDelegate received manager callback, coordinator=%@ ###", self.coordinator);
}

That's it. In my case, no more crashes, although I am still seeing the "Cannot connect to salesforce" popup a heartbeat after seeing the login page with the usual failed login message; clicking ok leaves me stuck on an SDK "Loading..." screen forever. I'll add a link here to a followup question later.

| improve this answer | |
  • Just to add to this. In the case where you handle everything in AppDelegate, there is no need to add the dealloc method. The instance you are running in is a singleton instance that stays alive the entire time the app is running and dealloc should never be called. You should possibly set your ref to the coordinator to nil and remove your delegate once you know you are done with it. – johnrechd Apr 30 '15 at 19:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.