More Related Content Similar to プロトコル指向に想う世界観 #__swift__ (20) More from Tomohiro Kumagai (20) プロトコル指向に想う世界観 #__swift__2. 熊谷友宏
Xcode 5 徹底解説 MOSA
Xcode 5 の全機能を
徹底的に解説した本
OSX/iOS 系の歴史深い
有料会員制の勉強会
紙版は絶版、電子書籍は販売中
Xcode 7 でも役立つはず 法人会員も多数
@es_kumagai
EZ-NET http://ez-net.jp/
書籍 / 登壇
3. 熊谷友宏
横浜 iPhone 開発者勉強会
#yidev
わいわい・ゆるく、iPhone 開発者の
みんなで楽しく過ごすのが目的の会
【 横浜・馬車道 】
カジュアル Swift 勉強会
#cswift
ゆるくみんなで Swift を語らえる場を
作りたくて始めた会
【 横浜・青葉台 】
第23回を 2016-05-07 に開催予定 第6回を 2016-04-02 に開催
@es_kumagai
EZ-NET http://ez-net.jp/
勉強会
5. CodePiece for OS X
勉強会を楽しむアプリ
ソースコードを Twitter と Gist に同時投稿できる
勉強会で知見をみんなと共有したい時とかに便利!
できること
#__swift__
15. プロトコル
▶ 概念を型で実現する … 実装
▶ その性質を持つことが約束される
適用
struct Location : Movable {
func moved(x x:Int, y:Int) -> Location {
return movedHorizontal(x).movedVertical(y)
}
func movedHorizontal(x:Int) -> Location {
return Location(x: self.x + x, y: self.y)
}
func movedVertical(y:Int) -> Location {
return Location(x: self.x, y: self.y + y)
}
}
20. ▶ プロトコルの概念だけで説明する
▶ 既存の概念から新しい概念を作る … 実装?
protocol Movable {
func moved(x x:Int, y:Int) -> Self
func movedHorizontal(x:Int) -> Self
func movedVertical(y:Int) -> Self
}
extension Movable {
func moved(x x:Int, y:Int) -> Self {
return movedHorizontal(x).movedVertical(y)
}
}
プロトコル拡張
新概念を作る
23. ジェネリック関数
▶ Movable に対応する型、みたいに指定
▶ プロトコルで規定した性質だけで組み立てる
任意の型を受け取れる関数
func randomMove<T:Movable>(location: T, maxStep: Int) -> T {
let deltaX = Int(arc4random_uniform(UInt32(maxStep)))
let deltaY = Int(arc4random_uniform(UInt32(maxStep)))
return location.moved(x: deltaX, y: deltaY)
}
// Movable に対応した Location 型を渡せる
var location = Location(x: 0, y: 0)
location = randomMove(location, maxStep: 100)
26. extension Location : CollectionType {
var startIndex: Int {
return 0
}
var endIndex: Int {
return 2
}
subscript (index: Int) -> Int {
switch index {
case 0: return x
case 1: return y
default: fatalError()
}
}
}
CollectionType
型に適用
27. // 要素の数を取得できる
location.count
// 最初の要素 (x) と最後の要素 (y) を取得できる
location.first!
location.last!
// 要素を順次処理 (x -> y) できる
for v in location {
}
// すべての要素 (x, y) をそれぞれ 2 倍にした配列を取得する
location.map { $0 * 2 }
// x, y で、小さい方の値や大きい方の値を取得できる
location.minElement()
location.maxElement()
CollectionType
配列のように振る舞う型になる
30. protocol PlayerType {
var content: Music? { get }
var canPlay: Bool { get }
func play() throws
}
プロトコル自作の難しさ
インターフェイスを規定する
31. final class MusicPlayer : PlayerType {
var content: Music?
var canPlay: Bool {
return content != nil
}
func play() throws {
guard canPlay else {
throw PlayerError.NotReady
}
try AVAudioPlayer(data: content!.data).play()
}
}
プロトコル自作の難しさ
型にプロトコルを適用する
32. let player = MusicPlayer(content: Music("SomeMusic.mp3"))
if player.canPlay {
try player.play()
}
プロトコル自作の難しさ
型にプロトコルを適用したときの動き
ちゃんと動く
再生 !
34. final class MusicPlayer : PlayerType {
var content: Music?
var canPlay: Bool {
return content != nil
}
func play() throws {
guard canPlay else {
throw PlayerError.NotReady
}
try AVAudioPlayer(data: content!.data).play()
}
}
プロトコル自作の難しさ
プロトコルの概念だけで作られた部分
型に依存しない
35. extension PlayerType {
var canPlay: Bool {
return content != nil
}
}
final class MusicPlayer : PlayerType {
var content: Music?
func play() throws {
guard canPlay else {
throw PlayerError.NotReady
}
try AVAudioPlayer(data: content!.data).play()
}
}
プロトコル自作の難しさ
プロトコル拡張で規定する
プロトコル拡張
に移行
36. final class MusicPlayer : PlayerType {
var content: Music?
func play() throws {
guard canPlay else {
throw PlayerError.NotReady
}
try AVAudioPlayer(data: content!.data).play()
}
}
プロトコル自作の難しさ
プロトコルの概念だけで作られた部分
型に依存しない
37. extension PlayerType {
var canPlay: Bool {
return content != nil
}
func play() throws {
guard canPlay else {
throw PlayerError.NotReady
}
try AVAudioPlayer(data: content!.data).play()
}
}
final class MusicPlayer : PlayerType {
var content: Music?
}
プロトコル自作の難しさ
プロトコル拡張で規定する
プロトコル拡張
に移行
38. let player = MusicPlayer(content: Music("SomeMusic.mp3"))
if player.canPlay {
try player.play()
}
プロトコル自作の難しさ
プロトコル拡張で規定したときの動き
ちゃんと動く
再生 !
41. let player1 = MusicPlayer(content: Music("SomeMusic.mp3"))
let player2 = SilentSoundPlayer()
if player1.canPlay {
try player1.play()
}
if player2.canPlay {
try player2.play()
}
プロトコル自作の難しさ
別の型では独自に実装したときの動き
それぞれ期待通りに動く
再生 !
再生 !
42. // どんな PlayerType にも対応した再生関数を用意
func start<T:PlayerType>(player: T) throws {
if player.canPlay {
try player.play()
}
}
// 用意した関数で再生する
let player1 = MusicPlayer(content: Music("SomeMusic.mp3"))
let player2 = SilentSoundPlayer()
try start(player1)
try start(player2)
プロトコル自作の難しさ
別の型では独自に実装したときの動き
ジェネリック関数で共通化してみる
どちらも
再生 !
44. protocol PlayerType {
var content: Music? { get }
// var canPlay: Bool { get }
func play() throws
}
extension PlayerType {
var canPlay: Bool {
return content != nil
}
func play() throws {
guard canPlay else {
throw PlayerError.NotReady
}
try AVAudioPlayer(data: content!.data).play()
}
}
プロトコルから
宣言を消去
45. if player1.canPlay {
try player1.play()
}
if player2.canPlay {
try player2.play()
}
プロトコル自作の難しさ
canPlay を宣言から省いたときの動き
再生 !
こちらは動きがおかしくなる…?
try start(player1)
try start(player2)
こちらはちゃんと動く
再生 !
player1 は 再生される
player2 は 再生されない
47. protocol PlayerType {
var content: Music? { get }
// var canPlay: Bool { get }
func play() throws
}
extension PlayerType {
var canPlay: Bool {
return content != nil
}
func play() throws {
guard canPlay else {
throw PlayerError.NotReady
}
let data = content?.data ?? NullSound().data
try AVAudioPlayer(data: data).play()
}
}
content = nil 時の
無音再生に対応
48. final class SilentSoundPlayer : PlayerType {
let content: Music? = nil
var canPlay: Bool {
return true
}
// func play() throws {
//
// guard canPlay else {
// throw PlayerError.NotReady
// }
//
// try AVAudioPlayer(data: NullSound().data).play()
// }
}
プロトコル拡張で
対応したので消去
49. こちらも動きがおかしくなる…?
if player1.canPlay {
try player1.play()
}
if player2.canPlay {
try player2.play()
}
プロトコル自作の難しさ
play をプロトコル拡張で共通化したときの動き
再生 !
こちらの動きは従前通りのおかしさ
try start(player1)
try start(player2)
player1 は 再生される
player2 は 再生されない
canPlay は 成功
play 実行時に NotReady エラー
82. 先天性と後天性
要約
▶ プロトコル拡張で規定したものは 先天的
▶ 型で規定したものは 後天的
// 人間には独自の遊泳を定義
class Human:Swimmable {
func swim() {
…
}
}
// 犬の遊泳は本能に従う
class Dog:Swimmable {
}
// プロトコルの定義
protocol Swimmable {
func swim()
}
extension Swimmable {
func swim() {
…
}
}
87. 型からの解放
▶ プロトコルで引数の種類を特定する
▶ 実際の型に囚われない
ヒントは ジェネリック関数
func randomMove<T:Movable>(location: T, maxStep: Int) -> T {
…
}
// Movable に対応した Location 型を渡せる
var location = Location(x: 0, y: 0)
location = randomMove(location, maxStep: 100)
95. プロトコル自作の難しさ
canPlay を宣言から省いたときの動き
// このうちの player2 で不思議な動作が起こる
let player1 = MusicPlayer(content: Music("SomeMusic.mp3"))
let player2 = SilentSoundPlayer()
// どんな PlayerType にも対応した再生関数を用意
func start<T:PlayerType>(player: T) throws {
if player.canPlay {
try player.play()
}
}
ジェネリック引数は
型に囚われない
精神世界に解き放たれる
〓
96. protocol PlayerType {
var content: Music? { get }
// var canPlay: Bool { get }
func play() throws
}
extension PlayerType {
var canPlay: Bool {
return content != nil
}
func play() throws {
guard canPlay else {
throw PlayerError.NotReady
}
try AVAudioPlayer(data: content!.data).play()
}
}
プロトコルから
宣言を消去
物質世界に干渉しない
〓
97. if player1.canPlay {
try player1.play()
}
if player2.canPlay {
try player2.play()
}
プロトコル自作の難しさ
canPlay を宣言から省いたときの動き
再生 !
こちらは動きがおかしくなる…?
try start(player1)
try start(player2)
こちらはちゃんと動く
再生 !
player1 は 再生される
player2 は 再生されない
100. protocol PlayerType {
var content: Music? { get }
// var canPlay: Bool { get }
func play() throws
}
extension PlayerType {
var canPlay: Bool {
return content != nil
}
func play() throws {
guard canPlay else {
throw PlayerError.NotReady
}
let data = content?.data ?? NullSound().data
try AVAudioPlayer(data: data).play()
}
}
content = nil 時の
無音再生に対応
本能側を調整
〓
プロトコルに
宣言しない
物質世界に干渉しない
〓
101. final class SilentSoundPlayer : PlayerType {
let content: Music? = nil
var canPlay: Bool {
return true
}
// func play() throws {
//
// guard canPlay else {
// throw PlayerError.NotReady
// }
//
// try AVAudioPlayer(data: NullSound().data).play()
// }
}
プロトコル拡張で
対応したので消去
本能に委ねる
〓
102. こちらも動きがおかしくなる…?
if player1.canPlay {
try player1.play()
}
if player2.canPlay {
try player2.play()
}
プロトコル自作の難しさ
play をプロトコル拡張で共通化したときの動き
再生 !
こちらの動きは従前通りのおかしさ
try start(player1)
try start(player2)
player1 は 再生される
player2 は 再生されない
canPlay は 成功
play 実行時に NotReady エラー