0

My problem that I'm facing right now is that whenever user loads up the app. The singleton object will run

Singleton design

import SocketIO

class SocketIOManager: NSObject {

    static let sharedInstance = SocketIOManager()

    var socket: SocketIOClient!

    override init() {
        socket = SocketIOClient(socketURL: URL(string: mainURL)!, .connectParams(["token": getToken()])])
        super.init()
    }

    func establishConnection() {
       socket.connect()

    }

    func closeConnection() {
        socket.disconnect()
    }

    func getToken() -> String {
      if let token = keychain["token"] {
         return token
      }
        return ""
    }
}

Take a look at init() and the .connectParams, in order for the user to connect to the server, token must be present thus the getToken() being passed.

If the token is not there it will initialize the socket object without the token. I run the establishConnection at the applicationDidBecomeActive

func applicationDidBecomeActive(_ application: UIApplication) {
        SocketIOManager.sharedInstance.establishConnection()
}

The token will only be there after the user logs in.

The main question is, is there any way to reinitialized the socket object? or do i use didSet or willSet method?

OverD
  • 2,487
  • 2
  • 12
  • 20
airsoftFreak
  • 1,272
  • 2
  • 26
  • 52

4 Answers4

1

Maybe something like this?

var socket: SocketIOClient! {
  didSet {
    oldValue.closeConnection()
  }
}

It looks like you could probably get rid of the ! too if you want, since you're setting it in your init, assuming SocketIOClient.init returns a non-optional instance.

jefflovejapan
  • 1,907
  • 3
  • 19
  • 33
0

One way to to do that is to create a public method inside SocketIOManager, and use that method to initialize the socket:

func initializeSocket() {
    socket = SocketIOClient(socketURL: URL(string: mainURL)!, .connectParams(["token": getToken()])])
}

And call this method after the user has logged in.

But the way, your initializer must be private in order to implement the Singleton design pattern properly.

Another note is that the initialization of static variables in Swift happens lazily, which means that they only get initialized the first time they are used. Check this answer and the Swift documentation on this topic for more information

Mo Abdul-Hameed
  • 5,516
  • 2
  • 21
  • 32
0

It is simple, You just need to declare a method in your class:

   func resetConnection() {

            socket.disconnect()
           socket = SocketIOClient(socketURL: URL(string: mainURL)!, .connectParams(["token": getToken()])])
socket.connect()

        }

and use in the following

SocketIOManager.sharedInstance.resetConnection()
let socket =  
SocketIOManager.sharedInstance.socket // this will be the newer 
Sucharu Hasija
  • 1,013
  • 10
  • 22
0

First, you are calling this flow from AppDelegate, trouble with this is you depend on this token being present. So what jumps out at me here is that you're missing a method that checks if this token is actually present before initiating the connection, the method should just forgo connecting the socket entirely if you can't produce the token (that is, if your connection is actually token dependent, if it is not then previous answers should help you out).

Since you're right to initialize the socket within the init override of your manager class, it's going against what I think you want, which is to reset a connection once a token does become present if it was not there initially. For this, you should hold back on creating the socket as I mention above.

What I usually do for singletons: I give them a blank "Configure" method, to commit it to memory, usually on AppDelegate's didFinishLaunchin withOptions. If this method contains anything, it's those methods which check for any values the singleton is dependent on, and to assign a custom internal state to the singleton based on those values (like some enum cases). I would then call up establishConnection like you do here, but establishConnection should be a generic method which can run at every appDidEnterForeground method, but without having to worry about altering things, and it should re-establish things that were dropped while your app was backgrounded.

So i'd recommend altering your class to something along the lines of:

import SocketIO

enum SocketIOManagerState {

    case invalidURL
    case launched
    case tokenNotPresent
    case manuallyDisconnected
    case backgroundedByOS

}

class SocketIOManager: NSObject {

    private var state : SocketIOManagerState = SocketIOManagerState.launched

    private var staticSocketURL : URL?

    static let sharedInstance = SocketIOManager()

    var socket: SocketIOClient?

    override init() {
        super.init()
    }

    func configure() {
        //fetch the url string from wherever and apply it to staticSocketURL
        guard let url = URL(string: "The URL from wherever") else {
            state = SocketIOManagerState.invalidURL
            return
        }
        if getToken() == nil {
            state = .tokenNotPresent
        } else {
            //only here can we be sure the socket doesn't have any restrictions to connection
            staticSocketURL = url
            state = SocketIOManagerState.launched
        }
    }

