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?
1 Answer 1
- 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 aURL
orURLRequest
as well as aWebSocketDelegate
. 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
ChatMessage
s) 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
implementsChatMessageHandling
which has some functions for theChatWebSocketDelegate
to call something likereceived(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 implementChatMessageListening
in anextension
andregister
andderegister
itself with theChatMessageHandler
- Typically register on
init
orviewDidLoad
- 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 theregister
function so you don't need to remember it every time
Explore related questions
See similar questions with these tags.