Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS App Crashes - I suspect it has to do with UIWebView using WebSockets

I am not currently using applicationWillResignActive / applicationWillEnterForeground or anything similar, as I'm not sure exactly how to handle this issue I'm running into.

My app keeps crashing if I open it up, press home screen, wait about 5 minutes, and try to go back into the app again.

The app is mainly a UIWebView that loads a node.js powered websocket, so I think that is the culprit.

How can I stop the UIWebView from doing anything/everything when the user leaves the app (applicationWillResignActive is what I'm guessing needs to be used). I have played with a half dozen ideas and I cannot for the life of me get the app to stop crashing.

My Current Idea:

- (void)applicationWillResignActive:(UIApplication *)application
{
[[NSNotificationCenter defaulCenter] postNotificationName:@"EnterBackground" object:Nil userInfo: Nil];
}

...and then from there I edit my ViewController.m file to do something to the UIWebView on that page. The problem is, what will stop the UIWebView from doing anything while in the background? I have tried stoploading, and for some reason had no success, or it would still crash.

like image 459
daemon Avatar asked Jan 05 '14 07:01

daemon


1 Answers

I couldn't reproduce the exact issue (WebThread: EXC_BAD_ACCESS (code=1, address=0x5)—taken from your other question), but I did notice that the WebSocket messages were being buffered, so it maybe that if you leave the app in the background long enough, you will hit a memory pressure issue that may be the cause of this crash.

However, in the process of researching this question I did manage to make the app reliably crash with a EXC_BAD_ACCESS originating from the WebThread so I'll post the details here in the hope it may be of use to you or someone else.

For the purposes of this question, my WebSocket server simply broadcast the time every second to any connected clients.

As I mentioned, I had noticed that the UIWebView was buffering the WebSocket messages when the app was backgrounded. To prevent this, I wanted to close the WebSocket connection when the app was moved into the background.

In my view controller hosting the UIWebView, I registered a selector for UIApplicationWillResignActiveNotification and in that selector, I called a JavaScript function in my web page that called close on the WebSocket object.

ViewController.m:

- (void)viewDidLoad
{
    [super viewDidLoad];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(appWillResignActive:)
                                                 name:UIApplicationWillResignActiveNotification
                                               object:nil];
}

- (void)appWillResignActive:(NSNotification *)notification
{
    [self.webView stringByEvaluatingJavaScriptFromString:@"closeWebSocket();"];
}

Web Page:

function closeWebSocket() {
    socket.close(); // socket is an instance of WebSocket
    socket = undefined;
}

I wanted the WebSocket to reconnect when the app was moved back into the foreground, so I registered another selector, this time for the UIApplicationDidBecomeActiveNotification and in that selector called another function that would initialise the WebSocket connection.

ViewController.m:

- (void)viewDidLoad
{
    [super viewDidLoad];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(appWillResignActive:)
                                                 name:UIApplicationWillResignActiveNotification
                                               object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(appDidBecomeActive:)
                                                 name:UIApplicationDidBecomeActiveNotification
                                               object:nil];
}

- (void)appWillResignActive:(NSNotification *)notification
{
    [self.webView stringByEvaluatingJavaScriptFromString:@"closeWebSocket();"];
}

- (void)appDidBecomeActive:(NSNotification *)notification
{
    [self.webView stringByEvaluatingJavaScriptFromString:@"openWebSocket();"];
}

Web Page:

function openWebSocket() {  
    socket = new WebSocket("ws://" + document.location.host + "/ping");  

    socket.onopen = function () { console.log("Opened"); };
    socket.onmessage = function (message) { 
      var log = $('#log');
      log.html(message.data + '<br>' + log.html()); 
      console.log(message.data);
    };
    socket.onclose = function () { console.log("Closed"); };
}

Running the app, the WebSocket connection was established, messages were received, and the app moved into the background. After a few seconds, I brought the app back to the foreground (by tapping on the app icon) and the app crashed with an exception similar to WebThread: EXC_BAD_ACCESS (code=1, address=0x5. The cause of this was the evaluation of the JavaScript call to openWebSocket() when the app was brought back to the foreground. Delaying the execution of openWebSocket() by a few milliseconds fixed the issue. By appDidBecomeActive method ended up looking like this:

- (void)appDidBecomeActive:(NSNotification *)notification
{
    [self.webView stringByEvaluatingJavaScriptFromString:@"setTimeout(openWebSocket, 5);"];
}

Running the app again, the WebSocket connection was established, messages received, and the app backgrounded. When I brought the app back into the foreground after few seconds later, the app did not crash and the messages started coming through WebSocket connection again.

like image 63
neilco Avatar answered Nov 15 '22 05:11

neilco