Working with backend services on iOS

4 Likes

Nice article.

I’m curious about your use of Structs & Protocols over Classes. Still getting to grips with such Swift concepts…

What was your thought process?

Hey Steven, thanks!

We’re a big fan of value types in Swift, since they provide proper immutability out of the box. Given that classes are reference counted, they often have unwanted side effects when copying or passing between other reference counted objects.

In general, we default to a value type unless we need reference counting.

For protocols, this gives us a way to define common interfaces for objects without resorting to inheritance, so again - we’re big fans of this. It allows us to standardise designs for our models in particular, and means that interfaces further up the stack don’t need to know about concrete types, only caring about protocol conformance - this is really great for extensibility.

3 Likes

That was an excellent article, Andy. Gives a lot of insight about how services are architected, the thought process behind it. I’ve been there myself and have often thought about how other people do it. I enjoyed reading it. Thanks!

An alternative using the Result Type to make the function more declarative and expressive.

enum Result<Success, Failure: Error> {
    case success(Success)
    case failure(Failure)
}

 struct FeedItemService {
    // by introducing a result type, we can get rid of the optionals and either get a result or error.
    // When we add our completion handler, we're only accepting either a `FeedItem` or `Error` case
    static func fetch(for account: Account, completion: @escaping (Result<[FeedItem], FailedResponseError>) -> Void) {
    
        let request = FetchRequest(accountID: account.id)
        let client = OAuthClient.shared
    
        client.request(request) { (response) in
        
            guard let data = response.object as? [String: Any],
            let items = data["feed"] as? [[String: Any]],
            response.wasSusseccful else { completion(.failure(.noResponse)); return }
        
            let feedItems = items.compactMap { FeedItem(dictionary: $0) }
            completion(.success(feedItems))
   }
    
 }

}

FeedItemService.fetch(for: Account(id: "1234")) { (result) in
    // We now either get a successful result or a failure reason and act accordingly
    switch result {
    case .success(let value):
        print(value)
    case .failure(let reason):
        print(reason)
    }
}