applicationDidBecomeActive runs after ViewController loaded

Viewed 355

I'm using socket.io to use the real-time mechanism. Problem that I'm facing right now is that Socket.io's object created after the viewController is loaded

I'm using Singleton design

import SocketIO

class SocketIOManager: NSObject {

    static let sharedInstance = SocketIOManager()

    var socket: SocketIOClient!

    func establishConnection() {
       socket = SocketIOClient(socketURL: URL(string: mainURL)!, config: [.log(false), .compress, .connectParams(["token": "asdasdasdsa"])])
       socket.connect()

    }

    func closeConnection() {
        socket.disconnect()
    }

}

I need to set the socket in the establishConnection no matter what to make sure that user has establish the connection to the server together with the token. If I follow Vlad's answer, the connection will be never be established because the token is empty before hand thus user won't get connected.

User logs in

func logIn() {
   // Getting it from alamofire
   keychain["token"] = token
   //then only establish connection
   SocketIOManager.sharedInstance.establishConnection()
}

If I use Init method then the socket will be nil because in the applicationDidBecomeActive there's the SocketIOManager.sharedInstance.establishConnection(), so the logic is whenever user loads up the app the applicationDidBecomeActive will run

so technically I'm using sharedInstance to run the socket.io object.

func applicationDidBecomeActive(_ application: UIApplication) {
        SocketIOManager.sharedInstance.establishConnection()
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

Right now the socket object in the didBecomeActive runs after the view loaded

override func viewDidLoad() {
    super.viewDidLoad()
    listeningOnMerchant()

}

func listeningOnMerchant() {
   // I got error around here
   SocketIOManager.sharedInstance.socket.on("listening") { ack, data in
      print("something")


   }
  }
}

Which will crash because socket is nil. How do I make the viewController not crash. I could use the let method to stop the crash

if let socket = SocketIOManager.sharedInstance.socket {

}

But it will be nil if I use socket and socket won't be running. How do I not make the socket not crash?

Because i really need to put the socket.connect on applicationDidBecomeActive to make it consistent as in the connection vs didLaunch

Thanks!

4 Answers

Technically to solve this problem you need to use the init method so that the socket won't be nil. Since you need the socket to be running when the app paused and so on, you need to use the init method no matter what.

   private override init() {
        self.socket = SocketIOClient(socketURL: URL(string: mainURL)!, config: [.log(true), .compress, .forceNew(true), .connectParams(["token": getToken()])])
        super.init()
    }

Add forceNew(true) so that you won't create multiple connections for one user

The next step you need to do is to create another function that only your loginViewController can access

 func establishConnectionWhenLogin(_ token: String) {
        self.socket =  SocketIOClient(socketURL: URL(string: mainURL)!, config: [.log(false), .compress, .connectParams(["token": token])])
        socket.connect()

    }

pass the token to the parameter so that only user has logged in then you want to establish the connection. If you have any problems let me know

I use this solution for my own app and it works perfectly

As I understand, your problem is in setting SocketIOClient at the establishConnection func. If it crashes with nil, probably your constructor returns nil here:

SocketIOClient(socketURL: URL(string: mainURL)!, config: [.log(false), .compress, .connectParams(["token": "asdasdasdsa"])])

I don't see a reason also to do this inside this func. It's easy to make a mistake and leave socket var uninitialised at some point. I would rather make it let and set it inside of constructor, something like this:

import SocketIO

class SocketIOManager: NSObject {

    static let sharedInstance = SocketIOManager()

    let socket: SocketIOClient!

    init() {
        super.init()
        socket = YourFixedConstructorHere()
    }
}

If you want to make sure it's initialised, you can also make it lazy var, like this:

lazy var socket = SocketIOClient(socketURL: URL(string: mainURL)!, config: [.log(false), .compress, .connectParams(["token": "asdasdasdsa"])])

I think the problem is a timing problem. You should call your singleton BEFORE the ViewController did load the view. So you better put this code

SocketIOManager.sharedInstance.establishConnection()

into a function (that is performed earlier) in AppDelegate

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

         SocketIOManager.sharedInstance.establishConnection()

    return true
}

If I understand your question correctly, at some point you can't use establishConnection() because you lack the token. This logic doesn't entirely fit with your singleton pattern, as your object's responsibility is only to establish / cancel connection and it already can't do that after instance created. I would rather use service here, like this:

class SocketIOService {
    let token: String // I think if token is related to SocketIO, you can store it here
    init(token: String) {
        self.token = token
    }
    // Your code to establish connections, etc
}

This way you would guaranty, that your entire service can't be created without token. Pretty better, as for me

Options with singleton pattern are not so good, but they definitely existed. First, you can add parameter to your method:

func establishConnection(withToken token: String) {
    // code
}

With this method, you will require token to be passed in, so without it user won't be able to establish connection.

Then, wherever you want to use it you can do like this:

if let token = // Your code to retrieve token {
    SocketIOManager.shared.establishConnection(withToken: token)
}

or even:

keychain["token"].flatMap {SocketIOManager.shared.establishConnection(withToken: $0)}

Pretty nice, huh?

There several other ways of doing this, but with all them your workflow is almost the same - at first you check, if you have a token, then you call this method. If you not succeed - after you retrieve token, try again.

Related