Berry Blue
Berry Blue

Reputation: 23

Swift: How to use response from SocketIO emitWithAck method

I'm using the SocketIO library to connect my iOS app to my server. I want to emit some data to the server and get a json dictionary back in the acknowledgment. I currently have something like this:

SocketHandler.mySocket.emitWithAck("my_event", [session, someInput]).timingOut(after: 3) {data in

       let myData = try? JSONDecoder().decode(myStruct.self, from: data)

MyStruct is defined as Class inheriting from Decodable and resembles the structure of the json I expect.

I get the following error: Cannot convert value of type 'Any' to expected argument type 'Data'

Any idea how I can tackle that type casting? Or would I need to go a totally other route?

(Swift 4.1 for iOS 11.3)

Cheers!

Upvotes: 2

Views: 3033

Answers (2)

Marin Bencevic
Marin Bencevic

Reputation: 321

If anyone else is wondering how to use SocketIO with Decodable, I created a little extension for the client to accept Decodable in the callback, based on Dan Karbayev's answer.

import Foundation
import SocketIO

extension Decodable {
  init(from any: Any) throws {
    let data = try JSONSerialization.data(withJSONObject: any)
    self = try JSONDecoder().decode(Self.self, from: data)
  }
}

extension SocketIOClient {

  func on<T: Decodable>(_ event: String, callback: @escaping (T)-> Void) {
    self.on(event) { (data, _) in
      guard !data.isEmpty else {
        print("[SocketIO] \(event) data empty")
        return
      }

      guard let decoded = try? T(from: data[0]) else {
        print("[SocketIO] \(event) data \(data) cannot be decoded to \(T.self)")
        return
      }

      callback(decoded)
    }
  }
}

Usage:

socket.on("location") { (data: LocationEventData) in
  // ...
}

socket.on("success") { (name: String) in
  // ...
}

Where LocationEventData and String are Decodable.

Upvotes: 6

Dan Karbayev
Dan Karbayev

Reputation: 2920

There're two things:

  1. decode(_:from:) accepts a Data as a second parameter. To be able to decode from Any you'll need to add an extension to first serialize the data and then pass it to JSONDecoder, like this:

    extension Decodable {
      init(from any: Any) throws {
        let data = try JSONSerialization.data(withJSONObject: any)
        self = try JSONDecoder().decode(Self.self, from: data)
      }
    }
    
  2. AckCallback's parameter is of an array type (i.e. [Any]), so you should get the first element of that array.

To make sure that you have indeed a decodable data (a dictionary or a JSON object) you can write something like this:

SocketHandler.mySocket.emitWithAck("my_event", [session, someInput]).timingOut(after: 3) { data in
  guard let dict = data.first as? [String: Any] else { return }
  let myData = try? myStruct(from: dict)
  // ...
}

Upvotes: 3

Related Questions