Skip to content

Instantly share code, notes, and snippets.

@AdrianBinDC
Created June 3, 2020 13:23
Show Gist options
  • Save AdrianBinDC/983012f0b3ec9d9ba26cf94d9d2d1d4c to your computer and use it in GitHub Desktop.
Save AdrianBinDC/983012f0b3ec9d9ba26cf94d9d2d1d4c to your computer and use it in GitHub Desktop.
Stub implementation for ChatEngine for Pursuit Students
import UIKit
/*
This is a high-level physics for poets sketch of an implementation for
sending and receiving messages. YOU'LL NEED TO REFINE IT.
If you want to implment encryption, I'd use a framework like CryptoKit,
or some well-known, public framework w/ a lotta stars, recent commits,
and not a lot of issues on the GitHub page.
There are loose ends on this implementation. I also don't know how the data will come
from firebase, so instead of handling a single message, you might need to
decode an array of messages.
Just to keep it simple, I used the delegation pattern. On your collectionView
you'll need to implement the ChatEngineDelegate method. As you refine this
code, you want to have separation of concerns between the methods and your
goal is a single point of failure...that way, if something goes wrong,
you're not hunting over the river and throught the woods to figure out
where the problematic code lives.
On your viewController, you'll want to write an extension and
implement the delegate methods there.
*/
struct User: Codable, Equatable, Hashable {
let username: String
static func == (lhs: User, rhs: User) -> Bool {
lhs.username == rhs.username
}
func hash(into hasher: inout Hasher) {
hasher.combine(username)
}
}
struct Message: Codable, Hashable {
let date: Date = Date()
let sender: User
let recipient: User
let message: String
static func == (lhs: Message, rhs: Message) -> Bool {
lhs.date == rhs.date && lhs.sender == rhs.sender && lhs.recipient == rhs.recipient && lhs.message == rhs.message
}
func hash(into hasher: inout Hasher) {
hasher.combine(date)
hasher.combine(sender)
hasher.combine(recipient)
hasher.combine(message)
}
}
protocol ChatEngineDelegate: class {
func refreshDataSource()
}
class ChatEngine {
let sender: User!
let recipient: User!
weak var delegate: ChatEngineDelegate?
init(sender: User, recipient: User) {
self.sender = sender
self.recipient = recipient
// we call this when it's initialized
retrieveMessageHistory()
}
// We initialize with an empty array. didSet only gets fired
// when messages is assigned or updated, not when it's initialized
var messages: [Message] = [] {
didSet {
messages = messages.sorted(by: { $0.date < $1.date })
delegate?.refreshDataSource()
}
}
// MARK: - Data Transfer
private func retrieveMessageHistory() {
// Write networking code to get the messages between sender and recipient
// retrieve messages and set the messages array
}
public func send(message: Message) {
// exit early if the message doesn't get encoded
guard let encodedMessage: Data = encode(message: message) else {
return
}
// send a message...write networking code to do it
// you may want to encrypt the messages before you send them
// encryption goes in the encode method, not here
messages.append(message)
}
public func recieve(messageData: Data) {
// exit early if the message doesn't get decoded
// while you don't need to specify what decodedMessage is,
// it can save you some time on type inference
// to make that determination, you can use measure in XCTestCase
guard let decodedMessage: Message = decode(data: messageData) else {
return
}
// receive a message
// you may want to decrypt the messages when you recieve them
// do decryption in the decode method
messages.append(decodedMessage)
}
/*
The encoding/decoding stuff should be private. When you write tests
for it, you'll need to use expectations and this code will get tested
upon the send & receive methods above. Those are public facing,
so they can be seen outside the class. The stuff blow will get called
by the stuff above.
*/
private func encode(message: Message) -> Data? {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
// you'll need to handle encryption here if you decide to implement that
return try? encoder.encode(message)
}
private func decode(data: Data) -> Message? {
// you'll need to handle decryption here if you decide to implement that
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return try? decoder.decode(Message.self, from: data)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment