I am using socket.io swift client in my app (version 13.1.0) Things work fine, but sometimes this scenario occurs:
Socket gets disconnected (3 out of 10 times). After it gets disconnected it tries and gets connected successfully, but after this it gets disconnected again and this forms an infinite loop of connected - disconnected state. I am searched but didn't get any possible solution.
My code is very simple. I have created a singleton class and its code is given as follows:
class SocketIOManager: NSObject {
static let sharedInstance = SocketIOManager()
let manager = SocketManager(socketURL: URL(string: "URL")!, config: [.compress, .log(true), .reconnects(false)])
override init() {
super.init()
let socket = manager.defaultSocket
socket.on("connect") {data, ack in
NSLog("*************** SOCKET CONNECTED")
}
}
func establishConnection() {
let socket = manager.defaultSocket
socket.connect()
}
}
Other than this I am elegantly disconnecting socket when app goes in background and then connecting it again when app comes in foreground.
Things I have tried:
Tried the configs parameters: forceNew(true/false), reconnects(true/false), forceWebsockets(true/false)
In order to keep the connection active, I am sending an empty parameter to the server every 10 seconds, so that it knows that the app is in active state.
My observation:
I am using Alamofire in the same project and it send requests in background thread. Might be possible that at some point of time both libs start using the same background thread and socket connection breaks.
Is this possible? Correct me if I am wrong in my understanding.
What should I do to overcome this socket disconnection problem. I am a newbie to iOS so any help would be appreciated.
Related
I am using swift 4 and Socket.IO-Client-Swift, '~>13.1.1' (latest version).
I tried to call socket.connect() after adding socket handlers but I was getting an error "Tried connecting socket when engine isn't open". So I have added socket.connect() code inside a timer and calling it after 5 seconds, so engine was already opened by that time and not getting that error anymore.
But now I am getting the error "Tried connecting on an already connected socket", even though connect client event handler was never called.
I have gone through the documentation and all the issues that were posted on git but no explanation on how to solve this or why this error even occurs.
You can find the code and console log below.
let manager = SocketManager(socketURL: URL(string: "https://socket.******.**")!, config: [.log(true),.connectParams(["token":Utils.getToken()]),.reconnects(true)])
socket = manager.socket(forNamespace: "/**********")
console log:
2018-02-01 02:20:00.810496+0530 *******[2476:1630437] LOG SocketIOClient{/**********}: Adding handler for event: connect
2018-02-01 02:20:00.837403+0530 *******[2476:1630437] LOG SocketIOClient{/**********}: Adding handler for event: message
2018-02-01 02:20:00.838168+0530 *******[2476:1630437] LOG SocketManager: Manager is being released
2018-02-01 02:20:05.815458+0530 *******[2476:1630437] LOG SocketIOClient{/**********}: Tried connecting on an already connected socket
From the console log, you can see that I am getting "Manager is being released" error msg. Is there a way to retain the manager object? Is that the main reason behind this issue?
Sockets created through the manager are retained by the manager. So at the very least, a single strong reference to the manager must be maintained to keep sockets alive. (As per docs, https://nuclearace.github.io/Socket.IO-Client-Swift/Classes/SocketManager.html)
Easiest way to fix this is create singleton class to retain the manager object
open class SocketConnection {
open static let `default` = SocketConnection()
private let manager: SocketManager
private var socket: SocketIOClient
private init() {
manager = SocketManager(socketURL: URL(string: "https://socket.******.**")!, config: [.log(true),.connectParams(["token":Utils.getToken()]),.reconnects(true)])
socket = manager.socket(forNamespace: "/**********")
}
}
I'm running SocketIO-Client-Swift in order to open WebSockets and communicate with a server. However the problem I'm having is that I want to connect to the socket and then connect to the server with an emit.
SocketIOManager.sharedInstance.connectToSocket()
SocketIOManager.sharedInstance.connectToServer(self.myUsername)
And those point to functions here:
func connectToSocket() {
addHandlers()
socket.joinNamespace("/chat")
socket.connect()
}
func addHandlers() {
self.socket.on("connectUser") { data in
print("socket connected")
}
}
func connectToServer(username: String) {
socket.emit("connectUser", username)
}
Even after registering the handle, the emit is being called before the socket is properly connected. The connect works fine after the connect has been established as I can properly send messages and get feedback after my Log shows a connection.
You need to wait for the client to receive the connect event. That will signal that it is now connected and you can safely send initial data on the connection from that event handler.
I am playing around with socket.io library, I already implemented the server side (in node.js). I try to follow a very simple scenario:
1) user establishes connection
2) user connects to the room
3) user disconnects
4) user reconnects
I'm using ios app (written in swift) on a client side.
My server code is as follows:
io.on('connection', function(clientSocket){
console.log('a user connected here');
clientSocket.on('disconnect', function(){
console.log('user disconnected');
});
clientSocket.on('connectUser', function(username){
clientSocket.join(username);
console.log('user ' + username + ' connected to the room.');
});
Now in my client side I created a class responsible for handling all socket stuff:
class SocketIOManager: NSObject {
static let sharedInstance = SocketIOManager()
var socket: SocketIOClient = SocketIOClient(socketURL: NSURL(string: serverURL)!, options: [.ForceNew(true)])
override init() {
super.init()
}
func establishConnection() {
socket.connect()
}
func closeConnection() {
socket.disconnect()
}
func connectToServerWithNickname(nickname: String) {
print("CONNECTING TO SOCKET SERVER \(nickname)")
socket.emit("connectUser", nickname)
}
}
Now - further on - in my swift app (in applicationDidBecomeActive) I'm calling establishConnection() and then SocketIOManager.sharedInstance.connectToServerWithNickname(username).
In my server console I see:
a user connected here
user 571fc6818451630f5becda9c connected to the room.
In my appDelegate I implemented this function:
func applicationWillResignActive(application: UIApplication) {
SocketIOManager.sharedInstance.closeConnection()
print("closing sockets")
}
so now when I put my app in the background I see closing sockets and in my server console I see user disconnected.
And now to the main problem - I want to reconnect user when he comes back to the app. Therefore I implemented this method:
func applicationDidBecomeActive(application: UIApplication) {
print("application is back to life")
SocketIOManager.sharedInstance.establishConnection()
SocketIOManager.sharedInstance.connectToServerWithNickname(username)
}
But now when I wake up the app from the background, in my server console I only see a user connected here. So this message:
user 571fc6818451630f5becda9c connected to the room.
is missing and I assume it's because my app is not reconnecting to the socket.
Now from what I've read, socket.io is really buggy and people recommend switching to other libraries, but I would like to use it anyway. I found this issue on github https://github.com/socketio/socket.io-client/issues/251 and they recommend to use a specific parameter on client side. Following this plugin's page for swift https://github.com/nuclearace/Socket.IO-Client-Swift I found the specific flag:
case ForceNew(Bool) // Will a create a new engine for each connect. Useful if you find a bug in the engine related to reconnects
and that is why I'm constructing my socket on client side as:
var socket: SocketIOClient = SocketIOClient(socketURL: NSURL(string: serverURL)!, options: [.ForceNew(true)])
but yeah, even in the official code of the Socket.IO-Client-Swift library there's a comment above the disconnect method:
/// Disconnects the socket. Only reconnect the same socket if you know what you're doing.
/// Will turn off automatic reconnects.
public func disconnect() {
DefaultSocketLogger.Logger.log("Closing socket", type: logType)
reconnects = false
didDisconnect("Disconnect")
}
Does anyone know how could I handle that? I want to close the socket each time my app goes to the background and connect to it again when my app goes to the foreground. Thanks!
add your code in applicationWillEnterForeground
SocketIOManager.sharedInstance.establishConnection()
SocketIOManager.sharedInstance.connectToServerWithNickname(username)
I am writing an ios app that relies on being able to tell when a user is connected to wifi and, when he or she connects or disconnects, send an asynchronous request using alamo fire.
The first time I connect, my asynchronous succeeds.
However, after I first connect, any toggling of the wifi results in 404s.
I suspect this is because I am sending the request as soon as the user connects/disconnects, meaning that for a brief moment he or she has no internet service.
My question is, can I repeat the request if it fails or is it possible to "cache" the requests I want to make and wait until the user has internet connection to make them?
There are many solutions to solve this. One is to call the download method recursively again and so implementing an automatic retry mechanism on errors:
func downloadSomething() {
Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
.response { request, response, data, error in
if let error = error {
log(error)
self.downloadSomething() // recursive call to downloadSomething
} else {
// do something on success
}
}
}
You can extend this by:
showing the user also an altert view asking him if he want's to retry
the download or not before retrying the download. (depending on your
UI strategy on network errors)
a specified count of automatic re-trys and then ask the user.
checking the error status code and then depending on the code do
different network error handling strategies...
etc...
I think there is no needed to re-invented apple code like reachability or this swift reachability porting. You can able to check if a user is connected to the net or wifi very easily:
class func hasConnectivity() -> Bool {
let reachability: Reachability = Reachability.reachabilityForInternetConnection()
let networkStatus: Int = reachability.currentReachabilityStatus().rawValue
return networkStatus != 0
}
For a Wi-Fi connection:
(reachability.currentReachabilityStatus().value == ReachableViaWiFi.value)
Using a Wi-Fi socket based adapter, I can successfully poll for a response like so:
func writeMessageWithResponse(message: String) -> [String] {
self.waitingForResponse = true
let runLoop = NSRunLoop.currentRunLoop()
if self.response != nil {
self.response?.removeAll()
}
writeMessage(message) // this will set self.waitingForResponse to false when a response is received
while self.waitingForResponse && runLoop.runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture()) {
// waiting for flag (self.waitingForResponse) to be set
}
return self.response!
}
When I use this same code with a CBCentralManager BLE connection, the main thread is blocked and does not receive the response from the connection. I've tried changing the CBCentralManager to a different queue, but I get the same results.
Does anybody have an idea how to wait in a loop and still be able to receive a BLE response? I know a response is being sent, but the thread is blocked and not reading it.
Using an async function with a completionHandler won't work for this use case because I need a reusable function that can issue a chain of commands that each depend on the result of the last response.
The CBCentralManager and CBPeripheral APIs are not really designed for this. You should be using the methods provided by CBPeripheralDelegate and CBCentralManagerDelegate protocols. Initialize your central manager with a dedicated queue so that you can listen for responses to your peripherals and act on that data as appropriate.