Created
June 3, 2020 13:23
-
-
Save AdrianBinDC/983012f0b3ec9d9ba26cf94d9d2d1d4c to your computer and use it in GitHub Desktop.
Stub implementation for ChatEngine for Pursuit Students
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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