    func evaluateConnection() {
        guard let token = getToken() else {
            //maybe something went wrong, so make sure the state is updated
            if socket != nil {
                return evaluateSocketAsNotNil()
            }
            return closeConnection(true, .tokenNotPresent)
        }
        switch state {
        case .tokenNotPresent, .invalidURL:
            closeConnection(true)
            break
        case .launched:
            //means token was present, so attempt a connection
            guard socket == nil else {
                evaluateSocketAsNotNil()
                return
            }
            guard let url = staticSocketURL else {
                //maybe something went wrong with the url? so make sure the state is updated.
                if socket != nil {
                   return closeConnection(true, .invalidURL)
                }

                return setState(.invalidURL)
            }
            if socket == nil {
                socket = SocketIOClient(socketURL: url, .connectParams(["token": token]))
            }
            socket?.connect()
        default:
            //unless you care about the other cases, i find they all fall back on the same logic : we already checked if the token is there, if we get here, it means it is, so  should we reconnect?
            guard weCanReconnect /*some param or method which you create to determine if you should*/ else {
                //you determine you should not, so do nothing
                return
            }
            //you determine you do, so:

        }
    }

    private func evaluateSocketAsNotNil() {
        guard let sock = socket else { return }
        switch sock.state {
        case .notConnected:
            //evaluate if it should be connected
            establishConnection()
        case .disconnected:
            evaluateSocketAsNotNil()
        case .connecting:
        //do nothing perhaps?
        case connected:
            guard getToken() != nil else {
                //token is not present, but the socket is initialized, this can't happen so disconnect and reset the instance
                closeConnection(true, .tokenNotPresent)
                return
            }
            break //nothing to do here
        }

    }

    private func establishConnection() {
        guard let sock = socket else { return }
        sock.connect()
    }

    func setState(_ to: SocketIOManagerState) {
        self.state = to
    }

    func closeConnection(_ clearMemory: Bool) {
        guard let sock = socket else { return }
        sock.disconnect()
        setState(.launched)
        if clearMemory {
            socket = nil
        }
    }

    private func closeConnection(_ clearMemory: Bool,_ to: SocketIOManagerState) {
        socket?.disconnect()
        setState(to)
        if clearMemory {
            socket = nil
        }
    }

    func getToken() -> String? {
        guard let token = keychain["token"] else {
            state = .tokenNotPresent
            return nil }
        return token
    }
}

And your AppDelegate would then look like this:

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

        SocketIOManager.sharedInstance.configure()

        return true
    }


    func applicationDidEnterBackground(_ application: UIApplication) {
        SocketIOManager.sharedInstance.closeConnection(false, .backgroundedByOS)
    }


    func applicationDidBecomeActive(_ application: UIApplication) {
        // 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.

        SocketIOManager.sharedInstance.evaluateConnection()
    }

From here, you can always call evaluateConnection() and closeConnection(_:, _:) anywhere else in the app, and add more state cases, and more ways to handle those cases logically. Either way, it's up to you to determine how you should connect and reconnect based on the token.

With this structure, if your user logs in, and you set your token properly in your app, you should then be able to connect the socket properly when calling evaluateConnection during the login process. There's also alot of comments, and some things might seem generic (apologies), but it's up to you to fill in the blanks for your use-case.

Hope it helps!

jlmurph
  • 1,010
  • 7
  • 17
  • Hey thanks a lot for putting your effort for this, I'm testing this out right now. – airsoftFreak Nov 20 '17 at 15:25
  • Just wondering, are you using socket.io swift for any of your projects? – airsoftFreak Nov 20 '17 at 15:27
  • I tried it and it failed. So technically what is happening is the app crashes when it loads up. The reason because socket is nil and didBecomeActive runs after the view already loaded thus why the socket is nil – airsoftFreak Nov 20 '17 at 16:09
  • Hello, yeah i use it rather regularly. I believe i forgot some nil checks in some of the sub-methods in evaluateConnection(). If i'm not mistaken that would be what's causing the crash? I've updated with some of the checks. If it still doesn't owrk let me know – jlmurph Nov 20 '17 at 16:38
  • Do you use it with token often? – airsoftFreak Nov 20 '17 at 16:49
  • The cause of the crash is because didBecomeActive runs after the view controller has loaded. – airsoftFreak Nov 20 '17 at 16:49
  • Maybe we create a chat room for solving this problem – airsoftFreak Nov 20 '17 at 16:50
  • Just to let you know I `establishConnection` whenever user logs in and on the app delegate which is in the `applicationDidBecomeActive` – airsoftFreak Nov 20 '17 at 16:52
  • Yeah sure. Also, for the token, i do but i just realized we don't use the same approach. I follow this thread's solution at the bottom. The poster actually has the same token header configuration you have and it didnt work for him apparently. https://github.com/socketio/socket.io-client-swift/issues/382 – jlmurph Nov 20 '17 at 16:52
  • It does work before, but after I changed to IOS 11, it crashes because of the applicationDidBecomeActive, the socket is always nil – airsoftFreak Nov 20 '17 at 16:57
  • Here's the link https://chat.stackoverflow.com/rooms/159403/socket-io-room , hopefully we could solve this problem. Thanks for helping – airsoftFreak Nov 20 '17 at 16:59