$30 off During Our Annual Pro Sale. View Details »

面向协议编程与 Cocoa 的邂逅

Wei Wang
September 24, 2016

面向协议编程与 Cocoa 的邂逅

My talk in MDCC 16. About using Protocol-Oriented Programming language in daily Cocoa life.

Wei Wang

September 24, 2016
Tweet

More Decks by Wei Wang

Other Decks in Technology

Transcript

  1. ᶎݻܐᦓᖫᑕӨ Cocoa ጱᮁᭊ
    MDCC 16
    ሴ૛ - 2016 ଙ 9 ์ 24 ෭

    View Slide

  2. Ԇ᷌
    • ᩸ɾॳᦩ
    • ಥɾ૬஌
    • ᫨ɾᅾ௡
    • ݳɾജ൐

    View Slide

  3. Ԇ᷌
    • ᩸ɾॳᦩ - Ջԍฎ Swi' ܐᦓ
    • ಥɾ૬஌
    • ᫨ɾᅾ௡
    • ݳɾജ൐

    View Slide

  4. Ԇ᷌
    • ᩸ɾॳᦩ - Ջԍฎ Swi' ܐᦓ
    • ಥɾ૬஌ - ܐᦓಘ઀޾ᶎݻܐᦓᖫᑕ
    • ᫨ɾᅾ௡
    • ݳɾജ൐

    View Slide

  5. Ԇ᷌
    • ᩸ɾॳᦩ - Ջԍฎ Swi' ܐᦓ
    • ಥɾ૬஌ - ܐᦓಘ઀޾ᶎݻܐᦓᖫᑕ
    • ᫨ɾᅾ௡ - ࣁ෭ଉ୏ݎӾֵአܐᦓ
    • ݳɾജ൐

    View Slide

  6. Ԇ᷌
    • ᩸ɾॳᦩ - Ջԍฎ Swi' ܐᦓ
    • ಥɾ૬஌ - ܐᦓಘ઀޾ᶎݻܐᦓᖫᑕ
    • ᫨ɾᅾ௡ - ࣁ෭ଉ୏ݎӾֵአܐᦓ
    • Model (Networking)
    • ViewController
    • ݳɾജ൐

    View Slide

  7. ᩸ɾॳᦩ
    Ջԍฎ Swi% ܐᦓ
    Protocol

    View Slide

  8. protocol Greetable {
    var name: String { get }
    func greet()
    }

    View Slide

  9. ᶎݻ੒᨝
    Object-oriented

    View Slide

  10. ᶎݻ੒᨝
    class Animal {
    var leg: Int { return 2 }
    func eat() {
    print("eat food.")
    }
    func run() {
    print("run with \(leg) legs")
    }
    }
    class Tiger: Animal {
    override var leg: Int { return 4 }
    override func eat() {
    print("eat meat.")
    }
    }
    let tiger = Tiger()
    tiger.eat() // "eat meat"
    tiger.run() // "run with 4 legs"

    View Slide

  11. ViewController → UIViewController
    class ViewCotroller:
    UIViewController
    {
    // ᖀಥ
    // view, isFirstResponder()...
    // ෛے
    func myMethod() {
    }
    }

    View Slide

  12. AnotherViewController → UITableViewController → UIViewController
    class AnotherViewController:
    UITableViewController
    {
    // ᖀಥ
    // tableView, isFirstResponder()...
    // ෛے
    func myMethod() {
    }
    }

    View Slide

  13. ࢯहԏӞ
    Cross-Cu'ng Concerns
    ཞڔىဳᅩ

    View Slide

  14. ᥴ٬ොໜ
    • Copy & Paste
    • ୚ف BaseViewController
    • ׁᩢဳف
    • ग़ᖀಥ

    View Slide

  15. Objec&ve-C
    Message Sending

    View Slide

  16. ViewController *v1 = ...
    [v1 myMethod];
    AnotherViewController *v2 = ...
    [v2 myMethod];

    View Slide

  17. NSArray *array = @[v1, v2];
    for (id obj in array) {
    [obj myMethod];
    }

    View Slide

  18. NSObject *v3 = [NSObject new]
    // v3 ဌํਫሿ `myMethod`
    NSArray *array = @[v1, v2, v3];
    for (id obj in array) {
    [obj myMethod];
    }
    // Runtime error:
    // unrecognized selector sent to instance blabla

    View Slide

  19. ࢯहԏԫ
    Dynamic Dispatch Safety
    ۖாၝݎਞق௔

    View Slide

  20. OOP ࢯह
    • ۖாၝݎਞق௔
    • ཞڔىဳᅩ

    View Slide

  21. ܐᦓ
    Protocol

    View Slide

  22. Java, C#
    Interface

    View Slide

  23. Swift
    protocol

    View Slide

  24. Swi$ protocol
    protocol Greetable {
    var name: String { get }
    func greet()
    }

    View Slide

  25. Swi$ protocol
    protocol Greetable {
    var name: String { get }
    func greet()
    }
    struct Person: Greetable {
    let name: String
    func greet() {
    print("֦অ \(name)")
    }
    }
    Person(name: "Wei Wang").greet()

    View Slide

  26. ಥɾ૬஌
    ܐᦓಘ઀޾ᶎݻܐᦓᖫᑕ

    View Slide

  27. OOP ࢯह
    • ۖாၝݎਞق௔
    • ཞڔىဳᅩ

    View Slide

  28. protocol Greetable {
    var name: String { get }
    func greet()
    }
    struct Person: Greetable {
    let name: String
    func greet() {
    print("֦অ \(name)")
    }
    }

    View Slide

  29. protocol Greetable {
    var name: String { get }
    func greet()
    }
    struct Cat: Greetable {
    let name: String
    func greet() {
    print("meow~ \(name)")
    }
    }

    View Slide

  30. let array: [Greetable] = [
    Person(name: "Wei Wang"),
    Cat(name: "onevcat")]
    for obj in array {
    obj.greet()
    }
    // ֦অ Wei Wang
    // meow~ onevcat

    View Slide

  31. struct Bug: Greetable {
    let name: String
    }
    // Compiler Error:
    // 'Bug' does not conform to protocol 'Greetable'
    // protocol requires function 'greet()'

    View Slide

  32. OOP ࢯह
    • ✅ ۖாၝݎਞق௔
    • ཞڔىဳᅩ

    View Slide

  33. ֵአܐᦓوՁդᎱ
    protocol P {
    func myMethod()
    }

    View Slide

  34. // class ViewController: UIViewController
    extension ViewController: P {
    func myMethod() {
    doWork()
    }
    }
    // class AnotherViewController: UITableViewController
    extension AnotherViewController: P {
    func myMethod() {
    doWork()
    }
    }

    View Slide

  35. Swi$ 2 - ܐᦓಘ઀

    View Slide

  36. protocol P {
    func myMethod()
    }
    extension P {
    func myMethod() {
    doWork()
    }
    }
    ԅ P ਧԎጱොဩ൉׀ἕᦊਫሿ

    View Slide

  37. extension ViewController: P { }
    extension AnotherViewController: P { }
    viewController.myMethod()
    anotherViewController.myMethod()

    View Slide

  38. ਫሿ๚ࣁܐᦓӾ्กጱٖ਻
    protocol P {
    func myMethod()
    }
    extension P {
    func myMethod() {
    doWork()
    }
    func anotherMethod() {
    myMethod()
    someOtherWork()
    }
    }
    viewController.anotherMethod()

    View Slide

  39. • ܐᦓਧԎ
    • ൉׀ਫሿጱفݗ
    • ᭽஗ܐᦓጱᔄࣳᵱᥝ੒ٌᬰᤈਫሿ
    • ܐᦓಘ઀
    • ԅفݗ൉׀ἕᦊਫሿ
    • ໑ഝفݗ൉׀᷐क़ਫሿ

    View Slide

  40. OOP ࢯह
    • ✅ ۖாၝݎਞق௔
    • ✅ ཞڔىဳᅩ

    View Slide

  41. ᫨ɾᅾ௡
    ࣁ෭ଉ୏ݎӾֵአܐᦓ

    View Slide

  42. WWDC 15 #408
    Protocol-Oriented Programming in Swi3

    View Slide

  43. Model (Networking)

    View Slide

  44. ໜֺ
    चԭ Protocol ጱᗑᕶ᧗࿢

    View Slide

  45. Demo
    • चԭܐᦓ
    • ᔄࣳਞق
    • ᥴᘠݳ
    • ݢܔᇿၥᦶ
    • ಘ઀௔

    View Slide

  46. • Networking:
    • AFNetworking
    • Alamofire
    • (ASIHTTPRequest) !
    • Model Parser
    • SwiAyJSON
    • Argo
    • Himotoki

    View Slide

  47. սضᘍᡤֵአܐᦓ
    ṛଶܐᦓ۸ํۗԭᥴᘠ҅ၥᦶզ݊ಘ઀

    View Slide

  48. APIKit1 + Himotoki2
    2 h$ps:/
    /github.com/ikesyo/Himotoki
    1 h$ps:/
    /github.com/ishkawa/APIKit

    View Slide

  49. Controller

    View Slide

  50. ໜֺ
    ړᶭے᫹

    View Slide

  51. ړᶭے᫹ጱᗑᕶ᧗࿢
    struct Pagination {
    let items: [T]
    let hasNext: Bool
    }
    struct ChannelsResquest: Request {
    typealias Response = Pagination
    let lastId: Int?
    }

    View Slide

  52. class ChannelsTableViewController: UITableViewController {
    private var lastId: Int? = nil
    private var hasNext = true
    override func viewDidLoad() {
    super.viewDidLoad()
    load()
    }
    func load() {
    if hasNext {
    client.send(ChannelsResquest(lastId: lastId)) {
    result in
    }
    }
    }
    }

    View Slide

  53. class ChannelsTableViewController: UITableViewController {
    private var lastId: Int? = nil
    private var hasNext = true
    private var data: [Channel] = []
    override func viewDidLoad() {
    super.viewDidLoad()
    load()
    }
    func load() {
    if hasNext {
    client.send(ChannelsResquest(lastId: lastId)) {
    result in
    self.lastId = result!.items.last?.id
    self.hasNext = result!.hasNext
    self.data = result.items
    self.tableView.reloadData()
    }
    }
    }
    }

    View Slide

  54. extension ChannelsTableViewController: UITableViewDelegate {
    override func tableView(tableView: UITableView,
    willDisplayCell cell: UITableViewCell,
    forRowAtIndexPath indexPath: NSIndexPath)
    {
    if indexPath.row == data.count - 1 {
    load()
    }
    }

    View Slide

  55. ඳԪᬮဌํᕮ๳...

    View Slide

  56. ඳԪᬮဌํᕮ๳...
    private var isLoading = false
    func load() {
    if isLoading { return }
    if hasNext {
    isLoading = true
    client.send(ChannelsResquest(lastId: lastId)) {
    result in
    //...
    self.isLoading = false
    }
    }
    }

    View Slide

  57. ChannelsTableViewController
    Pagina&on

    View Slide

  58. FriendsTableViewController
    Pagina&on

    View Slide

  59. ොໜӞғ॔ګᔌᩂ

    View Slide

  60. ොໜӞғ॔ګᔌᩂ

    View Slide

  61. ොໜԫғᆿᔄ

    View Slide

  62. BaseTableViewController

    View Slide

  63. class BaseTableViewController: UITableViewController {
    var lastId: Int? = nil
    var hasNext = true
    var isLoading = false
    func loadNext() {
    if isLoading { return }
    if hasNext {
    isLoading = true
    doLoad {result in
    self.lastId = //...
    self.hasNext = //...
    }
    }
    }
    func doLoad(handler: (Any?)->Void) {
    // ??
    }
    }

    View Slide

  64. class BaseTableViewController: UITableViewController {
    var lastId: Int? = nil
    var hasNext = true
    var isLoading = false
    func loadNext() {
    if isLoading { return }
    if hasNext {
    isLoading = true
    doLoad {result in
    self.lastId = //...
    self.hasNext = //...
    }
    }
    }
    func doLoad(handler: (Any?)->Void) {
    fatalError("You should implement it in subclass!")
    }
    }

    View Slide

  65. class FriendsTableViewController: BaseTableViewController {
    private var data: [Friend] = []
    override func viewDidLoad() {
    super.viewDidLoad()
    loadNext()
    }
    override func doLoad(handler: (Any?)->Void) {
    client.send(FriendsRequest(lastId: lastId)) {
    result in
    handler(result)
    // ...
    self.data = result!.items
    self.tableView.reloadData()
    }
    }
    }

    View Slide

  66. View Slide

  67. View Slide

  68. WTF?

    View Slide

  69. • FriendsTableViewController →
    FriendsCollec5onViewController

    View Slide

  70. • FriendsTableViewController →
    FriendsCollec5onViewController
    • BaseTableViewController →
    BaseCollec5onViewController

    View Slide

  71. • FriendsTableViewController →
    FriendsCollec5onViewController
    • BaseTableViewController →
    BaseCollec5onViewController
    ॔ګᔌᩂ?

    View Slide

  72. • FriendsTableViewController →
    FriendsCollec5onViewController
    • BaseTableViewController →
    BaseCollec5onViewController
    ॔ګᔌᩂ

    View Slide

  73. ොໜӣғܐᦓ

    View Slide

  74. struct NextPageState {
    private(set) var hasNext: Bool
    private(set) var isLoading: Bool
    private(set) var lastId: T?
    init() {
    hasNext = true
    isLoading = false
    lastId = nil
    }
    mutating func reset() {
    hasNext = true
    isLoading = false
    lastId = nil
    }
    mutating func update(hasNext: Bool, isLoading: Bool, lastId: T?) {
    self.hasNext = hasNext
    self.isLoading = isLoading
    self.lastId = lastId
    }
    }

    View Slide

  75. protocol NextPageLoadable: class {
    associatedtype DataType
    associatedtype LastIdType
    var data: [DataType] { get set }
    var nextPageState: NextPageState { get set }
    func performLoad(
    successHandler: (_ rows: [DataType],
    _ hasNext: Bool,
    _ lastId: LastIdType?) -> (),
    failHandler: () -> ()
    )
    }

    View Slide

  76. protocol NextPageLoadable: class {
    associatedtype DataType
    associatedtype LastIdType
    var data: [DataType] { get set }
    var nextPageState: NextPageState { get set }
    func performLoad(
    successHandler: (_ rows: [DataType],
    _ hasNext: Bool,
    _ lastId: LastIdType?) -> (),
    failHandler: () -> ()
    )
    }
    extension NextPageLoadable where Self: UITableViewController {
    func loadNext() {
    guard nextPageState.hasNext else { return }
    if nextPageState.isLoading { return }

    View Slide

  77. extension NextPageLoadable where Self: UITableViewController {
    func loadNext() {
    guard nextPageState.hasNext else { return }
    if nextPageState.isLoading { return }
    nextPageState.isLoading = true
    performLoad(successHandler: { rows, hasNext, lastId in
    self.data += rows
    self.nextPageState.update(hasNext: hasNext,
    isLoading: false,
    lastId: lastId)

    View Slide

  78. extension NextPageLoadable where Self: UITableViewController {
    func loadNext() {
    guard nextPageState.hasNext else { return }
    if nextPageState.isLoading { return }
    nextPageState.isLoading = true
    performLoad(successHandler: { rows, hasNext, lastId in
    self.data += rows
    self.nextPageState.update(hasNext: hasNext,
    isLoading: false,
    lastId: lastId)
    self.tableView.reloadData()
    }, failHandler: {
    //..
    })
    }
    }

    View Slide

  79. class FriendTableViewController: UITableViewController {
    var nextPageState = NextPageState()
    var data: [Friend] = []
    }
    extension FriendTableViewController: NextPageLoadable {
    func performLoad(
    successHandler: ([String], Bool, Int?) -> (),
    failHandler: () -> ())
    {
    client.send(FriendsRequest()) {
    result in
    if let result = result {
    successHandler(result.items,
    result.hasNext,
    result.items.last.id)
    } else {
    failHandler()
    }
    }
    }
    }

    View Slide

  80. extension NextPageLoadable where Self: UITableViewController {
    func loadNext() { ... }
    }

    View Slide

  81. extension NextPageLoadable where Self: UITableViewController {
    func loadNext() { ... }
    }
    extension FriendTableViewController: NextPageLoadable { ... }
    class FriendTableViewController: UITableViewController {
    //...
    override func viewDidLoad() {
    super.viewDidLoad()
    loadNext()
    }
    override func tableView(tableView: UITableView,
    willDisplayCell cell: UITableViewCell,
    forRowAtIndexPath indexPath: NSIndexPath)
    {
    if indexPath.row == data.count - 1 {
    loadNext()
    }
    }
    }

    View Slide

  82. extension NextPageLoadable where Self: UITableViewController {
    func loadNext() { ... }
    }
    UICollec(onView ெԍېҘ

    View Slide

  83. extension NextPageLoadable where Self: UITableViewController {
    func loadNext() { ... }
    }
    UICollec(onView ெԍېҘ
    ॔ګᔌᩂҘ
    extension NextPageLoadable where Self: UICollectionViewController {
    func loadNext() { ... }
    }

    View Slide

  84. extension NextPageLoadable where Self: UITableViewController {
    func loadNext() {
    guard nextPageState.hasNext else { return }
    if nextPageState.isLoading { return }
    nextPageState.isLoading = true
    performLoad(successHandler: { rows, hasNext, lastId in
    self.data += rows
    self.nextPageState.update(hasNext: hasNext, isLoading: false, lastId: lastId)
    self.tableView.reloadData()
    }, failHandler: {
    // Failed when first loading
    if self.nextPageState.lastId == nil {
    self.data = []
    self.nextPageState.reset()
    }
    })
    }
    }

    View Slide

  85. tableView.reloadData()
    colletionView.reloadData()

    View Slide

  86. tableView.reloadData()
    colletionView.reloadData()
    protocol ReloadableType {
    func reloadData()
    }
    extension UITableView: ReloadableType {}
    extension UICollectionView: ReloadableType {}

    View Slide

  87. extension NextPageLoadable
    where Self: UITableViewController {
    func loadNext() {
    //...
    self.tableView.reloadData()
    //...
    }
    }

    View Slide

  88. extension NextPageLoadable {
    func loadNext(view: ReloadableType) {
    //...
    view.reloadData()
    //...
    }
    }

    View Slide

  89. extension NextPageLoadable where Self: UITableViewController {
    func loadNext() {
    loadNext(reloadView: tableView)
    }
    }

    View Slide

  90. extension NextPageLoadable where Self: UITableViewController {
    func loadNext() {
    loadNext(reloadView: tableView)
    }
    }
    extension NextPageLoadable where Self: UICollectionViewController {
    func loadNext() {
    loadNext(reloadView: collectionView)
    }
    }

    View Slide

  91. • FriendsTableViewController →
    FriendsCollec5onViewController

    View Slide

  92. class FriendTableViewController: UITableViewController {
    var nextPageState = NextPageState()
    var data: [Friend] = []
    }
    extension FriendTableViewController: NextPageLoadable {
    func performLoad(
    successHandler: ([String], Bool, Int?) -> (),
    failHandler: () -> ())
    {
    client.send(FriendsRequest()) {
    result in
    if let result = result {
    successHandler(result.items,
    result.hasNext,
    result.items.last.id)
    } else {
    failHandler()
    }
    }
    }
    }

    View Slide

  93. class FriendCollectionViewController: UITableViewController {
    var nextPageState = NextPageState()
    var data: [Friend] = []
    }
    extension FriendCollectionViewController: NextPageLoadable {
    func performLoad(
    successHandler: ([String], Bool, Int?) -> (),
    failHandler: () -> ())
    {
    client.send(FriendsRequest()) {
    result in
    if let result = result {
    successHandler(result.items,
    result.hasNext,
    result.items.last.id)
    } else {
    failHandler()
    }
    }
    }
    }

    View Slide

  94. ViewController ၥᦶ

    View Slide

  95. ֵአܐᦓ๶ᕟᕢ ViewController
    • כ೮ᓌܔጱ ViewController ᖀಥ (ٺ੝ᖀಥ)

    View Slide

  96. ֵአܐᦓ๶ᕟᕢ ViewController
    • כ೮ᓌܔጱ ViewController ᖀಥ (ٺ੝ᖀಥ)
    • ֵአܐᦓ๶̿೪ᤰ̀ViewController ಅᵱᥝጱۑᚆ

    View Slide

  97. ֵአܐᦓ๶ᕟᕢ ViewController
    • כ೮ᓌܔጱ ViewController ᖀಥ (ٺ੝ᖀಥ)
    • ֵአܐᦓ๶̿೪ᤰ̀ViewController ಅᵱᥝጱۑᚆ
    • ਖ਼դᎱ޾ᨱձړᐶڊ ViewController

    View Slide

  98. ֵአܐᦓ๶ᕟᕢ ViewController
    • כ೮ᓌܔጱ ViewController ᖀಥ (ٺ੝ᖀಥ)
    • ֵአܐᦓ๶̿೪ᤰ̀ViewController ಅᵱᥝጱۑᚆ
    • ਖ਼դᎱ޾ᨱձړᐶڊ ViewController
    • ֵአ᯿຅ጱොဩਖ਼ܐᦓ᭑Ⴙ᭗አ۸

    View Slide

  99. ݳɾജ൐
    ֵአܐᦓଆۗද࠺դᎱᦡᦇ

    View Slide

  100. വគᩒා
    • Protocol-Oriented Programming in Swi4 - WWDC
    15 #408
    • Protocols with Associated Types - @alexisgallagher
    • Protocol Oriented Programming in the Real World -
    @_maHhewpalmer
    • PracIcal Protocol-Oriented-Programming -
    @natashatherobot

    View Slide

  101. ᨀᨀᘰލ
    FAQ
    Email: [email protected], GitHub: onevcat

    View Slide