Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
✓ Live Streaming✓ Interactive Chat✓ Private Shows✓ HD Quality
Anya is LIVE right now
FREE
Free to watch • No registration required • HD streaming
Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
✓ Live Streaming✓ Interactive Chat✓ Private Shows✓ HD Quality
Anya is LIVE right now
FREE
Free to watch • No registration required • HD streaming
My tolerance for adopting 3rd party libraries is relatively low - and networking is no different. In this post I describe a pattern to help write your own networking code.
Preface: Swift 4 has the awesome new Codable type, so some of this code may not apply if you're using Swift 4.
Motivation
Sometimes we're unable to use third party code, or it may not make sense for you. Networking is probably an area where I find using something like Alamofire acceptable given it's immense community support. But still, it's pretty darn big. How much of it do you actually need? If you ever decide to write your own, here's an approach that might work for you.
The code
The complete code is in this GitHub Gist.
After watching this objc.io Networking video. Actually using it wasn't working out. In fact, a big mess was made. It was a classic case of over-engineering. It was all thrown out and code continued to be written the way it should be at first: simple, and with duplication. After all the kinks were worked out, refactoring reduced it to the correct abstractions. It was practice of one of the most valuable programming lessons I learned (and lucky me, in person) from the amazing Sandi Metz - which is
Don't abstract too early. - Not a direct quote, but a message from Sandi Metz.
When done, some of the abstractions started to look like ones from the video :).
Major components
WebService
The highest level abstraction which provides the public API. It's a domain descriptive API consuming and providing domain objects. It creates Actions and sends them to the ActionManager for processing.
public struct WebService { let actionManager: ActionManager
// As public API, optimize for simplicity by only providing a Result and not throwing. func getProfile(id: String, completion: @escaping (Result<profile>) -> Void) { let action = Action<nsnull profile>(urlString: "api.com/profile", method: "GET", body: NSNull()) perform(action, completion: completion) } func createProfile(_ profile: Profile, completion: @escaping (Result<nsnull>) -> Void) { let action = Action<profile nsnull>(urlString: "api.com/profile", method: "POST", body: profile) perform(action, completion: completion) } private func perform<req res>(_ action: Action<req res>, completion: @escaping (Result<res>) -> Void) { do { try actionManager.perform(action) { completion($0) } } catch { completion(Result.failure(error)) } }
}
Since this is the public interface, we want to keep it as simple as possible by not throwing and only providing a Result. A Result is widely implemented Swift Enum:
public enum Result<value> { case success(Value) case failure(Error) }
The other thing to note here is the creation of Action objects, which is the crux of how all of this works.
Action
This encapsulates the behaviour and data for a round trip network call. Domain object -> Network -> Domain object.
internal struct Action<requestbody: encodable responsebody: decodable> { let urlString: String let method: String let body: RequestBody let decode = ResponseBody.decode // Anything else you may need like tokens, headers, etc. func request() throws -> URLRequest { guard let url = URL(string: urlString) else { throw WebServiceError.invalidURL(urlString) } var request = URLRequest(url: url) request.httpMethod = method request.httpBody = try body.encode() return request } }
This type requires specialized generic types for the request body, and the response body. I actually don't like these names much, but haven't thought of better ones yet. RequestBody and ResponseBody would be generally domain types which conform to Encodable and Decodable which serialize domain objects to JSON and vice versa:
public protocol Encodable { func encode() throws -> Data } public protocol Decodable { static func decode(data: Data) throws -> Self }
Here it is impelemented on our Profile domain object:
public struct Profile: Encodable, Decodable { let name: String let age: Int public static func decode(data: Data) throws -> Profile { guard let object = try JSONSerialization.jsonObject(with: data) as? [String: Any], let name = object["name"] as? String, let age = object["age"] as? Int else { throw WebServiceError.invalidJSON(data) } return self.init(name: name, age: age) } public func encode() throws -> Data { let object: [String: Any] = ["name": name, "age": age] return try JSONSerialization.data(withJSONObject: object) } }
We also implement these protocols on NSNull because we want to be able to send and receive empty bodies. So we can make use of the Null Object Pattern to achieve "same" code and less code paths. At first, creating a new null type was considered, but why when we have good ol' Foundation's NSNull sitting around!
extension NSNull: Encodable, Decodable { public static func decode(data: Data) throws -> Self { return self.init() } public func encode() throws -> Data { return Data() } }
Further, Action produces a native URLRequest that is used with NSURLSession later on to make the actual network call.
ActionManager
This performs Actions and knows how to talk to the lower level HTTPManager. It creates URLRequests and deserializes responses.
internal struct ActionManager { let httpManager: HTTPManager func perform<req res>(_ action: Action<req res>, completion: @escaping (Result<res>) -> Void) throws { let request = try action.request() httpManager.performRequest(request) { result in switch result { case .success(let data): do { completion(Result.success(try action.decode(data))) } catch { completion(Result.failure(error)) } case .failure(let error): completion(Result.failure(error)) } } } }
We can see perform is generic on the same types as the Action it accepts and calls back with a Result with Res value, which would be a domain type.
HTTPManager
The lowest level abstraction that implements URLSession.
internal struct HTTPManager { let urlSession: URLSession func perform(_ request: URLRequest, completion: @escaping (Result<data>) -> Void) { urlSession.dataTask(with: request) { data, response, error in if error != nil { return completion(Result.failure(error!)) } let response = response as! HTTPURLResponse // Trusting the docs for this example! // Could pass in an object that deals with status codes, which would be part of the Action. guard response.statusCode == 200 else { return completion(Result.failure(WebServiceError.invalidStatusCode(response.statusCode))) } completion(Result.success(data ?? Data())) }.resume() } }
It does the network call work and verifies an expected response status code. Notice, it calls back with empty Data when the response doesn't have one - instead of (optional) Data?. This works because when we expect an empty response, our ResponseBody type would be, NSNull. And it can handle decoding empty Data. Thus, we can avoid the complexity of passing through an optional here.
Conclusion
Again, the complete code is in this GitHub Gist. If you're considering writing your own network code, I hope this approach can help and I welcome any feedback you may have!