distributing information on ios

39

Upload: make-school

Post on 19-Jan-2017

318 views

Category:

Software


2 download

TRANSCRIPT

DISTRIBUTING INFORMATION

AGENDAWhat does information distribution mean?

Delegation

Closures

Notifications

Property Observers

Bindings / Key Value Observation

INFORMATION DISTRIBUTIONMobile apps are highly interactive

Multiple interfaces drive data changes:

UI

Network

Main task is responding to events and distributing new

data

CODE LOCALITYEach piece of code needs well

defined responsibility

Example: The code that triggers network request is not necessarily the code that is interested in response

EntryView

FolderView

updateImage

downloadButtonTapped

downloadImage

(1)

(2)

(3)Image

OtherView

updateImage

TYPE TO TYPE COMMUNICATION

TYPE TO TYPE COMMUNICATION

class UserViewController: UIViewController { func infoButtonTapped() { // communicate with business logic } }

class UserView { var userViewController: UserViewController? func infoButtonTapped() { userViewController?.infoButtonTapped() } }

Tight coupling!

TYPE TO TYPE COMMUNICATION

func infoButtonTapped()//…init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?)var view: UIView!func loadView()var nibName: String? { get }var nibBundle: NSBundle? { get }var storyboard: UIStoryboard? { get }//…

The UserView could call any method provided by UserViewController, including all the ones inherited from UIView Controller

UserView can become dependent on UIViewController

UserView has to deal with huge interface:

TYPE TO TYPE COMMUNICATION

Typically used to establish a life long connection

Easy to use

Results in tight coupling

No tight interface for communication between two types

Mostly only useful for 1-1 communication

DELEGATION

DELEGATION

Create a formal protocol that describes the

communication interface

Use this protocol to create an indirect

connection between the two types

DELEGATION

class UserView { var delegate: UserViewResponder? func infoButtonTapped() { responder?.infoButtonTapped() } }

Indirection = looser coupling

class UserViewController: UIViewController, UserViewResponder { func infoButtonTapped() { // communicate with business logic } }

protocol UserViewResponder { func infoButtonTapped() }

DELEGATION

Typically used to establish a life long connection

Decouples communication, easy to replace delegate with any other type conforming to protocol

Tight interface that contains only methods that are relevant for this specific communication channel

Mostly only useful for 1-1 communication

CLOSURES

CLOSURES

class APIClient { func userDetails(userId: String, callback: UserDetailsCallback) { // network request callback(user) } }

Indirection = looser coupling

class UserViewController: UIViewController { override func viewDidAppear(animated: Bool) { APIClient().userDetails("13") { user in // do something with user } } }

typealias UserDetailsCallback = User? -> ()

CLOSURE GOTCHAS

Code localityclass UserViewController: UIViewController { override func viewDidAppear(animated: Bool) {

super.viewDidAppear(animated) APIClient().userDetails("13") { user in

// do something with user } } }

Code localityclass UserViewController2: UIViewController { override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated)

APIClient().userDetails("13", callback: receivedUser) } func receivedUser(user: User?) { // do something with user } }

CLOSURE GOTCHAS

Retain Cycles

class UserViewController: UIViewController { var callback: UserDetailsCallback?

override func viewDidAppear(animated: Bool) { callback = { user in self.showUser(user) } } func showUser(user: User?) { //... } }

Object retains the callback

Callback retains the object through

self reference

Results in retain cycle

CLOSURE GOTCHAS

No Retain Cycles

class UserViewController: UIViewController { var callback: UserDetailsCallback?

override func viewDidAppear(animated: Bool) { callback = { [unowned self] user in self.showUser(user) } } func showUser(user: User?) { //... } }

Use capture list to capture self

weakly

Callback will no longer retain self

Breaks retain cycle!

CLOSURES

Typically used for short lived relationships

Decouples communication

Provides a communication interface with only a single function

Requires caution regarding code locality

Need to be careful to avoid retain cycles

NOTIFICATIONS

NOTIFICATIONS

Notifications allow us to broadcast information (1

to N)

Sender has no information about which objects

have subscribed to notifications

NOTIFICATIONS

func synchronize() { // network request NSNotificationCenter.defaultCenter().postNotificationName(

"MyApp.SynchronizationCompleted", object: self)

}

Posting a notification:

Notifications are delivered on the same thread on which they are posted!

Optionally you can use a userData argument to attach arbitrary data to the notification

NOTIFICATIONS

class Listener { init() { NSNotificationCenter.defaultCenter().addObserver(self, selector: "syncComplete", name: nil, object: nil)

} @objc func syncComplete() { // work print("ok") } }

Registering for Notifications: Specify which notification you want to listen to

Specify which method on which object should be called once this notification occurs

Mark the target method with @objc if you are not subclassing from an Objective-C object

NOTIFICATION GOTCHAS

Don’t forget to unsubscribe!

class Listener { deinit { NSNotificationCenter.defaultCenter().removeObserver(self) } //…}

If a deallocated object is registered

with the notification center, your app

will crash on an attempt to deliver a

notification to the dead object class UserViewController: UIViewController { override func viewDidDisappear(animated: Bool) { super.viewDidDisappear(animated) NSNotificationCenter.defaultCenter().removeObserver(self) }

}

NOTIFICATIONSUsed for broadcasting information

Easy to communicate with different parts of the program without explicit references

No well defined communication interface / no type information

Causes crashes if you forget to unregister

Can create dependencies between code that should not be coupled

PROPERTY OBSERVERS

PROPERTY OBSERVERS

class UserViewController: UIViewController { var label: UILabel! var user: User? { didSet { if let label = label, let user = user { label.text = user.name } } } }

Implicitly propagate changes within

an instance of a type

PROPERTY OBSERVERS

Used for information propagation within an instance

Easy to use, type safe

Not applicable in many scenarios

BINDINGS / KEY VALUE OBSERVATION

KEY VALUE OBSERVATION (KVO)

Objective-C API that relies on the Objective-C Runtime

Generates notifications when an observed property on

an observed object changes

Think: property observers for other objects

KVOclass Observer: NSObject { var user: User init(user: User) { self.user = user super.init() self.user.addObserver(self, forKeyPath: "name", options: NSKeyValueObservingOptions.New, context: nil) } override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) { if let newValue = change?[NSKeyValueChangeNewKey] { print("Name changed: \(newValue)") } else { super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context) } } deinit { self.user.removeObserver(self, forKeyPath: "name") } }

API was not designed for Swift!

KVOUsed to observe changing values on other objects

Allows observation of (almost) any property without additional work on class that is being observed

API doesn’t provide type information of observed values

API is arcane, e.g. one callback for all observer properties

Not available on Swift classes, need to inherit from NSObject and use the dynamic keyword

KVO / BINDINGS

Swift doesn’t have it’s own KVO mechanism, but there

are third party alternatives and it’s easy to implement

your own KVO alternative [1]

One framework that provides KVO and Binding

capabilities is Bond [2]

[1]: Exploring KVO Alternatives with Swift[2]: Summer Academy lecture discussing Bond

DYNAMIC PROPERTIES IN BOND

var image: Observable<UIImage?> = Observable(nil)

image.value = otherImage

Declaring an Observable property:

Setting an Observable property:

OBSERVING NEW VALUES

post.likes.observe { [unowned self] users inself.likingUsers = users//…

}

Calls callback whenever new value is available:

OBSERVING NEW VALUES

post.image.bindTo(postImageView.bnd_image)

Updates image view whenever new value is available:

BONDS

Used to observe changing values on other objects

Allows observation of (almost) any property without much additional work on class that is being observed

Communication protocol is implicit, harder to understand for other developers

SUMMARY

Delegation

Closures

Notifications

Property Observers

Bindings / Key Value Observation