MegaBits/SIOSocket

sometimes cannot send message (only on iOS 8)

jukiginanjar opened this issue · 18 comments

Hi
I've experienced weird error and it happen in iOS8+ only,
The error is SIOSocket intermittently didn't send the emit message to server.

Please help,

Same problem here. You must use the main thread all the time to access the library.
I think adding this information to the README can help others persons too 😄

ping @pcperini

I have this problem too, even tho I've moved to emitting everything from the main thread.

Could one of you show me an example of how you got emitting to work on iOS 8? thanks.

Hey y'all, the thread-safety branch contains a change to address this issue (by simply dispatching all emits to the main queue). Mind giving it a test and seeing if it resolves your issues?

Issue not resolved for me with the thread-safety branch

@DemonicEgg could you please post a code sample demonstrating where you're having the issue?

sure, I am just calling
[self.socket emit:@"chatevent" args:@[self.textfield.text]];
within an IBAction

Initializing the socket like this:

    SIOSocket socketWithHost:@"http://localhost:9092" response:^(SIOSocket *socket) {
    self.socket = socket;

    self.socket.onConnect = ^ () {
        NSLog(@"connected");
    };

    self.socket.onDisconnect = ^ () {
        NSLog(@"disconnected");
    };

    self.socket.onError = ^(NSDictionary* error) {
        NSLog(@"%@", error);
    };


    [self.socket on:@"chatevent" callback:^(id data) {
        NSString *string = (NSString*)data;
        NSLog(@"%@", string);
    }];
}];

I've been seeing this same issue, weird thing is that removing the dispatch_async around the emit fixed the issues on iOS 8 for me:

//    dispatch_async(dispatch_get_main_queue(), ^{
        [self.javascriptContext evaluateScript: [NSString stringWithFormat: @"objc_socket.emit(%@);", [arguments componentsJoinedByString: @", "]]];
//    });

I believe that the majority of issues that people have been seeing with instability on iOS 8 (specifically #16) are because most of the callback blocks are running on a WebThread and not the Main Thread. You can see this by adding a log for the current thread in one of the return blocks:

socket.javascriptContext[@"objc_onConnect"] = ^() {
    NSLog(@"javascript thread: %@", [NSThread currentThread]);

    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"main thread: %@", [NSThread currentThread]);
        if (weakSocket.onConnect)
            weakSocket.onConnect();
    });
};

Obviously all callbacks should be called from the main thread.

Guys,

I checked out the master branch of the library and tested this fix on iOS 7.

In my app there is a screen which constantly updates the UI. So when I attempt to emit an event using the library the UI freezes for a second, because events are emitted on the main thread.

From my point of view it is wrong to execute code that is not related to UI on the main queue. That is why, I would suggest to update the code to something like that:

dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    [self.javascriptContext evaluateScript: [NSString stringWithFormat: @"objc_socket.emit(%@);",      [arguments componentsJoinedByString: @", "]]];
});

This approach emits events successfully without freezing the UI.

iliu commented

anislav, the reason this works i believe is because dispatch_sync might optimize this and just call the block on the same thread. the dispatch to main thread in the original code causes threading issues because the javascriptContext will run on 2 different threads. This is effectively the same solution that @jonathannorris had when he commented out the dispatch_async

illu, In my opinion we must use exactly the same queue that JS context uses.
We could get it easily by "dispatch_get_current_queue" but it is deprecated since iOS 6...

Other approach is to create JSContext without WebView, but it is not so easy since socket.io js code uses many variables from it.

Remark:
WebView uses own thread, so it should be just thread, not queue.
Pull request: #42

iliu commented

@zulkis , yup i saw your changes to use NSThread currentThread. I think it should work better than #28 (see my comments in there)

iliu commented

@zulkis, to be safe you might also want to wrap around the evaluateScript call in

// Event listeners
- (void)on:(NSString *)event callback:(void (^)(SIOParameterArray *args))function {
    NSString *eventID = SIOMD5(event);
    self.javascriptContext[[NSString stringWithFormat: @"objc_%@", eventID]] = ^() {
        NSMutableArray *arguments = [NSMutableArray array];
        for (JSValue *object in [JSContext currentArguments]) {
            if ([object toObject]) {
                [arguments addObject: [object toObject]];
            }
        }

        function(arguments);
    };

    [self.javascriptContext evaluateScript: [NSString stringWithFormat: @"objc_socket.on('%@', objc_%@);", event, eventID]];
}

just in case someone keeps a reference to socket and tries to register a callback on a different thread

@Illu, may be, but it is not the case.
But for me it seems weird to add callbacks in different place than creation block :)

iliu commented

@zulkis, fair point, but it's definitely possible. For example, if one writes an app that listens to different socket io events based upon a menu selection, it's possible that they might try to call [self.socket on:@"dynamic event"] directly in the callback block of the ui event, in which most likely it won't be on the web thread that UIWebView is running on.

Yes, they might, it is true. I will implement it when I have free time for that.

How can I get data back from server. I used this code but it does not work
[socket on: requestString callback: ^(SIOParameterArray *args) {
NSString *response = [args firstObject];
NSLog(@"dosad");
}];