2
\$\begingroup\$

I have a UIViewController with a Socket Connection established once viewDidLoad.

This web socket is a multi-directional channel to exchange data between the iOS client app and the backend.

I have implemented the delegate methods and the ViewController listens to the updates from the socket. The received data is a JSON String, I convert it to JSON object using SwiftyJSON library. The JSON will be one of a 3 possible types I have already created the data model for. Then, I parse the JSON, create an object from my pre-defined Types, and append to an array. All done in the delegate method.

This is not a good coding practice because:

  • I want to be able to use the established socket connection in different parts of the app.
  • I want to abstract the JSON parsing into different parts so I just check the type of the model I need to create, and this calls the appropriate JSON parses.

Here's an example of the delegate method running in my ChatLogViewController

private var socket: WebSocket?
private var chatTableViewCell = [ChatTableViewCell]()

override func viewDidLoad() {
    super.viewDidLoad()

    self.socket = WebSocket(url: URL(string: streamUrl!)!)
    self.socket?.delegate = self
    self.socket?.connect()
}


func websocketDidReceiveMessage(socket: WebSocket, text: String) {
    let jsonObj = JSON(parseJSON: text)
    let text = activity["text"].string
    let entities = activity["entities"].arrayValue

    for dict in entities {

        if let type = dict["type"].string {

            if type == "restaurant" {
                // parse the JSON and create Resturant object and append to the array
                if let restaurants = dict["restaurant"].array {
                    var restaurantsHolder = [Restaurant]()
                    for restaurant in restaurants {
                        guard let name = restaurant["name"].string, let image = restaurant["image"].string else { return }
                        let restaurant = Restaurant(name: name, vendorLogo: URL(string: image))
                        restaurantsHolder.append(restaurant)
                    }
                    addCardOfType(restaurant: restaurantsHolder)
                }
            } else if type == "category" {
                // parse the JSON and create Category object and append to the array
                if let categories = dict["category"].array {
                    var categories = [Category]()
                    for category in categories {
                        guard let name = category["name"].string, let imageStringUrl = category["imageUrl"].string else { return }
                        let botModel = category.description
                        let category = Category(name: name, imageUrl: imageStringUrl)
                    categories.append(category)
                    }
                    addCardOfType(categories: categoriesHolder)
                }
            } else if type == "item" {
                // parse the JSON and create Item object and append to the array
                if let items = dict["item"].array {
                    var itemsPreview = [Item]()

                    for item in items {
                        guard let itemTitle = item["title"].string,
                        let restaurantLogo = item["_restaurant"]["image"].string,
                        let priceArray = item["price"].array,
                        let priceObj = priceArray[0].dictionary,
                        let price = priceObj["price"]?.string,
                        let images = item["images"].string
                        else { return }
                        let itemPreview = Item(name: itemTitle, price: Double(price)!, productImageUrl: URL(string: stringValue), vendorImageUrl: URL(string: restaurantLogo))

                         itemsPreview.append(itemPreview)
                     }
                     addCardOfType(item: itemsPreview)
                }

        }
    }

}


private func addCardOfType(restaurant: [Restaurant]) {
    let customizationCard = ChatTableViewCell(message: nil, itemCustomizations: nil, restaurant: restaurant, categories: nil, item: nil)
    self.chatTableViewCell.append(customizationCard)
    self.addTableViewCellFrom(edge: UITableViewRowAnimation.left)
}

private func addCardOfType(categories: [Category]) {
    let categoryCard = ChatTableViewCell(message: nil, itemCustomizations: nil, restaurant: nil, categories: categories, item: nil)
    self.chatTableViewCell.append(categoryCard)
    self.addTableViewCellFrom(edge: UITableViewRowAnimation.left)
}

private func addCardOfType(item: [Item]) {
    let itemsPreviewCard = ChatTableViewCell(message: nil, itemCustomizations: nil, restaurant: nil, categories: nil, item: item)
    self.chatTableViewCell.append(itemsPreviewCard)
    self.addTableViewCellFrom(edge: UITableViewRowAnimation.left)
}

//once my array gets appended to, I call the following method to insert a row to my tableView, this row is the last item in the array

private func addTableViewCellFrom(edge: UITableViewRowAnimation) {
    let lastItem = IndexPath(item: self.chatTableViewCell.count - 1, section: 0)
    self.tableView.beginUpdates()
    self.tableView.insertRows(at: [lastItem], with: edge)
    self.tableView.endUpdates()
    self.scrollToBottom()
}

So my questions are:
a) How should I isolate the delegate method into a different part?
b) What is the best way to create a parser that listens to the incoming type and do the JSON parsing job, and just returns the data to the ViewController?

\$\endgroup\$
1
\$\begingroup\$
  • Any part of the code that understand how the data is fetched or how the data is structured is considered Model
  • You could make a WebSocketController that accepts a URL or URLRequest as well as a WebSocketDelegate. This makes it easy to create new WebSockets for new types of connections
  • Now if you want to get the data in different parts of the app depending where you are you need to coordinate who gets a new message when. Typically you would create a Singleton-like class like ChatMessageHandler.
  • It will do two things: allow listeners to register and deregister themselves and it will forward messages (in this case ChatMessages) it receives from the WebSocket. The WebSocket should be built something like this:

    let delegate = ChatWebSocketDelegate(delegate: self) let socket = WebSocketController(connectingTo: url, delegate: delegate)

  • Your ChatMessageHandler implements ChatMessageHandling which has some functions for the ChatWebSocketDelegate to call something like received(newMessages: [Array])

  • This function will go over the array of registered listeners ([ChatMessageListening?] *) and send them the new messages. *retain loops!
  • Sometimes there's a bit of logic like if you're watching the chat you receive the messages for you won't get a toaster notification in-app. This logic also lives in ChatMessageHandler.
  • Your ViewController (or any object that wants to receive updates) only needs to implement ChatMessageListening in an extension and register and deregister itself with the ChatMessageHandler
  • Typically register on init or viewDidLoad
  • Deregister on deinit
  • Remember to send weak var weakSelf = self as a listener otherwise you'll create retain loops! Another option is to make a weak copy inside the register function so you don't need to remember it every time
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.