京都で開発合宿をしてきた
先週の話になりますが、開発合宿に行ってきました。
会社の有志メンバー+OBで開発合宿に行ってきた
去年の秋に
で書いて「開発合宿+最終日の聖地巡礼のパターンが最高楽しい」と感じたので、今回もそのパターンがいいなと思っていたら、id:naoya が合宿のSlack Teamで
と聖地のリクエストを出してくれたので、企画して行ってきました。今回は前回に加えて
といったメンバーが増えて総勢9名が参加しました。人数が増えると嬉しいですね。
準備・企画
行き先は近江神宮と決まっていたので、滞在は京都にしました。前回は古民家だったので、今回は京町家で探したのですが、なかなかよい物件が見つからずAirbnbで探して京都 四条にある一軒家を貸りました。
前回同様
という感じで企画を進めました。
持ち物
最近は宿の設備もいいので大体はなんとかなるのですが、下記を持参すると快適に合宿ライフを過ごせると思います。
- Wi-Fi回線
- 現地のネット回線が弱かった場合にクライアントを分散できる
- 電源タップ
- 3人に1つくらいの分量だとちょうどよい
- コンセントは3P->2Pの変換ができるものがよい
スケジュール
ざっくりですが、こんな感じです。
8/11(木・祝)
前回同様、開発開始時に自分のネタをあまり共有せずにスタートしました。最終日の成果発表LTまでネタをほとんど共有せずにやっていたので、成果発表がなかなか盛り上がりました。
- 13:00 京都駅集合(出発地点が東京、品川、新横浜、羽田と異なっていたので現地集合にしました)
- 13:30 滞在先の民家へ
- 14:00 荷物を預けてランチ+買い出し
- 16:00 チェックイン
- 16:30 スケジュールを共有して開発スタート!!
- 20:30 夕飯(九条ねぎを堪能できる居酒屋)
- 23:00 風呂(近くの銭湯へ)
- 24:00 「ちはやふる」観賞 第5話くらいまで?
- あとは開発を続ける人は続けて、眠くなったら寝て終了
- 自分は27:00頃までやっていた気がします
8/12(金)
食事以外はほぼ開発してました。前回と異なるのは夕飯が美味しすぎて酒をしこたま飲んで夜の進捗が思うように出せなかったことです。「夕飯までに進捗を出せば何も気にせずに飲める」ということを学んだ夜でした。
- 8:00頃 各自起床して開発開始(id:naoya, id:zimathon あたりはもっと早く起きてました)
- あとは食事以外は基本開発してました
- 夜は初日同様「ちはやふる」を観賞しているメンバーもいました
自分は酒飲んで落ちた後、4:00くらいに起きて作業を再開したのですが1Fに降りて行ったら id:shibayan を中心としたメンバーがずっと「ちはやふる」を観てて「なんとか聖地に行く前に近江神宮の回まで....」と15話くらいまで進捗しててビビりました。すごい。
この京風お好み焼きがとっても美味しかったです。お店はこちら。
8/13(土)
チェックアウトが10:00までだったので、9:00までにチェックアウトの準備を済ませて成果発表LTをした後で、聖地へ寄り道してから帰宅しました。
- 6:00頃~9:00 開発追い込みしつつ、チェックアウト準備
- 9:00~10:00 成果発表LT
- 10:00 チェックアウトして在来線で大津京へ
- 11:30~13:00 近江神宮へ聖地巡礼
- 13:30 新福菜館でランチ
- 14:30 京都駅へ戻って解散
近江神宮では、ちはやふるごっこをしたりして聖地を楽しみました。
ちはやふるごっこ中です pic.twitter.com/lPOkjJ76Ca
— Naoya Ito (@naoya_ito) 2016年8月13日
聖地巡礼中 #ちはやふる pic.twitter.com/NjZv6MKvOu
— kensuke tanaka (@kentana20) 2016年8月13日
大阪へ行くというメンバーや、京都でもう一泊していくメンバーもいたので、京都駅で流れ解散となりました。自分は京都の友人と少しだけ会ってから夜に帰宅しました。
やったこと
自分は今回の合宿では静的サイトジェネレーターを触ろうと考えていました。最近エンジニア採用周りを強化している最中で、自社採用ページの作りを変えたいと思っていたのでMiddlemanを触ってました。
といったところまでやりました。成果発表資料は以下。
まとめ
開発合宿はよい
10ヶ月ぶりの開発合宿でしたが、やはり最高に楽しかったです。日常から離れて開発に集中する時間は至福です。
という前回良かった点を継続しつつ、今回は参加者も増えて(前回6名に対して、今回は9名)とっても楽しかったです。
「開発合宿からの聖地巡礼」というパターンはめっちゃいいです。ガッツリ開発に集中して成果を出した後で聖地を楽しむという二度美味しいイベントですね。
Middlemanよい
とだいたい欲しい機能が整っていて、開発も活発に行われているのでコイツを使って自社ページをリニューアルしていこうと思います。
他のメンバーのブログエントリ
既にいくつかブログエントリが上がっているのでリンクしときます。
rei19.hatenablog.com blog.shibayan.jp rfp.hatenablog.com minato128.hateblo.jp
次回
帰りに「次回はどこに行こうかな」と話していて
- 秩父(あの花)
- 宇治(響け!ユーフォニアム)
あたりもいいね、なんて話してました。みんな「楽しかった」と言ってくれたので、また開催しようと思います。
詳解Swift#8(Chapter11 拡張)
前回に続き、詳解Swiftの写経を続ける。
- 作者: 荻原剛志
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2015/12/25
- メディア: 単行本
- この商品を含むブログを見る
chapter11 拡張
継承や型そのもののコード変更を行わずに、既に存在している型にメソッドやプロパティを追加できる機能。Objective-Cでは「カテゴリ」と呼ばれていたらしい。強力だと言っている。
拡張の宣言
extension 型名: プロトコル { イニシャライザ 計算型プロパティ メソッド その他の定義 }
という定義の仕方をする。
- 既存の型に対する拡張
extension String { var length: Int { return self.characters.count } }
クラスに対して拡張定義をした場合、サブクラスにも適用される。ただ、その拡張はfinalで定義されたものとみなされるのでサブクラス側で変更したりはできない。
拡張定義とプロトコルへの適合
truct Ounce { var mL:Double = 0.0 static let ounceUS = 29.5735 init(ounce:Double) { self.ounce = ounce } var ounce: Double { get { return mL / Ounce.ounceUS } set { mL = newValue * Ounce.ounceUS } } }
このOunce構造体にプロトコル拡張を定義すると以下。
:walking: extension Ounce : FloatLiteralConvertible { init(floatLiteral value: Double) { self.init(ounce: value) } }
プロトコル拡張
- 年月日を整数値で表現するプロトコル
protocol Datable { var year: Int { get } var month: Int { get } var day: Int { get } }
このプロトコルを拡張してみる。
protocol Datable { var year: Int { get } var month: Int { get } var day: Int { get } } // ツェラーの公式 func Zeller(var m:Int, _ d:Int, var _ y:Int) -> Int { if m < 3 { m += 12; --y } let leap = y + y / 4 - y / 100 + y / 400 return (leap + (13 * m + 8) / 5 + d) % 7 } extension Datable { var dayOfWeek: Int { return Zeller(month, day, year) } } struct Date : Datable { var year, month, day: Int } let d1 = Date(year:1992, month:7, day:19) print(d1) print(d1.dayOfWeek)
プロトコルを拡張することで、メソッド、添字付けなど実装を拡張することができる。プロトコル拡張で定義したメソッドなどの実装は 既定実装 と呼ぶ。
- 2つのプロトコル拡張
protocol DateType { var year: Int { get } var month: Int { get } var day: Int { get } var area: String? { get } } extension DateType { func toString() -> String { return "\(year). \(month). \(day)" } } protocol TimeType { var hour: Int { get } var minute: Int { get } var area: String? { get } } extension TimeType { func toString() -> String { var s = (hour < 10 ? " " : "") + "\(hour)" s += ":" + (minute < 10 ? "0" : "") + "\(minute)" if let a = area { s += " (\(a))" } return s } } struct Date : DateType, TimeType { let year, month, day: Int let hour, minute: Int let area: String? init(_ y:Int, _ m:Int, _ d:Int, _ t:(Int, Int), area:String? = nil) { year = y; month = m; day = d; (hour, minute) = t; self.area = area } }
プロトコル拡張の既定実装は宣言(静的)であり、クラスの継承のように動的に動作するものではない。
所感・雑感
詳解Swift#7(Chapter10 プロトコル)
前回に続き、詳解Swiftの写経を続ける。
- 作者: 荻原剛志
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2015/12/25
- メディア: 単行本
- この商品を含むブログを見る
chapter10 プロトコル
コレより前の章でもちょこちょこ出てきていたプロトコルについての話。プロトコルは 実装を伴わない宣言の集合 と書いている。インタフェースによく似ている。インタフェースの継承のようにインスタンスがどのような機能を持つか、ということをSwiftではプロトコルで実現していて
- クラス
- 構造体
- 列挙型
のそれぞれにプロトコルを使って標準ライブラリが持つ機能を継承できるらしい。
プロトコルの宣言
プロトコル名はキャメルケースの大文字開始で宣言する。
CustomStringConvertible
の定義stdlib/public/core/OutputStream.swift
に定義されてる- https://github.com/apple/swift/blob/55be501ad6d4d103db7b4ba303aa54343acaa779/stdlib/public/core/OutputStream.swift#L54
/// `CustomStringConvertible` public protocol CustomDebugStringConvertible { /// A textual representation of the instance, suitable for debugging. var debugDescription: String { get } }
protocol Personal { var name: String { get } init(name: String) func sayHelloto(p:Personal) func saySomething() -> String }
メソッドは宣言だけして、コードブロック {}
は記述しない。
- イニシャライザ
プロトコルのイニシャライザは、継承するクラスでは必須イニシャライザとなるので、 required
修飾子を付ける必要がある。
protocol Personal { var name: String { get } init(name: String) func sayHelloTo(p:Personal) func saySomething() -> String } class Citizen: Personal { let name: String required init(name: String) { self.name = name } func sayHelloTo(p: Personal) { print("どうも、 \(p.name) さん") } func saySomething() -> String { return "なるほど" } } var ct = Citizen(name: "kentana") var ct2 = Citizen(name: "tanaken") ct.saySomething() // なるほど ct.sayHelloTo(ct2) // どうも、 tanaken さん ct2.sayHelloTo(ct) // どうも、 kentana さん
プロトコルと型
プロトコルは型としても使える。(というか、↑の例で既に引数の型としてPersonalを使ってる)
enum Sex { case Male, Female } protocol HealthInfo: Personal { // Personalを継承 var weight: Double { get set } var height: Double { get set } var sex: Sex? { get } }
// 新しく合成したプロトコルを定義 protocol HelthyPerson: Healthy, Personal {} // 定義せずに合成して型とする var teller: protocol<Healthy, Personal>
- プロトコルの適合の評価
exp is P // 適合している場合はtrue, していない場合はfalse exp as P // プロトコルP型にキャストされる。適合してない場合は実行時エラー exp as? P // プロトコルP型にダウンキャストされる。適合してない場合はnil exp as! P // プロトコルP型にダウンキャストされる。適合してない場合は実行時エラー
- オプション項目のあるプロトコル
@objc protocol PersonalData { var name: String { get } optional avr bloodtype: String { get } optional func age() -> Int } var p: PersonalData = ... if let btype = p.bloodtype { print(btype) } if let a = p.age?() { print("hoge") }
プロトコルと付属型
protocol VectorType { typealias Element var x : Element { get set } var y : Element { get set } } struct VectorFloat : VectorType { var x, y: Float func convToDouble() -> VectorDouble { return VectorDouble( x: VectorDouble.Element(x), y: VectorDouble.Element(y)) } } struct VectorDouble : VectorType, CustomStringConvertible { var x, y: Double var description: String { return "[\(x), \(y)]" } } struct VectorGrade : VectorType { enum Element { case A, B, C, D, X } var x, y: Element } var a = VectorFloat(x: 10.0, y: 12.0) print("x:\(a.x), y:\(a.y)") let b = a.convToDouble() print(b) // [10.0, 12.0] var g = VectorGrade(x: .A, y: .C) print(g) // VectorGrade(x: VectorGrade.Element.A, y: VectorGrade.Element.C)
プロトコルに適合する型の定義方法
Swiftのデータ型の多くは構造体で定義されているが、この構造体も標準のプロトコルを使っている。ここに合わせる形で定義するデータ型も標準のプロトコルに適合させると、既存のデータ型と同じように使える。これは確かによいかもしれない。
- Comparable / Equatable
- 比較演算子を使えるようになる
- ForwardIndexType
for-in
で繰り返しできるように範囲を作れる
enum WeekDay: Int, ForwardIndexType { case Sun, Mon, Tue, Wed, Thu, Fri, Sat case _dummy func successor() -> WeekDay { if self == ._dummy { return ._dummy } return WeekDay(rawValue: self.rawValue + 1)! } } var d = WeekDay.Sun print(d.rawValue) // 0 print(d.successor().rawValue) // 1 var r = Range<WeekDay>(start:.Mon, end:.Fri) for i in WeekDay.Sun ... WeekDay.Sat { print(i.rawValue, terminator:" ") // 0 1 2 3 4 5 6 } for i in WeekDay.Mon ... .Sat { print(i.rawValue, terminator:" ") // 1 2 3 4 5 6 }
- 継承絡みのプロトコル
protocol Copyable { func copy() -> Self } class GeoPoint : Copyable, Equatable, CustomStringConvertible { let latitude: Double // 緯度 let longitude: Double // 経度 required init(lat:Double, lon:Double) { // 必須イニシャライザ self.latitude = lat self.longitude = lon } func copy() -> Self { let nw = self.dynamicType.init(lat:latitude, lon:longitude) return nw } var description: String { return "GeoPoint:\(latitude), \(longitude)" } } func == (lhs:GeoPoint, rhs:GeoPoint) -> Bool { return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude }
継承が絡んで来る場合、Selfではなく self.dynamicType
を使わないとサブクラス側から呼び出された場合にスーパークラスのインスタンスを参照してしまうので注意が必要。
所感・雑感
- プロトコルはC#, Javaなどでいうとこのインタフェース。特に違和感なく使えそう
- Swift標準を意識してプログラムを書くとプロトコルに適合するようになるのかもしれない
- プロトコルの合成はインタフェースの組み合わせを自在に定義できるので、シンプルなプロトコルをいくつか定義していおいて、それらを組み合わせてかなり便利に使えそう
@objc
修飾子は Objective-C と組み合わせる意図はなく、オプショナルの項目を作りたい場合でも定義できるのは名前が直感的でないのでイマイチ感ある。そして@objc
修飾したプロトコルはクラスでしか使えない。これは覚えられる気がしないので、実戦で掴んでいかないといかん。- 付属型はちょっとよくわからない。というか、複雑なコードになったときに活躍しそうなので、いまはまだ評価できないという印象
- Self難しい。
詳解Swift#6(Chapter9 メモリ管理)
前回に続き、詳解Swiftの写経を続ける。
- 作者: 荻原剛志
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2015/12/25
- メディア: 単行本
- この商品を含むブログを見る
chapter9 メモリ管理
Swiftでは
のみが参照型のデータ。多くの場合はメモリ管理を意識しなくてもよい(ホントか?)が、特定の状況下ではリファレンスカウンタはじめ、メモリ管理の概要を把握しておくべき、と書かれている。
参照型データとARC
- リファレンスカウンタの概念
class Person : CustomStringConvertible { let name:String var age:Int init(name:String, age:Int) { self.name = name; self.age = age } var description: String { return "\(name), \(age)" } deinit { print("\(name): deinit") } } var yuta:Person! = Person(name:"勇太", age:16) print(yuta) var x:Person! = yuta x.age++ print(yuta) print(yuta === x) yuta = nil // ここではリファレンスカウンタが 2 -> 1 になるだけなので、deinitされない x = nil //ここでリファレンスカウンタが0となるので、 deinitされる
演算子の話: ===
同一性を調べる、 !==
同一でないことを調べる。
- ARC(アーク)
- コンパイル時にリファレンスカウンタの増減タイミングを決定するメモリ管理方式
- Objective-C では手動で管理していた
強い参照の循環
- インスタンスが解放されない例
class Student : CustomStringConvertible { let name: String // 名前 var club: Club? // 所属クラブ init(name:String) { self.name = name } var description: String { var s = "\(name)" if let mem = club { s += ", \(mem.name)" } return s } deinit { // デイニシャライザ print("\(name): deinit") } } class Club { let name: String // クラブ名 var members = [Student]() // 所属学生のリスト init(name:String) { self.name = name } func add(p:Student) { // 学生を追加する members.append(p) p.club = self // 学生の所属も書き換える } deinit { // デイニシャライザ print("Club \(name): deinit") } } var tinyClub: Club! = Club(name:"昼寝同好会") var yuji:Student! = Student(name:"悠二") tinyClub.add(yuji) print(yuji) yuji = nil // deinitされない tinyClub = nil // deinitされない
インスタンスの循環参照が発生している場合はARCによってメモリ解放されない
- 弱参照 / 強参照
- 弱参照
weak
修飾子を変数の前に付ける- リファレンスカウンタの値に影響しない
- 強参照
- 通常の参照
- リファレンスカウンタの値に影響する
- 弱参照
参照の種類 | 定義の仕方 | リファレンスカウンタへの影響 | 備考 |
---|---|---|---|
弱参照 | weak 修飾子を付与 |
なし | オプショナル型である必要がある |
弱参照 | 通常の変数呼び出し | あり |
まとめるとこんな感じですね。
- 非所有参照
弱参照と同様にリファレンスカウンタに影響しない参照方法。 unowned
修飾子を付ける。型にオプショナルは指定できない。「変数の値がnilにならないことが明らかな場合」は弱参照ではなく、非所有参照を使う方が高速に動作し、Appleも推奨している。
var hg1: Hoge! = Hoge() unowned var hg2: Hoge = hg1 print(hg2) // Hoge hg1 = nil //print(hg2) <- error
参照先が解放された後で参照すると実行時エラーとなる。
オプショナルチェーン
オプショナル型はプロパティやメソッドの返り値で、nilが返る可能性がある場合に使われる。オプショナル型の値を !
で開示して使用する場合、その値がnilなのかをチェックしなければいけない。
などの記述も用意されているが、オプショナルチェーンもその1つ。
- オプショナルチェーン
class Student { let name: String // 名前 weak var club: Club? // 弱い参照 init(name:String) { self.name = name } } class Teacher { let name: String // 名前 var subject: String? = nil // 担当教科 init(name:String) { self.name = name } } class Club { let name: String // 名前 weak var teacher: Teacher? // 弱い参照 var budget = 0 // 予算 var members = [Student]() init(name:String) { self.name = name } func add(p:Student) { members.append(p) p.club = self } func legal() -> Bool { // 公認クラブか? return members.count >= 5 && teacher != nil } } var who: Student? = Student(name: "kentana20") //who!.club!.teacher!.name <- error if let w = who { if let cl = w.club { if let tc = cl.teacher { print(tc.name) } } } // ↑をオプショナルチェーンで書くと↓ if let name = who?.club?.teacher?.name { print(name) }
!
による開示の代わりに ?
を使って目的の値を記述する。どこかでnilが返る場合はオプショナルチェーンの値はnilとなる。「値がある場合とない場合で処理が異る場合」は重宝しそう。
- オプショナルチェーンでメソッド呼び出し
var recognized = who?.club?.legal()
この場合、recognizedはBool?型(オプショナル)となる。
所感・雑感
- 参照型のデータを扱う場合はリファレンスカウンタを意識しないといけない
- 弱参照(weak) / 非所有参照(unowned) をうまく使えば参照循環を作らず、メモリにやさしい処理になる
- オプショナルチェーンは宣言的に書けてよいが、値はオプショナル型であることに注意する
詳解Swift#5(Chapter8 クラス)
前回に続き、詳解Swiftの写経を続ける。
- 作者: 荻原剛志
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2015/12/25
- メディア: 単行本
- この商品を含むブログを見る
chapter8 クラス
クラス定義
構造体とは異なり、クラスでは 全項目イニシャライザを使えない(カプセル化の思想と相性悪)。
class Time { var hour = 0, min = 0 init(hour:Int, min:Int) { self.hour = hour; self.min = min } func add(min:Int) { // mutatingは不要 let m = self.min + min if m >= 60 { self.min = m % 60 let t = self.hour + m / 60 self.hour = t % 24 }else{ self.min = m } } func inc() { // mutatingは不要 self.add(1); } func toString() -> String { let h = hour < 10 ? " \(hour)":"\(hour)" let m = min < 10 ? "0\(min)":"\(min)" return h + ":" + m } } let t1 = Time(hour:13, min:20) let t2 = t1 print(t1.toString()) // 13:20 を出力 t1.inc() print(t2.toString()) // 13:21 を出力
- クラスの継承
class クラス名: スーパークラス名 { }
で書ける。
// Timeを継承、プロトコルを採用 class Time12 : Time, CustomStringConvertible { var pm:Bool // 新しいインスタンス変数。午後なら真 init(hour:Int, min:Int, pm:Bool) {// 新しいイニシャライザ self.pm = pm super.init(hour:hour, min:min) } // スーパークラスのイニシャライザを呼び出して使う override convenience init(hour:Int, min:Int) { // 24時制 let isPm = hour >= 12 self.init(hour:isPm ? hour - 12 : hour, min:min, pm:isPm) } // スーパークラスのイニシャライザを上書きする override func add(min:Int) { // メソッドを上書き super.add(min) // スーパークラスのメソッド while hour >= 12 { // hourはスーパークラスの定義 hour -= 12 pm = !pm } } var description : String {// スーパークラスのメソッドを利用 return toString() + " " + (pm ? "PM" : "AM") } }
convenience
は他のイニシャライザ(この例だと直上のイニシャライザ)に処理を委譲する場合に使う。 CustomStringConvertible
はプロトコル。 description
をつけて print(Time12型のインスタンス)
で標準出力を可能にする。
これを除くと書いてることは結構シンプル。
動的結合とキャスト
クラスメソッド / クラスプロパティ
class A : CustomStringConvertible { static var className : String { return "A" } // 計算型タイププロパティ static var total = 0 // 格納型タイププロパティ class var level : Int { return 1 } // 計算型クラスプロパティ static func point() -> Int { return 1000 } // タイプメソッド class func say() -> String { return "Ah." } // クラスメソッド init() { ++A.total } var description: String { return "\(A.total): \(A.className), " + "Level=\(A.level), \(A.point())pt, \(A.say())" } } class B : A { // override static var className : String { return "B" } // 定義不可. error: class var overrides a 'final' class var // static var total = 0 // 定義不可. error: cannot override with a stored property 'total' override class var level : Int { return 2 } // override static func point() -> Int { return 2000 } // 定義不可. error: class method overrides a 'final' class method override class func say() -> String { return "Boo" } override init() { super.init(); ++B.total } override var description: String { return "\(B.total): \(B.className), " + "Level=\(B.level), \(B.point())pt, \(B.say())" } } let a = A() print(a) let b = B() print(b)
こんな感じ。
クラスのイニシャライザ
Swiftでは、ベースクラスを継承して作られているクラスのインスタンスを正しくつくるために、イニシャライザにはいくつかのルールがある。
用語の話
- 指定イニシャライザ
- そのイニシャライザだけで、インスタンスの初期化が完了する
- 簡易イニシャライザ
- ほかのイニシャライザも使ってインスタンスの初期化をする
- ほかのイニシャライザを使って初期化を任せることをデリゲートと呼ぶ
convenience
というキーワードを付ける
どのクラスも指定イニシャライザを最低1つは備えている必要がある。
- インスタンスのイニシャライズの概念
これは↓の画像を見るのが一番理解が早い。
- 簡易イニシャライザ
- 指定イニシャライザ
- スーパークラスの指定イニシャライザ
- ...
という順序でイニシャライズされる。
イニシャライズ時の制約も結構ある。
- 指定イニシャライザはクラスで追加されている変数、定数の初期化をしなければいけない
- サブクラスのイニシャライザはスーパークラスのイニシャライズが終わるまではスーパークラスで定義されている変数、定数を扱うことはできない
簡易イニシャライザは指定イニシャライザの初期化処理が終わるまで変数、定数を扱うことはできない
継承しているクラスのイニシャライザのサンプル
// Zellers congruence func dayOfWeek(var m:Int, let _ d:Int, var year y:Int) -> Int { if m < 3 { m += 12; --y } let leap = y + y / 4 - y / 100 + y / 400 return (leap + (13 * m + 8) / 5 + d) % 7 } // base class class DayOfMonth : CustomStringConvertible { var month, day: Int init(month:Int, day:Int) { self.month = month; self.day = day } var description: String { return DayOfMonth.twoDigits(month) + "/" + DayOfMonth.twoDigits(day) } class func twoDigits(n:Int) -> String { let i = n % 100 if (i < 10) { return "0\(i)" } return "\(i)" } } // sub class1 class Date: DayOfMonth { var year: Int var dow: Int init(_ y:Int, _ m:Int, _ d:Int) { year = y dow = dayOfWeek(m, d, year: y) super.init(month: m, day: d) } } // sub class2 class DateW : Date { static let namesOfDay = [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ] var dweek = String() // 指定イニシャライザ(スーパークラスのイニシャライザと引数が同じなのでoverrideする) override init(_ y:Int, _ m:Int, _ d:Int) { super.init(y, m, d) dweek = DateW.namesOfDay[dow] } // 簡易イニシャライザ convenience init(_ m: Int, _ d:Int, year:Int = 2016) { self.init(year, m, d) } // プロパティオブザーバ(didSetなので、dayの値が変更した直後に発動する) override var day:Int { didSet { dow = dayOfWeek(month, day, year:year) dweek = DateW.namesOfDay[dow] } } // printで呼ばれるプロパティの上書き(本質ではない) override var description: String { return "\(year)/" + super.description + " (\(dweek))" } } var d = DateW(1991, 5, 8) print(d, terminator:"") // 1991/05/08(Wed) d.day++ print(d, terminator:"") // 1991/05/09(Thu)
ここまで書いたところで、初期化についてのまとめ。
- パターン共通(すべてのケースに当てはまる)
- プロパティの値がすべて設定されるまで、処理はできない
- プロパティに初期値がある場合はイニシャライザで初期化しなくてもよい
- パターン1: スーパークラスがない場合
- イニシャライザで各プロパティの値を設定すればOK。シンプル
- パターン2: 同じクラスの別のイニシャライザを使う(デリゲートする)場合
- デリゲートするイニシャライザの呼出し後に変数の設定をし直せる
- パターン3: スーパークラスがある場合
- スーパークラスのイニシャライザを呼び出す前に自身のクラスで定義している変数・定数の初期化が済んでいる必要がある
少し端折ってるけど、こんな感じだと思います。
イニシャライザの継承
サブクラスで指定イニシャライザを定義していない
- サブクラスがスーパークラスの指定イニシャライザをすべて持っている
この2つの条件のどちらかを満たす場合にサブクラスでスーパークラスのイニシャライザを継承できるらしい。「2.」の意味がよくわからない。。
- 必須イニシャライザ
class ICCard { static let Deposit = 500 var money = 0 required init(charge:Int) { money = charge - ICCard.Deposit } } class Icocai : ICCard { static var serial = 0 let idnumber: Int init(id:Int, money:Int) { idnumber = id super.init(charge:money) } required init(charge:Int) { idnumber = ++Icocai.serial super.init(charge:charge) } } class Suiica : ICCard { var name = String() } var card = Suiica(charge:2000) print(card.money) // 1500 var mycard = Icocai(charge:2000) print(mycard.money) // 1500
required
なイニシャライザを持つクラスを継承したサブクラスは、同じイニシャライザの実装が必須となる。
- 失敗のあるイニシャライザ
指定イニシャライザの場合、すべての変数・定数の値が設定されるまで return nil
できないが、簡易イニシャライザの場合はいつでも return nil
できる。
通常、失敗のあるイニシャライザを使ってクラスのインスタンスを作るとオプショナル型になるが、構造体と同じく、 !
で開示すると、オプショナル型ではなくなる。
この辺りは基本的に構造体と同じ設計思想。
継承とサブクラスの定義
- プロパティの継承
スーパークラスのプロパティ(格納型、計算型どちらも)を継承して上書きできる。参照するときは super.プロパティ名
と書く。
- 継承とプロパティオブザーバ
スーパークラスのプロパティを監視できる。計算型、格納型の制限はない。
class Propα { var attr = 0 } class Propβ : Propα { override var attr : Int { willSet{ print("β: will set") } didSet{ print("β: did set") } } } class Propγ : Propβ { override var attr : Int { willSet{ print("γ: will set") } didSet{ print("γ: did set") } } } var g = Propγ() g.attr = 1
- 継承をさせない
サブクラスでの継承をさせたくないメソッド、プロパティは final
修飾子を定義の先頭に付ける。final
修飾子がついたメソッド、プロパティを継承/overrideしようとするとコンパイル時にエラーとなる。
- 継承と型推論 / クラスのメタタイプ
クラスAと、クラスAを継承したクラスBのそれぞれのインスタンスを含む配列をつくると、その配列の型はスーパークラスであるクラスAとなる。
class Hoge { var text: String init() { text = "hoge" } } class Fuga: Hoge { init(msg: String) { super.init() super.text = msg } } var h1 = Hoge() var f1 = Fuga(msg: "fuga") var array = [h1, f1] // [Hoge] array.dynamicType // Array<Hoge>.Type
xxxx.dynamicType
で実際に属するクラスを表すオブジェクトを返す。コレ欲しかったやつだ。
解放時処理
- Swiftではインスタンスは自動的に消去される
- クラスのインスタンスは参照型なので、Swiftが内部に持っているリファレンスカウンタで管理している
- いつ消去されるかは実行時までわからない
- 明示的に消すことで、ファイルのクローズやメモリの解放を行うことができる
解放処理を デイニシャライザ とよぶ。
deinit { }
で解放処理を書く。
遅延格納型プロパティ
初期化の際には値を決めずに、必要になったタイミングで初めて値を決めるプロパティのこと。
ex. 画像情報の読み込み - 画像は使われるかもしれないし、使われないかもしれない - パスだけは初期化時に指定しておく - 画像が必要になったタイミングで遅延読み込みを行うことで、画面初期化時の処理を減らせる
こんなイメージ。 lazy
キーワードをプロパティの宣言の先頭に書く。
import Foundation class FileAttr { let filename: String // 遅延格納型プロパティ lazy var size:Int = self.getFileSize() init(file: String) { filename = file } func getFileSize() -> Int { var buffer = stat() stat(filename, &buffer) print("[getFileSize]") return Int(buffer.st_size) } } let d = FileAttr(file:"/Users/kentana20/Desktop/363.png") print(d.filename) print(d.size) // getFileSize() が呼ばれる print(d.size)
- いくつかの制限がある
- クラス / 構造体定義の内部でのみ使用可能
- プロパティは定数ではなく変数とする
- 構造体の場合で、遅延評価を引き起こす可能性のあるメソッドには
mutating
が必要となる - プロパティオブザーバは指定できない
所感・雑感
クラス
イニシャライザ
- ひととおりの把握はしたものの、奥が深すぎて実戦経験積まないと手に馴染まなそう
- イニシャライザの継承は正直あまり使わずに(というか継承自体そんなに使わずに)
super.init
を基本形とするべきだと思った - イニシャライザはそんなにバンバン増やすものではないのでは。なるべく増やさないほうがインスタンスの見通しが明るくなりそうだと感じた。
継承
- オブジェクト指向言語の継承そのままといったイメージ
- クラス自体、プロパティ、メソッドの継承や
final
での制限など、すんなり使えそう - 継承の文脈ではないが、 Any, AnyObject 型はどれくらい使うのだろう。型に厳密なSwiftのよさをスポイルしてしまいそう。
解放処理
- ファイルI/O、メモリ大食いなどの処理を行うクラスについては
deinit
処理を明示的に書いていったほうがよい - ネイティブアプリのメモリ管理はシビアそうなので、チューニングやコーナーケースのデバッグなどではこの対応を行うケースがありそう
- ファイルI/O、メモリ大食いなどの処理を行うクラスについては
遅延格納型プロパティ
- 使えそう
- ネイティブアプリにおいては遅延評価をするとパフォーマンスを意識したプログラミングができそう
ほか
- ここまで読んで
xxxx.dynamicType
の存在を知った。ほっとした。
- ここまで読んで