Learning from Open Source
Tags: swift
Published: 2018-09-21 19:21:45

---
title: Learning from Open Source
published: true
description: 
tags: swift,
---


Here is what I learned from some of the open source proiects

![](https://cdn-images-1.medium.com/max/3200/0*G11MqPA1ecHTaPf-.jpeg)

## Pure: Generic factory

From [devxoul/Pure:Sources/Pure/FactoryModule.swift@master](https://github.com/devxoul/Pure/blob/master/Sources/Pure/FactoryModule.swift)

    public protocol FactoryModule: Module {

      /// A factory for `Self`.
      associatedtype Factory = Pure.Factory<Self>

      /// Creates an instance of a module with a dependency and a payload.
      init(dependency: Dependency, payload: Payload)
    }

From [devxoul/Pure:Sources/Pure/Factory.swift@master](https://github.com/devxoul/Pure/blob/master/Sources/Pure/Factory.swift)

    open class Factory<Module: FactoryModule> {
      private let dependencyClosure: () -> Module.Dependency

      /// A static dependency of a module.
      open var dependency: Module.Dependency {
        return self.dependencyClosure()
      }

      /// Creates an instance of `Factory`.
      ///
      /// - parameter dependency: A static dependency which should be resolved in a composition root.
      public init(dependency: @autoclosure @escaping () -> Module.Dependency) {
        self.dependencyClosure = dependency
      }

      /// Creates an instance of a module with a runtime parameter.
      ///
      /// - parameter payload: A runtime parameter which is required to construct a module.
      open func create(payload: Module.Payload) -> Module {
        return Module.init(dependency: self.dependency, payload: payload)
      }
    }

From [devxoul/Pure:Tests/PureTests/PureSpec.swift@master#L72](https://github.com/devxoul/Pure/blob/master/Tests/PureTests/PureSpec.swift#L72)

    let factory = FactoryFixture<Dependency, Payload>.Factory(dependency: .init(
      networking: "Networking A"
    ))
    let instance = factory.create(payload: .init(id: 100))
    expect(instance.dependency.networking) == "Networking A"
    expect(instance.payload.id) == 100

## retrofit: Making deferred

From [JakeWharton/retrofit2-kotlin-coroutines-adapter:src/main/java/com/jakewharton/retrofit2/adapter/kotlin/coroutines/experimental/CoroutineCallAdapterFactory.kt@master#L86](https://github.com/JakeWharton/retrofit2-kotlin-coroutines-adapter/blob/master/src/main/java/com/jakewharton/retrofit2/adapter/kotlin/coroutines/experimental/CoroutineCallAdapterFactory.kt#L86)

    override fun adapt(call: Call<T>): Deferred<T> {
      val deferred = CompletableDeferred<T>()

      deferred.invokeOnCompletion {
        if (deferred.isCancelled) {
          call.cancel()
        }
      }

      call.enqueue(object : Callback<T> {
        override fun onFailure(call: Call<T>, t: Throwable) {
          deferred.completeExceptionally(t)
        }

        override fun onResponse(call: Call<T>, response: Response<T>) {
          if (response.isSuccessful) {
            deferred.complete(response.body()!!)
          } else {
            deferred.completeExceptionally(HttpException(response))
          }
        }
      })

      return deferred
    }

## On: Generic extension with associatedtype protocol

I like extensions, and I like to group them under 1 common property to easily access. This also makes it clear that these all belong to the same feature and not to confuse with Apple properties.

### Grouping all related extensions

This is how I do it in [Anchor](https://github.com/onmyway133/Anchors) and [On](https://github.com/onmyway133/On)

    activate(
      a.anchor.top.left,
      b.anchor.top.right,
      c.anchor.bottom.left,
      d.anchor.bottom.right
    )

    textField.on.text { text in
      print("textField text has changed")
    }

    textField.on.didEndEditing { text in
      print("texField has ended editing")
    }

### Generic extension

For On, it is a bit tricky as it needs to adapt to different NSObject subclasses. And to make auto completion work, meaning that each type of subclass gets its own function hint, we need to use generic and associatedtype protocol.

You can take a look at [Container](https://github.com/onmyway133/On/blob/master/Sources/Shared/Core/Container.swift) and [OnAware](https://github.com/onmyway133/On/blob/master/Sources/Shared/Core/OnAware.swift)

    public class Container<Host: AnyObject>: NSObject {
      unowned let host: Host

      init(host: Host) {
        self.host = host
      }
    }

    public protocol OnAware: class {
      associatedtype OnAwareHostType: AnyObject

      var on: Container<OnAwareHostType> { get }
    }

### RxCocoa

[RxSwift](https://github.com/ReactiveX/RxSwift) has its RxCocoa that does this trick too, so that you can just declare

    button.rx.tap
    textField.rx.text
    alertAction.rx.isEnabled

The power lies in the struct [Reactive](https://github.com/ReactiveX/RxSwift/blob/0b66f666ba6955a51cba1ad530311b030fa4db9c/RxSwift/Reactive.swift) and ReactiveCompatible protocol

    public struct Reactive<Base> {
        /// Base object to extend.
        public let base: Base

        /// Creates extensions with base object.
        ///
        /// - parameter base: Base object.
        public init(_ base: Base) {
            self.base = base
        }
    }

    public protocol ReactiveCompatible {
        /// Extended type
        associatedtype CompatibleType

        /// Reactive extensions.
        static var rx: Reactive<CompatibleType>.Type { get set }

        /// Reactive extensions.
        var rx: Reactive<CompatibleType> { get set }
    }

    extension ReactiveCompatible {
        /// Reactive extensions.
        public static var rx: Reactive<Self>.Type {
            get {
                return Reactive<Self>.self
            }
            set {
                // this enables using Reactive to "mutate" base type
            }
        }

        /// Reactive extensions.
        public var rx: Reactive<Self> {
            get {
                return Reactive(self)
            }
            set {
                // this enables using Reactive to "mutate" base object
            }
        }
    }

Here [UIButton+Rx](https://github.com/ReactiveX/RxSwift/blob/master/RxCocoa/iOS/UIButton%2BRx.swift) you can see how it can be applied to UIButton

    extension Reactive where Base: UIButton {
        
        /// Reactive wrapper for `TouchUpInside` control event.
        public var tap: ControlEvent<Void> {
            return controlEvent(.touchUpInside)
        }
    }

## BackchannelSDK-iOS: Using Coordinator

The [Coordinator](http://khanlou.com/2015/10/coordinators-redux/) pattern can be useful to manage dependencies and handle navigation for your view controllers. It can be seen from [BackchannelSDK-iOS](https://github.com/backchannel/BackchannelSDK-iOS), take a look at [BAKCreateProfileCoordinator](https://github.com/backchannel/BackchannelSDK-iOS/blob/611de61611a9609d708920e2b602b2edbcc26b20/Source/Authentication/BAKCreateProfileCoordinator.m) for example

    @implementation BAKCreateProfileCoordinator

    - (instancetype)initWithUser:(BAKUser *)user navigationController:(UINavigationController *)navigationController configuration:(BAKRemoteConfiguration *)configuration {
        self = [super init];
        if (!self) return nil;
        
        _navigationController = navigationController;
        _user = user;
        _profileViewController = [[BAKProfileFormViewController alloc] init];
        [self configureProfileForm];
        _configuration = configuration;
        
        return self;
    }

    - (void)start {
        [self.profileViewController updateDisplayName:self.user.displayName];
        [self.navigationController pushViewController:self.profileViewController animated:YES];
    }

    - (void)profileViewControllerDidTapAvatarButton:(BAKProfileFormViewController *)profileViewController {
        BAKChooseImageCoordinator *imageChooser = [[BAKChooseImageCoordinator alloc] initWithViewController:self.navigationController];
        imageChooser.delegate = self;
        [self.childCoordinators addObject:imageChooser];
        [imageChooser start];
    }

    - (void)imageChooserDidCancel:(BAKChooseImageCoordinator *)imageChooser {
        [self.childCoordinators removeObject:imageChooser];
    }

Look how it holds navigationController as root element to do navigation, and how it manages childCoordinators

## Kickstarter: Manage dependencies

Another cool thing about [ios-oss](https://github.com/kickstarter/ios-oss) is how it manages dependencies. Usually you have a lot of dependencies, and it’s good to keep them in one place, and inject it to the objects that need.

The [Environment](https://github.com/kickstarter/ios-oss/blob/master/Library/Environment.swift) is simply a struct that holds all dependencies throughout the app

    /**
     A collection of **all** global variables and singletons that the app wants access to.
     */
    public struct Environment {
      /// A type that exposes endpoints for fetching Kickstarter data.
      public let apiService: ServiceType

      /// The amount of time to delay API requests by. Used primarily for testing. Default value is `0.0`.
      public let apiDelayInterval: DispatchTimeInterval

      /// A type that exposes how to extract a still image from an AVAsset.
      public let assetImageGeneratorType: AssetImageGeneratorType.Type

      /// A type that stores a cached dictionary.
      public let cache: KSCache
      
      /// ...
    }

Then there’s global object called [AppEnvironment](https://github.com/kickstarter/ios-oss/blob/master/Library/AppEnvironment.swift) that manages all these Environment in a stack

    public struct AppEnvironment {
      /**
       A global stack of environments.
       */
      fileprivate static var stack: [Environment] = [Environment()]

      /**
       Invoke when an access token has been acquired and you want to log the user in. Replaces the current
       environment with a new one that has the authenticated api service and current user model.

       - parameter envelope: An access token envelope with the api access token and user.
       */
      public static func login(_ envelope: AccessTokenEnvelope) {
        replaceCurrentEnvironment(
          apiService: current.apiService.login(OauthToken(token: envelope.accessToken)),
          currentUser: envelope.user,
          koala: current.koala |> Koala.lens.loggedInUser .~ envelope.user
        )
      }

      /**
       Invoke when we have acquired a fresh current user and you want to replace the current environment's
       current user with the fresh one.

       - parameter user: A user model.
       */
      public static func updateCurrentUser(_ user: User) {
        replaceCurrentEnvironment(
          currentUser: user,
          koala: current.koala |> Koala.lens.loggedInUser .~ user
        )
      }

      public static func updateConfig(_ config: Config) {
        replaceCurrentEnvironment(
          config: config,
          koala: AppEnvironment.current.koala |> Koala.lens.config .~ config
        )
      }

      // Invoke when you want to end the user's session.
      public static func logout() {
        let storage = AppEnvironment.current.cookieStorage
        storage.cookies?.forEach(storage.deleteCookie)

        replaceCurrentEnvironment(
          apiService: AppEnvironment.current.apiService.logout(),
          cache: type(of: AppEnvironment.current.cache).init(),
          currentUser: nil,
          koala: current.koala |> Koala.lens.loggedInUser .~ nil
        )
      }

      // The most recent environment on the stack.
      public static var current: Environment! {
        return stack.last
      }

    }

Then whenever there’s event that triggers dependencies update, we call it like

    self.viewModel.outputs.logIntoEnvironment
      .observeValues { [weak self] accessTokenEnv in
        AppEnvironment.login(accessTokenEnv)
        self?.viewModel.inputs.environmentLoggedIn()
    }

The cool thing about Environment is that we can store and retrieve them

    // Returns the last saved environment from user defaults.
    public static func fromStorage(ubiquitousStore: KeyValueStoreType,
                                     userDefaults: KeyValueStoreType) -> Environment {
      // retrieval

    }

And we can mock in tests

    AppEnvironment.replaceCurrentEnvironment(
      apiService: MockService(
        fetchDiscoveryResponse: .template |> DiscoveryEnvelope.lens.projects .~ [
          .todayByScottThrift,
          .cosmicSurgery,
          .anomalisa
        ]
      )
    )

## Kickstarter: Using Playground

One thing I like about [kickstarter-ios](https://github.com/kickstarter/ios-oss) is how they use Playground to quickly protoyping views.
> *We use Swift Playgrounds for iterative development and styling. Most major screens in the app get a corresponding playground where we can see a wide variety of devices, languages and data in real time.*

This way we don’t need [Injection](http://johnholdsworth.com/injection.html) or using React Native anymore. Take a look at all the pages [kickstarter/ios-oss:Kickstarter-iOS.playground/Pages@master](https://github.com/kickstarter/ios-oss/tree/master/Kickstarter-iOS.playground/Pages)

## Touchbar simulator: Making macOS app in code

I’m familiar with the whole app structure that Xcode gives me when I’m creating new macOS project, together with Storyboard. The other day I was reading [touch-bar-simulator](https://github.com/sindresorhus/touch-bar-simulator) and see how it declares app using only code. See this [main.swift](https://github.com/sindresorhus/touch-bar-simulator/blob/master/Touch%20Bar%20Simulator/main.swift)

    let app = NSApplication.shared
    let delegate = AppDelegate()
    app.delegate = delegate
    app.run()

    final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
      let controller = IDETouchBarSimulatorHostWindowController.simulatorHostWindowController()!
      lazy var window: NSWindow = self.controller.window!
    }

## Kickstarter: Input and output container

This is a very nifty trick from [ios-oss](https://github.com/kickstarter/ios-oss) which was built around MVVM pattern. It uses protocol to define input and output, and a container protocol to contain them. Take [kickstarter/ios-oss:Library/ViewModels/LoginViewModel.swift@1f5643f](https://github.com/kickstarter/ios-oss/blob/1f5643f6a769995ccd1bb3826699745e64597ab7/Library/ViewModels/LoginViewModel.swift) for example

    public protocol LoginViewModelInputs {

    }

    public protocol LoginViewModelOutputs {

    }

    public protocol LoginViewModelType {
      var inputs: LoginViewModelInputs { get }
      var outputs: LoginViewModelOutputs { get }
    }

    public final class LoginViewModel: LoginViewModelType, LoginViewModelInputs, LoginViewModelOutputs {
      public var inputs: LoginViewModelInputs { return self }
      public var outputs: LoginViewModelOutputs { return self }
    }

Look how LoginViewModel conforms to 3 protocols. And when you access its input or outputproperties, you are constrained to only LoginViewModelInputs and LoginViewModelOutputs

## Suas: Observing object deinit in Swift

* Today I was browsing through [Suas-iOS](https://github.com/zendesk/Suas-iOS) and the subscription links to life cycle of another object

    subscription.linkLifeCycleTo(object: self)

It observes the deinit of another job, interesting approach 👍 , take a look in [zendesk/Suas-iOS:Sources/StoreDeinitCallback.swift@master](https://github.com/zendesk/Suas-iOS/blob/master/Sources/StoreDeinitCallback.swift)

    var deinitCallbackKey = "DEINITCALLBACK_SUAS"

    // MARK: Registartion
    extension Suas {

      static func onObjectDeinit(forObject object: NSObject,
                                 callbackId: String,
                                 callback: @escaping () -> ()) {
        let rem = deinitCallback(forObject: object)
        rem.callbacks.append(callback)
      }

      static fileprivate func deinitCallback(forObject object: NSObject) -> DeinitCallback {
        if let deinitCallback = objc_getAssociatedObject(object, &deinitCallbackKey) as? DeinitCallback {
          return deinitCallback
        } else {
          let rem = DeinitCallback()
          objc_setAssociatedObject(object, &deinitCallbackKey, rem, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
          return rem
        }
      }
    }

    @objc fileprivate class DeinitCallback: NSObject {
      var callbacks: [() -> ()] = []

      deinit {
        callbacks.forEach({ $0() })
      }
    }

## Touchbar simulator: Take an app from a private framework

The other day I was browsing through [sindresorhus/touch-bar-simulator](https://github.com/sindresorhus/touch-bar-simulator), it was very brilliant of him to pull IDETouchBarSimulatorHostWindowController from DFRSupportKit.framework. This is worth checking out

![](https://cdn-images-1.medium.com/max/4000/0*u1ZYB06tiKEjKjlk.png)