Upload
stanfy
View
449
Download
0
Embed Size (px)
Citation preview
Why do you need to switch from Objective-C
to Swift, or let’s talk about ReactiveCocoa v4
Malakhovskyi Vitalii iOS Developer @
- 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:
What is ReactiveCocoa ?
Hot and Cold
HOT… — .Next(Void) — .Next(Void) — .Next(Void) — …
… — .Next(Void) — .Next(Void) — .Completed |
COLD| — .Next(data) — Completed |
| — .Error(.ConnectionLost) |
S
S
S
S
Objective-C & ReactiveCocoa v2.5
RACSignal type represents both hot and cold signals.
Eventspublic enum Event<Value, Error: ErrorType> { case Next(Value) case Failed(Error) case Completed case Interrupted }
Signal<Int, NoError>
New events prerogatives:• strict types
• generics
• code highlighting
• ErrorType -> NoError
Hot Signal
Cold SignalProducer
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
Propertiespublic protocol PropertyType { typealias Value
var value: Value { get }
var producer: SignalProducer<Value, NoError> { get } }
• MutableProperty
• ConstantProperty
• AnyProperty
• DynamicProperty
Properties can be
KVO vs Property
class Car: NSObject { dynamic var miles = 0 dynamic var name = "TAZ" }
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") } }
KVO in Swift
• NSObject inheritance - should be Objective-C types - i.e. no structs, enums, and no generics
• dynamic attribute
• cumbersome method signature
• property <~ signal
• property <~ producer
• property <~ otherProperty
Meet “<~”
Action Action<Input, Output, Error: ErrorType>
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)
}
@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 )
loginButton.rac_enabled <~ viewModel.signIn.enabled.producer.takeUntil(rac_willDeallocSignalProducer()) viewModel.signIn.executing.producer .takeUntil(rac_willDeallocSignalProducer()) .startWithNext { [weak self] executing in if executing { self?.activityIndicator.startAnimating() } else { self?.activityIndicator.stopAnimating() } }
Reactive UI
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
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 }) } }
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 ?? "" } }
MVVM - DEMO
Model
ViewModel
View
Modelfunc signIn(login: String, password: String) -> SignalProducer<User, NSError>
ViewModelvar 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
Modelpublic 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) }}
ViewModelpublic 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) } } }
Viewprivate 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 }}
To sum up:• pure Swift implementation
• updated events
• separating hot and cold signals
• new property feature
• type safety
• much nicer syntax
Щастя! Здоровля!
Contact me:
purpleshirted crimsongf Vitaliy Malakhovskiy
https://spin.atomicobject.com/2015/10/26/reactivecocoa-4-differences/
https://github.com/ReactiveCocoa/ReactiveCocoa/tree/master/Documentation
http://nomothetis.svbtle.com/an-introduction-to-reactivecocoa
http://blog.scottlogic.com/2015/05/15/mvvm-reactive-cocoa-3.html
http://blog.scottlogic.com/2015/02/11/swift-kvo-alternatives.html
😱https://github.com/vmalakhovskiy/RACExtensions