Upgrade to Pro — share decks privately, control downloads, hide ads and more …

iOS Clean Architecture

Karumi
February 04, 2016

iOS Clean Architecture

A talk about commons problems in iOS app, and solution based on clean Architecture.

Karumi

February 04, 2016
Tweet

More Decks by Karumi

Other Decks in Programming

Transcript

  1. Karumi is the Rock Solid Code software development studio. We

    embark on every project with a commitment to create something elegant, enduring and effective. That is our heartbeat. Our approach is uniquely simple and honest. We are a dedicated software studio delivering outstanding work.
  2. [
 {
 "id": 1,
 "name": "Yoda",
 "mood": "Sleepy"
 },
 {


    "id": 2,
 "name": "Audrey",
 "mood": "Crazy"
 }
 ] Data: JSON + Images Painting: UIKit
  3. Disk Datasource NSCoding class Book: NSObject, NSCoding { var title:

    String var author: String // Memberwise initializer init(title: String, author: String) { self.title = title self.author = author } // MARK: NSCoding required convenience init?(coder decoder: NSCoder) { guard let title = decoder.decodeObjectForKey("title") as? String, let author = decoder.decodeObjectForKey("author") as? String else { return nil } self.init( title: title, author: author ) } func encodeWithCoder(coder: NSCoder) { coder.encodeObject(self.title, forKey: "title") coder.encodeObject(self.author, forKey: "author") } }
  4. Memory Datasource public class MemoryDataSource<T:Cacheable> { var items: OrderedDictionary<K, CacheItem<T>>?

    ... } public protocol Cacheable : NSCoding { func getKey() -> String } public protocol Cacheable : NSCoding { typealias K: Hashable func getKey() -> K }
  5. Repository class Repository { let networkDataSource: DataSourceApi let readableDatasources: [BaseDataSource<DTO,

    String>] init(networkDataSource: DataSourceApi, memoryDataSource: MemoryDataSource<DTO, String>, diskDataSource: DiskDataSource<DTO, String>) { ... } }
  6. Repository func getAll( forceRefresh excludeCachedDatasource: Bool = false, result: (Result<[Reclamation],

    NSError>) -> Void ) { ... } func getPage( offset: Int, limit: Int, forceRefresh excludeCachedDatasource: Bool = false, result: (Result<CollectionContainer<Reclamation>, NSError>) -> Void ) { ... } func get( key: String, result: (Result<Reclamation, NSError>) -> Void) { ... }
  7. Repository v2 protocol ReadableDataSource { typealias T: Cacheable typealias K:

    Hashable func get(key: K) -> Future<T, NSError> func get(key: K) -> T? func getPage(offset offset: Int, limit: Int) -> Future<CollectionContainer<T>, NSError> func getPage(offset offset: Int, limit: Int) -> CollectionContainer<T>? func getAll() -> Future<[T], NSError> func getAll() -> [T]? func populate(items: [T]) func populate(collectionContainer: CollectionContainer<T>) func populate(collectionContainer: FullCollectionContainer<T>) }
  8. Repository v2 class ReadableBaseRepository<T: Cacheable, K: Hashable where T.K ==

    K> { let readableDatasources: [BaseDataSource<T, K>] init(networkDataSource: BaseDataSource<T, K>, memoryDataSource: MemoryDataSource<T, K>, diskDataSource: DiskDataSource<T, K>) { self.readableDatasources = [memoryDataSource, diskDataSource, networkDataSource] } }
  9. Repository v2 func get( key: K, callback: (Result<T, NSError>) ->

    Void) { getVia(key, datasources: readableDatasources, excludeCachedDatasource: false) { repositoryResult in dispatch_async(dispatch_get_main_queue()) { callback(repositoryResult) } } }
  10. Repository v2 class NSLogInterceptor: RequestInterceptor, ResponseInterceptor { public init() {}

    public func intercept(request: HTTPRequest) -> HTTPRequest { NSLog("-> \(request)") return request } public func intercept(response: HTTPResponse, completion: (Result<HTTPResponse, BothamAPIClientError>) -> Void) { NSLog("<- \(response)") completion(Result.Success(response)) } }
  11. ViewModel public class MenuEntry: NSObject, Cacheable { public let name:

    String public let url: String public let image: String } protocol MenuEntryViewModel { var identifier: String { get } var name: String { get } var contentURL: NSURL? { get } var imageURL: NSURL? { get } } extension MenuEntry : MenuEntryViewModel { var contentURL: NSURL? { return NSURL(string: self.url) } var imageURL: NSURL? { return NSURL(string: self.image) } }
  12. Use Case • Contains Business Logic • No code specific

    to a Framework • V1 - Group method in Use Case Class by Domain • How to abstract Async call ?
  13. Use Case V2 • Use Command Pattern • One public

    method per class • Allow Reuse of Generic code by Presenter and UI • Loading • Error/Retry • Autoloading
  14. Presenter + Command Use Case class GetMenuEntries: UseCase { let

    repository: MenuRepository init(categoryRepository repository: MenuRepository) { self.repository = repository } func execute(forceRefresh: Bool, callback: (Result<[MenuEntryViewModel], NSError>) -> Void) { self.repository.getAll(forceRefresh: forceRefresh) { repositoryResult in callback( repositoryResult.map { $0.map { $0 as MenuEntryViewModel } } ) } } }
  15. Presenter + Command Use Case func loadItems(ui: StoreHomeUI) { UseCaseExecutor(ui:

    ui, showLoader: true) .execute(useCase, forceRefresh: false) { (result: Result<[MenuEntryViewModel], NSError>) in if let menuEntries = result.value where result.succeeded { ui.showMenuEntries(menuEntries) } } }
  16. Dependency Injection func provideSuperHeroDetailViewController(superHeroName: String) -> UIViewController { let viewController:

    SuperHeroDetailViewController = storyBoard.instantiateViewController(“SuperHeroDetailViewController") viewController.presenter = provideSuperHeroDetailPresenter(viewController, erHeroName: superHeroName) return viewController }
  17. Botham • BothamUI (released) • BothamUseCase(Planned) • BothamRepository (In dev)

    • BothamNetworking (released) https:/ /github.com/Karumi https:/ /cocoapods.org/?q=botham