My talk in MDCC 16. About using Protocol-Oriented Programming language in daily Cocoa life.
ᶎݻܐᦓᖫᑕӨ Cocoa ጱᮁᭊMDCC 16ሴ - 2016 ଙ 9 ์ 24 ෭
View Slide
Ԇ᷌• ᩸ɾॳᦩ• ಥɾ૬• ɾᅾ• ݳɾജ
Ԇ᷌• ᩸ɾॳᦩ - Ջԍฎ Swi' ܐᦓ• ಥɾ૬• ɾᅾ• ݳɾജ
Ԇ᷌• ᩸ɾॳᦩ - Ջԍฎ Swi' ܐᦓ• ಥɾ૬ - ܐᦓಘᶎݻܐᦓᖫᑕ• ɾᅾ• ݳɾജ
Ԇ᷌• ᩸ɾॳᦩ - Ջԍฎ Swi' ܐᦓ• ಥɾ૬ - ܐᦓಘᶎݻܐᦓᖫᑕ• ɾᅾ - ࣁ෭ଉݎӾֵአܐᦓ• ݳɾജ
Ԇ᷌• ᩸ɾॳᦩ - Ջԍฎ Swi' ܐᦓ• ಥɾ૬ - ܐᦓಘᶎݻܐᦓᖫᑕ• ɾᅾ - ࣁ෭ଉݎӾֵአܐᦓ• Model (Networking)• ViewController• ݳɾജ
᩸ɾॳᦩՋԍฎ Swi% ܐᦓProtocol
protocol Greetable {var name: String { get }func greet()}
ᶎݻObject-oriented
ᶎݻ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"
ViewController → UIViewControllerclass ViewCotroller:UIViewController{// ᖀಥ// view, isFirstResponder()...// ෛےfunc myMethod() {}}
AnotherViewController → UITableViewController → UIViewControllerclass AnotherViewController:UITableViewController{// ᖀಥ// tableView, isFirstResponder()...// ෛےfunc myMethod() {}}
ࢯहԏӞCross-Cu'ng Concernsཞڔىဳᅩ
ᥴ٬ොໜ• Copy & Paste• ف BaseViewController• ׁᩢဳف• ग़ᖀಥ
Objec&ve-CMessage Sending
ViewController *v1 = ...[v1 myMethod];AnotherViewController *v2 = ...[v2 myMethod];
NSArray *array = @[v1, v2];for (id obj in array) {[obj myMethod];}
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
ࢯहԏԫDynamic Dispatch Safetyۖாၝݎਞق
OOP ࢯह• ۖாၝݎਞق• ཞڔىဳᅩ
ܐᦓProtocol
Java, C#Interface
Swiftprotocol
Swi$ protocolprotocol Greetable {var name: String { get }func greet()}
Swi$ protocolprotocol Greetable {var name: String { get }func greet()}struct Person: Greetable {let name: Stringfunc greet() {print("֦অ \(name)")}}Person(name: "Wei Wang").greet()
ಥɾ૬ܐᦓಘᶎݻܐᦓᖫᑕ
protocol Greetable {var name: String { get }func greet()}struct Person: Greetable {let name: Stringfunc greet() {print("֦অ \(name)")}}
protocol Greetable {var name: String { get }func greet()}struct Cat: Greetable {let name: Stringfunc greet() {print("meow~ \(name)")}}
let array: [Greetable] = [Person(name: "Wei Wang"),Cat(name: "onevcat")]for obj in array {obj.greet()}// ֦অ Wei Wang// meow~ onevcat
struct Bug: Greetable {let name: String}// Compiler Error:// 'Bug' does not conform to protocol 'Greetable'// protocol requires function 'greet()'
OOP ࢯह• ✅ ۖாၝݎਞق• ཞڔىဳᅩ
ֵአܐᦓوՁդᎱprotocol P {func myMethod()}
// class ViewController: UIViewControllerextension ViewController: P {func myMethod() {doWork()}}// class AnotherViewController: UITableViewControllerextension AnotherViewController: P {func myMethod() {doWork()}}
Swi$ 2 - ܐᦓಘ
protocol P {func myMethod()}extension P {func myMethod() {doWork()}}ԅ P ਧԎጱොဩ׀ἕᦊਫሿ
extension ViewController: P { }extension AnotherViewController: P { }viewController.myMethod()anotherViewController.myMethod()
ਫሿ๚ࣁܐᦓӾ्กጱٖprotocol P {func myMethod()}extension P {func myMethod() {doWork()}func anotherMethod() {myMethod()someOtherWork()}}viewController.anotherMethod()
• ܐᦓਧԎ• ׀ਫሿጱفݗ• ᭽ܐᦓጱᔄࣳᵱᥝٌᬰᤈਫሿ• ܐᦓಘ• ԅفݗ׀ἕᦊਫሿ• ໑ഝفݗ׀᷐क़ਫሿ
OOP ࢯह• ✅ ۖாၝݎਞق• ✅ ཞڔىဳᅩ
ɾᅾࣁ෭ଉݎӾֵአܐᦓ
WWDC 15 #408Protocol-Oriented Programming in Swi3
Model (Networking)
ໜֺचԭ Protocol ጱᗑᕶ᧗
Demo• चԭܐᦓ• ᔄࣳਞق• ᥴᘠݳ• ݢܔᇿၥᦶ• ಘ
• Networking:• AFNetworking• Alamofire• (ASIHTTPRequest) !• Model Parser• SwiAyJSON• Argo• Himotoki
սضᘍᡤֵአܐᦓṛଶܐᦓ۸ํۗԭᥴᘠ҅ၥᦶզ݊ಘ
APIKit1 + Himotoki22 h$ps://github.com/ikesyo/Himotoki1 h$ps://github.com/ishkawa/APIKit
Controller
ໜֺړᶭے
ړᶭےጱᗑᕶ᧗struct Pagination {let items: [T]let hasNext: Bool}struct ChannelsResquest: Request {typealias Response = Paginationlet lastId: Int?}
class ChannelsTableViewController: UITableViewController {private var lastId: Int? = nilprivate var hasNext = trueoverride func viewDidLoad() {super.viewDidLoad()load()}func load() {if hasNext {client.send(ChannelsResquest(lastId: lastId)) {result in}}}}
class ChannelsTableViewController: UITableViewController {private var lastId: Int? = nilprivate var hasNext = trueprivate var data: [Channel] = []override func viewDidLoad() {super.viewDidLoad()load()}func load() {if hasNext {client.send(ChannelsResquest(lastId: lastId)) {result inself.lastId = result!.items.last?.idself.hasNext = result!.hasNextself.data = result.itemsself.tableView.reloadData()}}}}
extension ChannelsTableViewController: UITableViewDelegate {override func tableView(tableView: UITableView,willDisplayCell cell: UITableViewCell,forRowAtIndexPath indexPath: NSIndexPath){if indexPath.row == data.count - 1 {load()}}
ඳԪᬮဌํᕮ...
ඳԪᬮဌํᕮ...private var isLoading = falsefunc load() {if isLoading { return }if hasNext {isLoading = trueclient.send(ChannelsResquest(lastId: lastId)) {result in//...self.isLoading = false}}}
ChannelsTableViewControllerPagina&on
FriendsTableViewControllerPagina&on
ොໜӞғ॔ګᔌᩂ
ොໜԫғᆿᔄ
BaseTableViewController
class BaseTableViewController: UITableViewController {var lastId: Int? = nilvar hasNext = truevar isLoading = falsefunc loadNext() {if isLoading { return }if hasNext {isLoading = truedoLoad {result inself.lastId = //...self.hasNext = //...}}}func doLoad(handler: (Any?)->Void) {// ??}}
class BaseTableViewController: UITableViewController {var lastId: Int? = nilvar hasNext = truevar isLoading = falsefunc loadNext() {if isLoading { return }if hasNext {isLoading = truedoLoad {result inself.lastId = //...self.hasNext = //...}}}func doLoad(handler: (Any?)->Void) {fatalError("You should implement it in subclass!")}}
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 inhandler(result)// ...self.data = result!.itemsself.tableView.reloadData()}}}
WTF?
• FriendsTableViewController →FriendsCollec5onViewController
• FriendsTableViewController →FriendsCollec5onViewController• BaseTableViewController →BaseCollec5onViewController
• FriendsTableViewController →FriendsCollec5onViewController• BaseTableViewController →BaseCollec5onViewController॔ګᔌᩂ?
• FriendsTableViewController →FriendsCollec5onViewController• BaseTableViewController →BaseCollec5onViewController॔ګᔌᩂ
ොໜӣғܐᦓ
struct NextPageState {private(set) var hasNext: Boolprivate(set) var isLoading: Boolprivate(set) var lastId: T?init() {hasNext = trueisLoading = falselastId = nil}mutating func reset() {hasNext = trueisLoading = falselastId = nil}mutating func update(hasNext: Bool, isLoading: Bool, lastId: T?) {self.hasNext = hasNextself.isLoading = isLoadingself.lastId = lastId}}
protocol NextPageLoadable: class {associatedtype DataTypeassociatedtype LastIdTypevar data: [DataType] { get set }var nextPageState: NextPageState { get set }func performLoad(successHandler: (_ rows: [DataType],_ hasNext: Bool,_ lastId: LastIdType?) -> (),failHandler: () -> ())}
protocol NextPageLoadable: class {associatedtype DataTypeassociatedtype LastIdTypevar 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 }
extension NextPageLoadable where Self: UITableViewController {func loadNext() {guard nextPageState.hasNext else { return }if nextPageState.isLoading { return }nextPageState.isLoading = trueperformLoad(successHandler: { rows, hasNext, lastId inself.data += rowsself.nextPageState.update(hasNext: hasNext,isLoading: false,lastId: lastId)
extension NextPageLoadable where Self: UITableViewController {func loadNext() {guard nextPageState.hasNext else { return }if nextPageState.isLoading { return }nextPageState.isLoading = trueperformLoad(successHandler: { rows, hasNext, lastId inself.data += rowsself.nextPageState.update(hasNext: hasNext,isLoading: false,lastId: lastId)self.tableView.reloadData()}, failHandler: {//..})}}
class FriendTableViewController: UITableViewController {var nextPageState = NextPageState()var data: [Friend] = []}extension FriendTableViewController: NextPageLoadable {func performLoad(successHandler: ([String], Bool, Int?) -> (),failHandler: () -> ()){client.send(FriendsRequest()) {result inif let result = result {successHandler(result.items,result.hasNext,result.items.last.id)} else {failHandler()}}}}
extension NextPageLoadable where Self: UITableViewController {func loadNext() { ... }}
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()}}}
extension NextPageLoadable where Self: UITableViewController {func loadNext() { ... }}UICollec(onView ெԍېҘ
extension NextPageLoadable where Self: UITableViewController {func loadNext() { ... }}UICollec(onView ெԍېҘ॔ګᔌᩂҘextension NextPageLoadable where Self: UICollectionViewController {func loadNext() { ... }}
extension NextPageLoadable where Self: UITableViewController {func loadNext() {guard nextPageState.hasNext else { return }if nextPageState.isLoading { return }nextPageState.isLoading = trueperformLoad(successHandler: { rows, hasNext, lastId inself.data += rowsself.nextPageState.update(hasNext: hasNext, isLoading: false, lastId: lastId)self.tableView.reloadData()}, failHandler: {// Failed when first loadingif self.nextPageState.lastId == nil {self.data = []self.nextPageState.reset()}})}}
tableView.reloadData()colletionView.reloadData()
tableView.reloadData()colletionView.reloadData()protocol ReloadableType {func reloadData()}extension UITableView: ReloadableType {}extension UICollectionView: ReloadableType {}
extension NextPageLoadablewhere Self: UITableViewController {func loadNext() {//...self.tableView.reloadData()//...}}
extension NextPageLoadable {func loadNext(view: ReloadableType) {//...view.reloadData()//...}}
extension NextPageLoadable where Self: UITableViewController {func loadNext() {loadNext(reloadView: tableView)}}
extension NextPageLoadable where Self: UITableViewController {func loadNext() {loadNext(reloadView: tableView)}}extension NextPageLoadable where Self: UICollectionViewController {func loadNext() {loadNext(reloadView: collectionView)}}
class FriendCollectionViewController: UITableViewController {var nextPageState = NextPageState()var data: [Friend] = []}extension FriendCollectionViewController: NextPageLoadable {func performLoad(successHandler: ([String], Bool, Int?) -> (),failHandler: () -> ()){client.send(FriendsRequest()) {result inif let result = result {successHandler(result.items,result.hasNext,result.items.last.id)} else {failHandler()}}}}
ViewController ၥᦶ
ֵአܐᦓᕟᕢ ViewController• כ೮ᓌܔጱ ViewController ᖀಥ (ٺᖀಥ)
ֵአܐᦓᕟᕢ ViewController• כ೮ᓌܔጱ ViewController ᖀಥ (ٺᖀಥ)• ֵአܐᦓ̿೪ᤰ̀ViewController ಅᵱᥝጱۑᚆ
ֵአܐᦓᕟᕢ ViewController• כ೮ᓌܔጱ ViewController ᖀಥ (ٺᖀಥ)• ֵአܐᦓ̿೪ᤰ̀ViewController ಅᵱᥝጱۑᚆ• ਖ਼դᎱᨱձړᐶڊ ViewController
ֵአܐᦓᕟᕢ ViewController• כ೮ᓌܔጱ ViewController ᖀಥ (ٺᖀಥ)• ֵአܐᦓ̿೪ᤰ̀ViewController ಅᵱᥝጱۑᚆ• ਖ਼դᎱᨱձړᐶڊ ViewController• ֵአ᯿ጱොဩਖ਼ܐᦓ᭑Ⴙ᭗አ۸
ݳɾജֵአܐᦓଆۗද࠺դᎱᦡᦇ
വគᩒා• Protocol-Oriented Programming in Swi4 - WWDC15 #408• Protocols with Associated Types - @alexisgallagher• Protocol Oriented Programming in the Real World -@_maHhewpalmer• PracIcal Protocol-Oriented-Programming -@natashatherobot
ᨀᨀᘰލFAQEmail: [email protected], GitHub: onevcat