kentana20 技忘録

技術ネタを中心に、セミナー、勉強会、書籍、会社での出来事を綴っていきます。不定期更新。

京都で開発合宿をしてきた

先週の話になりますが、開発合宿に行ってきました。

会社の有志メンバー+OBで開発合宿に行ってきた

去年の秋に

kentana20.hatenablog.com

で書いて「開発合宿+最終日の聖地巡礼のパターンが最高楽しい」と感じたので、今回もそのパターンがいいなと思っていたら、id:naoya が合宿のSlack Teamで

f:id:kentana20:20160820232709p:plain

と聖地のリクエストを出してくれたので、企画して行ってきました。今回は前回に加えて

といったメンバーが増えて総勢9名が参加しました。人数が増えると嬉しいですね。

準備・企画

行き先は近江神宮と決まっていたので、滞在は京都にしました。前回は古民家だったので、今回は京町家で探したのですが、なかなかよい物件が見つからずAirbnbで探して京都 四条にある一軒家を貸りました。

www.airbnb.jp

前回同様

という感じで企画を進めました。

持ち物

最近は宿の設備もいいので大体はなんとかなるのですが、下記を持参すると快適に合宿ライフを過ごせると思います。

  • 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頃までやっていた気がします

f:id:kentana20:20160820234925j:plain f:id:kentana20:20160820234939j:plain

8/12(金)

食事以外はほぼ開発してました。前回と異なるのは夕飯が美味しすぎて酒をしこたま飲んで夜の進捗が思うように出せなかったことです。「夕飯までに進捗を出せば何も気にせずに飲める」ということを学んだ夜でした。

  • 8:00頃 各自起床して開発開始(id:naoya, id:zimathon あたりはもっと早く起きてました)
  • あとは食事以外は基本開発してました
  • 夜は初日同様「ちはやふる」を観賞しているメンバーもいました

自分は酒飲んで落ちた後、4:00くらいに起きて作業を再開したのですが1Fに降りて行ったら id:shibayan を中心としたメンバーがずっと「ちはやふる」を観てて「なんとか聖地に行く前に近江神宮の回まで....」と15話くらいまで進捗しててビビりました。すごい。

f:id:kentana20:20160820235356j:plain

この京風お好み焼きがとっても美味しかったです。お店はこちら。

tabelog.com

f:id:kentana20:20160820235405j:plain

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 京都駅へ戻って解散

f:id:kentana20:20160820234924j:plain

近江神宮では、ちはやふるごっこをしたりして聖地を楽しみました。

大阪へ行くというメンバーや、京都でもう一泊していくメンバーもいたので、京都駅で流れ解散となりました。自分は京都の友人と少しだけ会ってから夜に帰宅しました。

やったこと

自分は今回の合宿では静的サイトジェネレーターを触ろうと考えていました。最近エンジニア採用周りを強化している最中で、自社採用ページの作りを変えたいと思っていたのでMiddlemanを触ってました。

  • Middleman基礎
  • 自社採用ページをMiddleman
  • Circle CI を使ってHerokuへデプロイ

といったところまでやりました。成果発表資料は以下。

まとめ

開発合宿はよい

10ヶ月ぶりの開発合宿でしたが、やはり最高に楽しかったです。日常から離れて開発に集中する時間は至福です。

という前回良かった点を継続しつつ、今回は参加者も増えて(前回6名に対して、今回は9名)とっても楽しかったです。

「開発合宿からの聖地巡礼」というパターンはめっちゃいいです。ガッツリ開発に集中して成果を出した後で聖地を楽しむという二度美味しいイベントですね。

Middlemanよい

  • Railsから静的サイトを作る機能だけ抜き出した感じで使いやすい
  • 開発もしやすいしデプロイ周りも充実
  • Markdownにも対応している

とだいたい欲しい機能が整っていて、開発も活発に行われているのでコイツを使って自社ページをリニューアルしていこうと思います。

他のメンバーのブログエントリ

既にいくつかブログエントリが上がっているのでリンクしときます。

