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

Why do you need to switch from Obj-C to Swift, or let's talk about ReactiveCocoa v4

Why do you need to switch from Obj-C to Swift, or let's talk about ReactiveCocoa v4

- what has been changed since ReactiveCocoa v2

- how to make CocoaTouch reactive 🚀

- a few words about MVVM architecture designed with ReactiveCocoa v4

vmalakhovskiy

March 02, 2016
Tweet

More Decks by vmalakhovskiy

Other Decks in Programming

Transcript

  1. Why do you need to switch from Objective-C to Swift,

    or let’s talk about ReactiveCocoa v4 Malakhovskyi Vitalii iOS Developer @
  2. - what has been changed since ReactiveCocoa v2 - how

    to make CocoaTouch reactive - a few words about MVVM architecture designed with ReactiveCocoa v4 So what we will talk about:
  3. HOT … — .Next(Void) — .Next(Void) — .Next(Void) — …

    … — .Next(Void) — .Next(Void) — .Completed | COLD | — .Next(data) — Completed | | — .Error(.ConnectionLost) | S S S S
  4. Events public enum Event<Value, Error: ErrorType> { case Next(Value) case

    Failed(Error) case Completed case Interrupted }
  5. let signal: SignalProducer<Bool, NoError> let observer: Observer<Bool, NoError> init() {

    (signal, observer) = SignalProducer.buffer(3) } signal.startWithNext { value in print(value) } observer.sendNext(true) // true observer.sendNext(false) // false observer.sendNext(false) // false signal.startWithNext { value in print(value) } // true // false // false
  6. Properties public protocol PropertyType { typealias Value var value: Value

    { get } var producer: SignalProducer<Value, NoError> { get } }
  7. class CarObserver: NSObject { private var kvoContext: UInt8 = 1

    private let car: Car init(_ car: Car) { self.car = car super.init() car.addObserver(self, forKeyPath: "miles", options: NSKeyValueObservingOptions.New, context: &kvoContext) } override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [String : AnyObject], context: UnsafeMutablePointer<Void>) { if context == &kvoContext { print("Change at keyPath = \(keyPath) for \(object)") } } deinit { car.removeObserver(self, forKeyPath: "miles") } }
  8. KVO in Swift • NSObject inheritance - should be Objective-C

    types - i.e. no structs, enums, and no generics • dynamic attribute • cumbersome method signature
  9. let enabled = MutableProperty(false) enabled <~ combineLatest(self.username.producer, self.password.producer) .map {

    return $0.characters.count >= 1 && $1.characters.count >= 1 } signIn = Action<Void, User, NSError>(enabledIf: enabled) { _ -> SignalProducer<User, NSError> in return self.model.authorize(self.username.value, password: self.password.value) }
  10. @IBAction func onSignButtonTap(sender: UIButton) { viewModel.signIn.apply().start() } OR self.action =

    CocoaAction(viewModel.signIn, input: () ) loginButton.addTarget(action, action: CocoaAction.selector, forControlEvents: UIControlEvents.TouchUpInside )
  11. func lazyAssociatedProperty<T: AnyObject>(host: AnyObject, key: UnsafePointer<Void>, factory: () -> T)

    -> T { return objc_getAssociatedObject(host, key) as? T ?? { let associatedProperty = factory() objc_setAssociatedObject(host, key, associatedProperty, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) return associatedProperty }() } func lazyMutableProperty<T>(host: AnyObject, key: UnsafePointer<Void>, setter: T -> (), getter: () -> T) -> MutableProperty<T> { return lazyAssociatedProperty(host, key: key) { let property = MutableProperty<T>(getter()) property.producer.startWithNext { newValue in setter(newValue) } return property } } https://github.com/vmalakhovskiy/RACExtensions
  12. struct AssociationKey { static var text: UInt8 = 0 static

    var textColor: UInt8 = 1 } extension UILabel { public var rac_text: MutableProperty<String> { return lazyMutableProperty(self, key: &AssociationKey.text, setter: { self.text = $0 }, getter: { self.text ?? "" }) } public var rac_textColor: MutableProperty<UIColor> { return lazyMutableProperty(self, key: &AssociationKey.textColor, setter: { self.textColor = $0 }, getter: { self.textColor }) } }
  13. extension UISearchBar: UISearchBarDelegate { public var rac_text: MutableProperty<String> { return

    lazyAssociatedProperty(self, key: &AssociationKey.text) { self.delegate = self self.rac_signalForSelector(Selector("searchBar:textDidChange:"), fromProtocol: UISearchBarDelegate.self) .toSignalProducer() .startWithNext({ [weak self] _ in self?.changed() }) let property = MutableProperty<String>(self.text ?? "") property.producer.startWithNext { newValue in self.text = newValue } return property } } func changed() { rac_text.value = self.text ?? "" } }
  14. Model func signIn(login: String, password: String) -> SignalProducer<User, NSError> ViewModel

    var username: MutableProperty<String> { get } var password: MutableProperty<String> { get } var signIn: Action<Void, User, NSError> { get } View @IBOutlet weak var userNameField: TextFieldWithInset @IBOutlet weak var passwordField: TextFieldWithInset @IBOutlet weak var loginButton: UIButton @IBOutlet weak var activityIndicator: UIActivityIndicatorView
  15. Model public protocol LoginModel { func signIn(login: String, password: String)

    -> SignalProducer<User, NSError> } public class LoginModelImpl: LoginModel { let loginManager: LoginManager public init(loginManager: LoginManager) { self.loginManager = loginManager } public func signIn(login: String, password: String) -> SignalProducer<User, NSError> { return loginManager.signIn(login, password: password) } }
  16. ViewModel public protocol LoginViewModel { var username: MutableProperty<String> { get

    } var password: MutableProperty<String> { get } var signIn: Action<Void, User, NSError>! { get } } public class LoginViewModelImpl: LoginViewModel { private let model: LoginModel public let username: MutableProperty<String> = MutableProperty("") public let password: MutableProperty<String> = MutableProperty("") public var signIn: Action<Void, User, NSError>! public init(model: LoginModel, initialUsername: String, lastLoginError: NSError?) { self.model = model let enabledSignal = MutableProperty(false) enabledSignal <~ combineLatest(self.username.producer, self.password.producer) .map { return $0.characters.count >= 1 && $1.characters.count >= 1 } signIn = Action<Void, User, NSError>(enabledIf: enabledSignal) { _ -> SignalProducer<User, NSError> in return self.model.signIn(self.username.value, password: self.password.value) } } }
  17. View private func setupBindings() { viewModel.username <~ userNameField.rac_text.producer.takeUntil(rac_willDeallocSignalProducer()) viewModel.password <~

    passwordField.rac_text.producer.takeUntil(rac_willDeallocSignalProducer()) loginButton.rac_enabled <~ viewModel.signIn.enabled.producer.takeUntil(rac_willDeallocSignalProducer()) viewModel..executing.producer .takeUntil(rac_willDeallocSignalProducer()) .startWithNext { [weak self] executing in if executing { self?.activityIndicator.startAnimating() } else { self?.activityIndicator.stopAnimating() } } loginButton.addTarget(action, action: CocoaAction.selector, forControlEvents: UIControlEvents.TouchUpInside) viewModel.signIn.values.observeNext { [weak self] user in } viewModel.signIn.errors.observeNext { [weak self] error in } }
  18. To sum up: • pure Swift implementation • updated events

    • separating hot and cold signals • new property feature • type safety • much nicer syntax