rei19.hatenablog.com blog.shibayan.jp rfp.hatenablog.com minato128.hateblo.jp

次回

帰りに「次回はどこに行こうかな」と話していて

あたりもいいね、なんて話してました。みんな「楽しかった」と言ってくれたので、また開催しようと思います。

詳解Swift#8(Chapter11 拡張)

前回に続き、詳解Swiftの写経を続ける。

詳解 Swift 改訂版

詳解 Swift 改訂版

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)

プロトコルを拡張することで、メソッド、添字付けなど実装を拡張することができる。プロトコル拡張で定義したメソッドなどの実装は 既定実装 と呼ぶ。

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
    }
}

プロトコル拡張の既定実装は宣言(静的)であり、クラスの継承のように動的に動作するものではない。

所感・雑感

  • プロトコルの拡張は便利に使えそう
    • String系のプロトコルにログ出力系処理を拡張に書くとか
  • プロトコル拡張の既定実装は定義したプロトコルの使い方というか利用をある程度規定できる気がするので意図しない使い方を防げそう

詳解Swift#7(Chapter10 プロトコル)

前回に続き、詳解Swiftの写経を続ける。

詳解 Swift 改訂版

詳解 Swift 改訂版

chapter10 プロトコル

コレより前の章でもちょこちょこ出てきていたプロトコルについての話。プロトコル実装を伴わない宣言の集合 と書いている。インタフェースによく似ている。インタフェースの継承のようにインスタンスがどのような機能を持つか、ということをSwiftではプロトコルで実現していて

  • クラス
  • 構造体
  • 列挙型

のそれぞれにプロトコルを使って標準ライブラリが持つ機能を継承できるらしい。

プロトコルの宣言

プロトコル名はキャメルケースの大文字開始で宣言する。

///   `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 さん
  • クラス定義のためのプロトコル
    • 用途がクラスに限定されているプロトコルprotocol プロトコル名: class { と書く

プロトコルと型

プロトコルは型としても使える。(というか、↑の例で既に引数の型として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の写経を続ける。

詳解 Swift 改訂版

詳解 Swift 改訂版

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なのかをチェックしなければいけない。

  • オプショナル束縛構文
    • if let x = オプショナル型 的なアレ
  • 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の写経を続ける。

詳解 Swift 改訂版

詳解 Swift 改訂版

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 を出力
  • クラスの継承
    • クラスを継承した場合、メソッドやプロパティなど殆どのものは引き継ぐが、イニシャライザは継承しない
    • override キーワードでスーパークラスメソッドをオーバーライドできる
    • サブクラスの定義は self. , スーパークラスの定義は super. キーワードを使う
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 キーワードで書くメソッドとプロパティ
    • タイププロパティとは異なり、サブクラスでの上書きができる(タイプメソッド・タイププロパティはサブクラスでは上書きできない)
    • クラスプロパティは計算型に限定されていて、格納型は定義できない
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つは備えている必要がある。


これは↓の画像を見るのが一番理解が早い。

  1. 簡易イニシャライザ
  2. 指定イニシャライザ
  3. スーパークラスの指定イニシャライザ
  4. ...

という順序でイニシャライズされる。

initialize.jpg

イニシャライズ時の制約も結構ある。

  1. 指定イニシャライザはクラスで追加されている変数、定数の初期化をしなければいけない
  2. サブクラスのイニシャライザはスーパークラスのイニシャライズが終わるまではスーパークラスで定義されている変数、定数を扱うことはできない
  3. 簡易イニシャライザは指定イニシャライザの初期化処理が終わるまで変数、定数を扱うことはできない

  4. 継承しているクラスのイニシャライザのサンプル

// 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 処理を明示的に書いていったほうがよい
    • ネイティブアプリのメモリ管理はシビアそうなので、チューニングやコーナーケースのデバッグなどではこの対応を行うケースがありそう
  • 遅延格納型プロパティ

    • 使えそう
    • ネイティブアプリにおいては遅延評価をするとパフォーマンスを意識したプログラミングができそう
  • ほか

    • ここまで読んで xxxx.dynamicType の存在を知った。ほっとした。