kentana20 技忘録

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

詳解Swift#4(chapter7 演算子)

昨日に続いて、詳解Swiftの写経を続ける。

詳解 Swift 改訂版

詳解 Swift 改訂版

chapter7 演算子

演算子だけのchapterがあるってすごい。内容はさらっと。

Swift演算子

目についた部分だけメモ。

  • シフト演算
  • 四則演算
  • ビット操作

などが優先。

  • 比較
  • 条件判断

などは優先度低。

// オーバーフロー演算子
z - 1
//z - 2 <- error
z &- 2

// 剰余演算子
11 % 2.5
11.0 % 2.5

var x: Int = 10
x = 5
x % 2
x // 5
x %= 2 // 複合代入演算子
x // 1

// ビット演算子
func odd(i:Int) -> Bool { print(i); return i&1 == 1 }

var b = true
var c1 = b || odd(1)  // oddは実行されない
var c2 = !b && odd(3) // oddは実行されない

&& , || は短絡評価。

演算子の定義

Swiftでは演算子を独自に定義したり、既存の定義をオーバーライドすることもできる(できないものもある)。

infix operator 演算子 {
    precedence 数字(優先度)
    associativity 規則(left|right|none)
}

func 演算子(左項: 型, 右項: 型) {
    return 戻り値
}
prefix operator 演算子 {}
postfix operator 演算子 {}

でそれぞれ定義できる。

  • サンプル
// ~ を演算子として定義した例
infix operator 〜 {      // 二項(中置)演算子として宣言
    precedence 20        // 他の演算子より低い優先度
    associativity none   // 結合規則は「なし」
}
func 〜 (n:Int, w:Int) -> String {
    var str = String(n)  // Stringのイニシャライザで、整数を文字列にする
    let pad = w - str.characters.count  // 左に詰める空白の個数
    if pad > 0 {
        str = String(count:pad,
                     repeatedValue:Character(" ")) + str
    }
    return str
}

タプル、列挙型などのパターン、整数と区間についてのマッチングを行う演算子~= で利用できる。また、~= 演算子を独自定義することで必要に応じた場合分けができる。が、グローバルに影響を与えるので、使うのはあまり、、、、的な記述が最後にあった。

enum Rank : Int {
    case Ace = 1, Two, Three, Four, Five, Six, Seven,
    Eight, Nine, Ten, Jack, Queen, King
}

// 実体型の値と等しい時もマッチしたものとする
func ~= (p:Int, r:Rank) -> Bool {
    return p == r.rawValue
}

let hand:[Rank] = [ .Queen, .Ace, .Ten, .Six, .King, .Jack ]
for card in hand {
    switch card {
    case 1: print("Ace")
    case 13: print("King")
    case .Queen: print("Queen")
    default: print(".")
    }
}

実体型の値もマッチするので、 "Ace", "King" も出力される。

所感・雑感

演算子

  • 独自定義
    • JSの既存のprototypeにメソッドを生やすみたいに、グローバルに影響がでるので使うの慎重になりそう
    • 業務的によく利用するもの(ex. 業務時間とか、金額の計算とか)は独自演算子を作って限定的に使うのがよいかもしれない

明日も続ける。

詳解Swift#3(chapter6 パターン)

昨日に続いて、詳解Swiftの写経を続ける。

詳解 Swift 改訂版

詳解 Swift 改訂版

chapter6 パターン

いくつかのデータを組にして扱うことができる タプルと列挙型 についての章。

タプル

複数個のデータを組にしてまとめたもの。データは値型。

  • 複数個の値(データ)をまとめて扱いたい
  • 構造体・クラスを作る程でもない
  • 関数の戻り値を複数返却したい

といったシーンで活躍する。

  • シンプルなタプルの例
let m = ("monkey.jpg", 161_022)
let m2: (String, Int) = ("monkey.jpg", 161_022)

let cat: (String, Int, Int) = ("cat.jpg", 1024, 768)
var img = cat
//img = m2 <- error
var img2 = cat

print(cat.0) // cat.jpg
img.1 = 1920
// img == img2 <- error

let m3 = (file: "tiger.jpg", width: 1024, height: 768)
m3.file
m3.0

変数名: (1つ目の要素の型, 2目の要素の型, .....) = (要素1の値, 要素2の値, .....) で記述する。タプルの要素へのアクセスは タプル変数.添え字 と書く。要素にキーワードが付いている場合は タプル変数.要素キーワード名 でもアクセスできる。要素の値を書き換えることもできる。タプルの要素の1つにタプルを入れることもできる。

タプルの代入には要素の個数と型が一致していなければいけない、タプルの比較はできないなどの制約がある。

  • タプルを返す関数
func BMI(tall:Double, _ weight:Double) -> (bmi: Double, idealWeight: Double) {
    let ideal = 22.0                  // 理想的な値
    let t2 = tall * tall / 10000.0    // cm を m に換算して二乗
    let index = weight / t2           // BMIを計算
    return (index, ideal * t2)        // 目標体重も計算して返す
}

var ret = BMI(172.0, 58.5)
print(ret.bmi)
print(ret.idealWeight)

関数の戻り値を (Double, Double) とすることで、2つのDouble型の値を含むタプルを返却できる。これは便利。

  • キーワード付きのタプル
let img = ("phoenix.png", 1200, 800)
let photo = (file:"tiger.jpg", width:640, height:800)
print(img)
print(photo)
var v1 : (String, Int, Int) = img
var v2 : (file:String, width:Int, height:Int) = img
var v3 : (image:String, x:Int, y:Int) = img
v1 = photo
v2 = photo
// v3 = photo  // エラーになる
print("v1 = \(v1)")
print("v2 = \(v2)")

v3 = photo as (String, Int, Int)
print("v3 = \(v3)")

キーワード付きのタプルへの代入は同じキーワードと型を持つタプルかキーワードを持たないタプルでないとできない。↑の例だと異るキーワードを持つタプルv3への代入はエラーとなる。

列挙型

Swiftでの列挙型は列挙型の中にメソッドやプロパティの定義ができたり、イニシャライザも定義できたり拡張性が高い。

  • シンプルな列挙型
enum Direction {
    case Up
    case Down
    case Right
    case Left
}

Direction.Up
print(Direction.Up, terminator: "") // Up
enum Direction {
    case Up, Down, Right, Left
    func clockwise() -> Direction {
        switch self {
        case Up:    return Right
        case Down:  return Left
        case Right: return Down
        case Left:  return Up
        }
    }
    
    mutating func changeClock() {
        switch self {
        case Up: self = Right
        case Down: self = Left
        case Right: self = Down
        case Left:  self = Up
        }
    }
}

let d = Direction.Up
var d2 = Direction.Up
d.clockwise()
print(d) // 値は変わらない
//d.changeClock() <- error
d2.changeClock() // ここで値を変更(Up -> Right)
print(d2)

メソッド付きの列挙型。 mutating キーワードを付けると列挙型自身の値を変更するメソッドも作れる。計算型のプロパティも定義できる。

  • 列挙体のタイプメソッド、タイププロパティ
enum Direction : Int {
    case Up = 0, Right, Down, Left
    static var defaultDirection = Direction.Up
    init() {
        self = Direction.defaultDirection
    }
    static func arrow(d:Direction) -> String {
        return ["↑", "→", "↓", "←"][d.rawValue]
    }
    func clockwise() -> Direction {
        let t = (self.rawValue + 1) % 4
        return Direction(rawValue:t)!
    }
    var horizontal: Bool {
        switch self {
        case Right, Left: return true
        default: return false
        }
    }
    mutating func turnBack() {
        self = Direction(rawValue:((self.rawValue + 2) % 4))!
    }
}

Direction.defaultDirection = .Right
var e = Direction()
print(Direction.arrow(e))

構造体と同様にタイプメソッド、タイププロパティは static , イニシャライザは init で定義できる。

共用型の列挙型

enum WebColor {
    case Name(String)   // 色の名前
    case Code(String)   // 16進数によるカラーコード
    case White, Black, Red, Blue, Green, Yellow, Gray
}

let indigo = WebColor.Name("indigo")         // インディゴブルー
let turquoise: WebColor = .Code("#40E0D0")   // ターコイズ
let textColor = WebColor.Black

値型の列挙型とはまったくユースケースが異る。同じ役割のオブジェクトなんだけど、使い方(定義の仕方)が違う。みたいな場合に使えそう。

  • if-case文
enum Ticket {
    case 切符(Int, Bool, 回数券:Bool) // 普通券:価格、小人、回数券かどうか
    case カード(Int, Bool)            // プリペイドカード:残高、小人
    case 敬老パス                     // 敬老パス
}

var list:[Ticket] = [
    .切符(250, false, 回数券:false),
    .カード(3300, false),
    .敬老パス,
]

for p in list {
    /* エラー
      if p == .カード { print("プリペイドカード") }
    */
    switch p {                  // switch文ではこのように記述できる
    case .カード: print("プリペイドカード")
    default: break
    }
}

// if-case文を使うとよりシンプルに
for p in list {
    if case .カード = p {
        print("プリペイドカード")
    }
}

共用型の列挙型の配列から条件にあうものだけで処理したい場合に使える。残念だけど今のところ、どれくらい便利なのかがわからん。

  • for-in文でもcaseパターンを使える
enum Ticket {
    case 切符(Int, Bool, 回数券:Bool) // 普通券:価格、小人、回数券かどうか
    case カード(Int, Bool)            // プリペイドカード:残高、小人
    case 敬老パス                     // 敬老パス
}

var passes:[Ticket] = [
    .切符(320, false, 回数券:false),
    .切符(120, true, 回数券:true),
    .切符(240, false, 回数券:true),
    .切符(240, true, 回数券:false),
    .敬老パス,
    .カード(3300, false)
]

for case let .切符(fare, child, coupon) in passes where fare > 220 {
    var k = coupon ? "回数券" : "普通券"
    if child { k += "(小人)" }
    print(k, "\(fare)円")
}

ここまで読んで有用性が少し見えてきた気がする。列挙型の配列の中から複数条件を指定して処理を書く場合にうまく使えばシンプルに書ける。ただ、caseパターンが複数あったり、条件が複雑過ぎて横に記述が長くなるので万能ではない印象。

enum メッセージ : CustomStringConvertible {
    case 文書 (String, String)              // 差出人、文書
    case データ(String, [Int8])             // 差出人、データ列
    indirect case 転送(String, メッセージ)  // 差出人、メッセージ
    var description: String {
        switch self {
        case let 文書(from, str): return from + "(" + str + ")"
        case let データ(from, _): return from + "[データ]"
        case let 転送(from, msg): return from + "←\(msg)"
        }
    }
}

let m1 = メッセージ.文書("伊藤", "休みます")
print(m1) // 伊藤(休みます)
let m2 = メッセージ.転送("白石", m1)
print(m2) // 白石←伊藤(休みます)
let m3 = メッセージ.転送("山田", m2)
print(m3) // 山田←白石←伊藤(休みます)```

再帰的に列挙型のタプルに列挙型自身を要素として指定する場合は indirect キーワードをcaseパターンの前に記述する。

パターンマッチ

どの構造に何が当てはまるのかというルールを定義して、ルールに基づいたデータ処理をパターンマッチという。

  • タプル
  • 共用型の列挙型

で利用できる。

などでパターンを書いて、それぞれにマッチする場合の処理を記述する。

所感・雑感

  • タプル
    • 代入の要素/型制約などを見ても型についての厳格さを感じる。個人的には嫌いではないものの、実戦では少し窮屈に思う時があるかもしれない
    • タプルの要素にはキーワードを付けるのをデフォルトにしたい。添字だけだと、コードが大きくなった場合に見通しが悪くなりそう。
  • 列挙体
    • 構造体と同様に確かに拡張高い。コード系の管理とか、コードに関する振る舞いを列挙体自身のメソッドにしておくと、一貫性あるコードを書けそう。
    • 「共用型」という名前の意味がわからん。何を共用するのか。
    • if-case / for-in でのcaseなどはライブラリのコードを読んでみたい
  • パターンマッチ
    • 例が少なかったので、逆引きリファレンスとかレシピがほしい

今日はあんまり時間とれなかったので1章分。夜に時間あったらもうちょい進める。

詳解Swift#2(chapter3 構造体~5 基本的なデータ型)

昨日に続いて、詳解Swiftの読み込みと写経を続ける。

詳解 Swift 改訂版

詳解 Swift 改訂版

chapter3 構造体

構造体(Structure)についての章。

構造体の定義

struct 型名 {
    変数/定数定義
    イニシャライザ定義
    メソッド定義
    その他の定義(型、プロパティなど)
}

で書く。

  • 用語の話

    • イニシャライザ: 構造体のインスタンス生成時の手続き
    • 既定イニシャライザ: 各プロパティの初期値を記述してある構造体を引数なしで生成できるイニシャライザ
    • 全項目イニシャライザ: 既定イニシャライザの逆。個々のプロパティの値を指定して生成するイニシャライザ
  • シンプルな構造体 / 初期値 / イニシャライザ

// 初期値のない構造体
struct Date {
    var year: Int
    var month: Int
    var day: Int
}

// 初期値のある構造体
struct DateInit {
    var year: Int = 2016
    var month: Int = 5
    var day: Int = 3
}

var dx = DateInit()
print(dx, terminator:"")

var d = Date(year: 2016, month: 5, day: 3)

// "."でプロパティ名を指定して参照可能
print(String(d.year), terminator:"") // "2016"

// 演算もできる
d.day++
++d.day
print(String(d.day), terminator:"") // "5"

構造体の要素(プロパティ)は初期値があったり、なかったりする。構造体を定義しておくと、構造体のインスタンスを生成して、各プロパティに".プロパティ名"でアクセスできるし、プロパティの値を変更したりもできる。

  • 定数プロパティと構造体
struct DateX {
    var year: Int
    var month: Int
    var day: Int
}

// 定数としても代入できる
let l = DateX(year: 2016, month: 5, day: 3)
//l.day++ <- error (変更できない)

struct Time {
    let in24h: Bool = false
    var hour = 0, min = 0
}

var t1 = Time()
var t2 = Time(hour: 11, min: 0)
//var t3 = Time(in24h: true, hour: 11, min: 0) <- error

プロパティには定数も定義できる。当然、定数のプロパティは変更不可。イニシャライザによってインスタンス生成時に定数のプロパティに値をセットするケースと、初期値が定義されているケースとある。

  • イニシャライザの定義
struct Date {
    var year, month, day: Int
    init() {
        year = 2016
        month = 5
        day = 3
        self.year++
    }
}

var d = Date()
d.year // 2017

構造体に init キーワードでイニシャライザを定義できる。

  • 複数のイニシャライザ
struct Time {
    let in24h: Bool
    var hour = 0, min = 0
    
    // 1つめのイニシャライザ
    init(oHour: Int, oMin: Int) {
        in24h = false
        self.hour = oHour
        self.min = oMin
    }
    
    // 2つめのイニシャライザ
    init(hourIn24 h: Int) {
        in24h = true
        self.hour = h
    }
    
    // 3つめのイニシャライザ
    init(_ hour: Int) {
        self.init(hourIn24: hour)
    }
}

var a = Time(oHour: 11, oMin: 30)
var b = Time(hourIn24: 12)
var c = Time(13)
//var d = Time() <- error
//var e = Time(in24h: true, hour: 12, min: 0) <- error

イニシャライザは複数定義できる。またイニシャライザが定義されている場合は既定イニシャライザと全項目イニシャライザは使えなくなる。

  • 構造体を含む構造体とネスト型
struct DateWithTime {
    var date = Date()
    var time = Time(oHour: 12, oMin: 30)
}

var u = DateWithTime()
u.date.year
u.time.hour

構造体を含む構造体も作ることができる。"."の繰り返しで必要なプロパティにアクセスできるので宣言的に書ける。

struct SimpleTime  {
    var hour, min: Int
    init(_ hour:Int, _ min: Int) {
        self.hour = hour
        self.min = min
    }
}

struct PointOfTime {
    struct Date {
        var year, month, day: Int
    }
    
    typealias Time = SimpleTime
    var date: Date
    var time: Time
    init(year: Int, month: Int, day: Int, hour: Int, min: Int) {
        date = Date(year: year, month: month, day: day)
        time = Time(hour, min)
    }
    
}

メソッド

struct Clock {
    var hour = 0
    var min = 0
    func time() -> Clock {
        return Clock(hour: hour, min: min)
    }
    
    mutating func advance(min: Int) {
        let m = self.min + min
        if m >= 60 {
            self.min = m % 60
            self.hour = (self.hour + m / 60) % 24
        } else {
            self.min = m
        }
    }
    
    func nonAdvande(min: Int) {
        //self.min += min <- error
    }

    static func isPm(h: Int) -> Bool {
        return h > 12
    }

}

構造体にはメソッドを定義できる。構造体の内容を変更するメソッドには mutating キーワードを付与する必要がある。オブジェクト指向でのクラスメソッドに相当するタイプメソッドstatic キーワードを付与して定義する。タイプメソッドはイニシャライザの中でも使えるが、インスタンスメソッドはプロパティの初期設定が完了していないと使えない。

プロパティ

  • タイププロパティ

タイププロパティはクラス変数的なもの。

イニシャライザからタイププロパティ/タイプメソッドにアクセスするときは構造体の修飾子が必要。一報でタイプメソッドからタイププロパティにアクセスする場合は修飾子不要。タイププロパティを変更するメソッドには mutating キーワードは不要。

  • 格納型プロパティ
var serialNumber = 2127

struct LCD {
    struct Size { var width, height : Int }
    static var stdHeight = 1080
    static var stdWidth = 1920
    static var stdSize = Size(width: stdWidth, height:stdHeight )
    var size: Size
    let serial = "CZ:\(++serialNumber)"
    init(_ w:Int, _ h:Int) {
        size = Size(width: w, height: h)
    }
}

let small = LCD(800, 600)
print(small.serial)
LCD.stdHeight = 1200
print(LCD.stdSize)
LCD.stdWidth = 2560
print(LCD.stdSize)

プロパティに式を指定することもできる。タイププロパティの初期化のための式は値を必要としたタイミングで初めて評価され、その後は使われない(変更されない)。値を都度変更して使いたい場合は 計算型プロパティ を使う。

この辺り、慣れないと読み違えそうな雰囲気...

  • 計算型プロパティ
struct Ounce {
    var ml: Double = 0.0
    static let ounceUS = 29.5735
    
    var ounce: Double {
        get { return ml / Ounce.ounceUS }
        set { ml = newValue * Ounce.ounceUS }
    }
    
    init(ounce: Double) {
        self.ounce = ounce
    }
}

var a = Ounce(ounce: 1.5)
a.ml // 44.3625
a.ounce = 3 // ounce代入によってmlも値が変わる
a.ml // 88.7205
a.ounce // 3

クラス変数のアクセサ的なもの。set節とget節によって参照/更新を行う。set節の引数は省略可能で、 newValue という名前で引数を利用できる。

読み書き可能なプロパティを関数のinout引数に渡すこともできる。この辺り、多分実戦になると構造体のプロパティを引数に関数を呼ぶことがある気がするのでなかなか気の利いた設計になってるように感じた。

  • プロパティオブザーバ
struct Stock {
    let buyingPrice: Int
    var high = false
    var count = 0
    init(price:Int) {
        buyingPrice = price
        self.price = price
    }
    var price:Int {
        // プロパティオブザーバ(更新前に呼び出される)
        willSet {
            ++count
            high = newValue > buyingPrice
        }
        // プロパティオブザーバ(更新後に呼び出される)
        didSet {
            print("\(oldValue) => \(price)")
        }
    }
}

var st = Stock(price:400)
st.price = 410
st.price = 380
st.price = 430
print("\(st.count), \(st.high)")
st.price++
print("\(st.count), \(st.high)")

格納型プロパティの値が更新されるタイミングで別の手続きを呼び出す。更新前、更新後それぞれの手続を定義できる。Railsで言うところのフィルタみたいな機能。 willSet , didSet というコードブロックで定義する。インスタンス生成時にはプロパティオブザーバは発動しない。


添字付けはあんまり用途なさそう、というか実装したあとで可読性下がりそうなので説明は割愛


プロトコル

後のchapterで説明があるので、ここでは概念だけ。 型が持つべきメソッドやプロパティなどの宣言をまとめる仕組みプロトコルと呼ぶ。インタフェースみたいなやつ。

- Int
- Double
- String

などのデータ型はComparableプロトコルに準拠しているので、比較演算を行うことができる。

chapter4 オプショナル

Swiftの特徴の1つ。「扱うデータがない」という状態をとりうる型。頻出、超重要。

オプショナル型

  • オプショナル型とnil
var a: Int = 0
var b: Int? = 100

b = nil
//a = nil <- error

// このようにも書ける
var c: Optional<Int> = 0
c = nil
c = 5

nilを取りうる変数は型名の末尾に ? をつける。オプショナルではない変数にはnilを代入できないので、Null Pointer例外による実行時エラーを少なくできる(と期待されている)。たとえば、nilを返す可能性がある関数やイニシャライザの戻り値を受け取る変数の型はオプショナルでなくてはいけない。

  • 開示
var a: Int = 10
var c: Int? = 5

//a + c <- error
a + c!

c = nil

//a + c!  <- error

オプショナルInt型とInt型は厳密には異なるので、そのまま演算できない。オプショナル型の変数に ! を付けてデータを開示して利用する。値がnilだった場合は開示しても実行時にエラーとなる。

オプショナル束縛構文

let year: Int? = 2016
if let y = year {
    print("yearはnilじゃない。\(y)だ。")
} else {
    print("yearはnil")
}

// letじゃなくてvarでも受けられる
if var ye = year {
    print("yearはnilじゃない。\(y)だ。")
} else {
    print("yearはnil")
}

// 特別な意味を持って開示された状態で代入されるのは if/while の条件式で書いた時のみ
var z = year  // これだとフツーにオプショナル型になる

とある変数がnilじゃなかったときに必要な処理をしたいユースケースに適用できる。if-let文というらしい。条件式で代入している変数(この例だと y )はオプショナル型ではなくなるので、開示は不要となる。オプショナル束縛構文ともいうらしい。ちなみにelseに入った場合は条件式で代入した変数は使えない。

これは結構使えそうだが、1つ残念なのは三項演算子では使えないこと。三項演算子で使えないのはなんでなんだろ。

var opv: Int? = 0

(opv != nil) ? opv! : 5
opv ?? 5 //↑と同じ意味

自己代入的なあれ。これも結構使いそう。

有値オプショナル型

var year: Int! = 2020

print("あと\(year - 2016)年")
//year++ <- error
year = nil
var month: ImplicitlyUnwrappedOptional<Int> = 2020

開示しなくても通常の変数のように使える型のこと。nilを代入できてしまうが、開示は不要。はじめはnilを許可しておくのだけど、どこかのタイミングで値が入ることが保証されている場合に開示を意識せずにコードを書けるので、実戦では役に立ちそう。

func nickname(name:String?, age:Int) -> String {
    let s = name ?? "名無し"
    return "浪速の" + s + "(\(age)歳)"
}

これはめっちゃ実践的なコードな気がする。関数の引数にnilが入って来る場合を考慮して let s で受けて自己代入して実際の処理を行うパターン。

失敗のあるイニシャライザ

struct Time {
    let in24h: Bool
    var hour = 0, min = 0
    init?(_ h:Int, _ m:Int, in24h:Bool = false) {
        let maxh = in24h ? 23 : 11
        if h < 0 || h > maxh || m < 0 || m > 59 {
            return nil
        }
        self.in24h = in24h
        hour = h
        min = m
    }
    init(time:Time, in24h:Bool) {
        var h = time.hour
        if !in24h && time.hour > 11 {
            h -= 12
        }
        self.in24h = in24h
        hour = h
        min = time.min
    }
}

インスタンス初期化時にnilを返す可能性があるイニシャライザ。データが作れなかったり、引数に不備があったりして初期化に失敗した場合にnilを返す。 init? でイニシャライザを定義する。イニシャライザの中で例外ケースの場合に return nil する。

chapter5 基本的なデータ型

この章はデータ型メインの話なのでさらっと。

整数と実数

  • 数値型リテラル
    • 桁数の大きい数値を見やすくするために _ を使える
    • 16進数の場合は 0x , 8進数の場合は 0o で表す(両方小文字)
  • Swiftには暗黙の型変換がないので、異なる型の演算には型変換が必要
    • 型変換例
Int.init()
Int.init(_: Double)
Int.init(_: UInt)
.
.
.
演算子 意味
半開区間型 A..<B A ≦ x < B
閉区間型 A...B A ≦ x ≦ B
範囲型 A..<B A ≦ x < B
範囲型 A...B A ≦ x ≦ B

半開区間型と閉区間型はわかるけど、範囲型で書き直す意味がよくわからない。

配列

コレクションの1種。最初に宣言した型の値しか格納できない。配列はCollectionTypeというプロトコルに準拠している。

  • 部分配列
var days = ["日", "月", "火", "水", "木", "金", "土"]
let sub = days[2..<5]   // sub は ArraySlice型
print(sub)          // [火, 水, 木] を出力
print(sub.count)        // 3 を出力。要素は3つ。
print(sub.startIndex)   // 2 を出力。配列の最初の添字を表すプロパティ。
print(sub[2])       // "火" を出力
print(sub[4])       // "木" を出力

添字を指定して元ある配列から部分的に抽出した配列を作れる。Array型ではなくてArraySlice型となる。添え字は0から始まらず、部分的に切り取った添え字がそのまま残る。

若干わかりづらくて、バグの温床になりそうな気配。あまり使いたくない印象。

  • 配列のプロパティ

    • count
    • first
    • isEmpty
    • last
  • 配列のメソッド

    • append
    • appendContentsOf
    • insert
    • removeAll
    • removeAtIndex
    • removeLast
    • reverse

だいたいどれもメソッド名見れば振る舞いがわかる。

  • 多次元配列
var table: [[String]] = [["A","B"],["X","Y","Z"]]
print(table[0])            // ["A", "B"] を出力
print(table[1][0])         // X を出力
table[0].removeAtIndex(1)  // "B" が削除される
print(table)               // [["A"], ["X", "Y", "Z"]] を出力

[[型]] で定義する。

文字列と文字

  • String型のプロパティ

    • isEmpty
    • characters
    • unicodeScalars
    • utf8
    • utf16
  • 式展開

    • \(変数) で式展開できる
var str: String = "kentana20"
print("わたしは \(str) です")

文字列のところは、実戦でつかうときに辞書的に引きそうなので割愛。文字コードとかUnicodeの概念がわかってれば大丈夫。

辞書

Dictionary, Hash的なもの。これも配列同様に始めに定義した型のデータのみを扱える。Swiftは型による制約が厳しい。

  • シンプルな辞書
var d: [String: Int]
d = ["swift": 2014, "Objective-C": 1983]
d["swift"] // 2014
  • 辞書とfor-in
let dic = ["Mars": "火星", "Venus": "金星"]
for (en, ja) in dic {
    print("\(en) = \(ja)")
}

// こんな感じでも書ける
for t in dic {
    print("\(t.0) = \(t.1)")
}
  • 辞書のプロパティとメソッド
    • updateValue
    • removeValueForKey
    • count

集合

配列に似ているが、以下の点で異る。

  • 同一の要素を重複格納できない
  • 要素の順序に意味が無い

集合演算を行う必要がある場合は集合を使う。

  • 集合のメソッド
    • contains
    • insert
    • remove
    • removeFirst
    • isSubsetOf
    • union

だいたいわかる。

所感・雑感

3~5章まで読了。気になったことをいくつか書いてみる。

  • 構造体はかなり便利に使えそう。イニシャライザが複数定義できるので、用途に応じて初期化の仕方が変わることは多々あるので良さそうな気配
  • オプショナル/有値オプショナルもうまく使えば厳密なコードになりそうなので好印象
  • 式展開や自己代入など、よく使う機能もしっかり整備されてるのでやっぱりSwift扱いやすくて、且つ型定義に厳しいので保守性高いコードが継続的に書けそうで、多少人数増えても統一性のあるコードにできそう
  • 以下細かな疑問
    • .class に相当するメソッドはないのか(まだそんなに調べてない)
    • 部分配列は便利に利用できそうな半面、見通しが悪くなりそうな懸念がある
    • パラメータ付き型指定ってどれくらい書かれているのか、もう少し読み進めたらCocoa Podsのライブラリのコードを読んでみよう

明日も進めるぞ。

詳解Swift#1(chapter1~2)

7月下旬までの3ヶ月でアプリ1本作るために、Swift再入門する。 GWで詳解Swift進捗させたいので、Chapterごとのまとめを書いておく。

詳解 Swift 改訂版

詳解 Swift 改訂版

chapter1 Swiftでプログラミング

1章は型とか、変数、定数、構文、プレイグラウンドなどの第一歩的な話なので部分的に掻い摘んで書く。

データ型

種類 型名 説明
整数型 Int 整数全般はコレ
整数型 UInt 符号なし整数
実数型 Float 浮動小数
実数型 Double 高精度浮動小数
論理型 Bool true/false
文字 Character Unicodeの1文字
文字 UnicodeScalar Unicode文字コード
文字列 String Unicode文字列

変数 / 定数 / 文字列 / 配列

  • 変数: var
    • var x: 型 = 初期値 で書く
  • 定数: let
    • let y: 型 = 代入値 で書く

型推論が使えるので、型は省略可能。

  • 文字列

  • print

    • print(xxxx) で標準出力に書ける
    • separator: "" , terminator: "" で区切りと最終文字を定義できる
  • 配列

    • var a : [Int] = [1,2,3,4,5]
    • var s : [String] = []

制御構文

  • while
var i = 1, j = 2
while i < 2 {
    ++i
}
  • repeat-while
var n = 10
repeat {
    print("\(n) ", terminator:"")
    --n
} while n > 1
  • ラベル付き
let days = 31
let firstDay = 2
var w = 0

for ; w < firstDay; w++ {
    print("    ", terminator:"")
}

var d = 1
loop: while true {
    for ; w < 7; w++ {
        let pad = d < 10 ? " " : ""
        print(pad + "  \(d)", terminator:"")
        if ++d > days {
            print("")
            break loop
        }
    }
    print("")
    w=0
}

コマンドラインからSwift

  • swiftc
$ swiftc xxxx.swift

でバイナリ作れる。

chapter2 関数

関数定義

var total = 0

func count(n: Int) -> Int {
      total += n
          return total
}

func reset() { total = 0 }

呼び出すときは↓。戻り値を使わない場合は受け取らなくてもいい。

count(10)
var ret = count(5)
reset()
  • 外部引数名
func buyProduct(product: Int, price: Int, quantity: Int) {
    print("Product: \(product), amount: \(price*quantity)")
}

buyProduct(19090, price:18000, quantity:1)

第一引数は外部引数名を指定しない。 (指定するとコンパイルエラーになる)

Objective-C, Swiftの流儀として、 関数名には関数の役割+第一引数の意味を説明したものにする ため、第一引数に外部引数名を指定しないらしい。

  • 内部引数名
func area(height h: Double, width w: Double) -> Double {
    return h * w
}

area(height: 10.2, width: 1.5)
//area(10.2, width: 1.5) <- error
//area(width: 1.5, height: 10.2) <- error

外部引数名が指定されている場合は、呼び出し時の引数名指定が必須となる。 外部引数名を指定しているからといって、引数の順序を入れ替えられるわけではない。順番通りにしないといけない。

func areaNew(h: Double, _ w: Double) -> Double {
    return h*w
}

areaNew(10.2, 1.6)

第二引数の先頭に_をつけると、外部引数名の呼び出しを省略できる。呼び出し側を考えるとこれが一番シンプル。

  • 内部引数の省略
func compare(a:Int, _ b:Int, _:Bool) -> Bool {
    return a > b
}

compare(10, 15, true)

func compareX(a:Int, _ b:Int, option _:Bool) -> Bool {
    return a > b
}

compareX(10, 11, option: true)

インタフェースを揃えるために引数定義はするけど使わない場合は _:Bool のように内部引数名を省略できる。

_ は何とでも組み合わせて使えるワイルドカード。繰り返しの値を使わない場合とかに以下のように書ける。

for _ in 1...10 {
    print("hoge")
}

関数のいくつかの設定

  • inout引数 (参照渡し)
func mySwap(inout a:Int, inout b:Int) {
    let t = a; a = b; b = t
}

var x = 1; var y = 3

mySwap(&x, b: &y)

print(x, terminator:"")
print(y, terminator:"")

引数の値を関数内で変更する場合に inout を付与した引数を使う。その場合は関数呼び出し時に & を付けた変数を指定する必要がある。

  • 引数の既定値
let fontSize: Float = 12.0

func setFont(name: String, size: Float = fontSize, bold: Bool = false) {
    
    print("\(name) \(size)" + (bold ? "[B]" : ""))
}

setFont("ReglanPunch")
setFont("Times", size: 15.0, bold: false)
setFont("Times", bold: false, size: 15.0)

func setFontX(name: String, size: Float, bold: Bool) {
    print("\(name) \(size)" + (bold ? "[B]" : ""))
}

setFontX("Meryo", size: 16.0, bold: false)
//setFontX("Meryo", bold: false, size: 14.0) <- error

いわゆるoptional引数。既定値を定義しておくと呼び出し時に省略可能。optionalじゃないときは順番変えたらダメだったくせに、optionalのときは順番変えてもフツーに動く。書籍には既定値ありの引数についての制約みたいのがだ~っと書いてある。 こういうのはシンプルじゃなくてあまり好きじゃない。極力optional引数はナシでコード書きたい。

  • 引数の値を変更できるようにする
func dayOfWeek(var m:Int, _ d:Int, var year y:Int = 2016) -> Int {
    if m < 3 {
        m += 12; --y
    }
    let leep = y + y / 4 - y - y / 100 + y / 400
    return (leep + (13 * m + 8) / 5 + d) % 7
}
dayOfWeek(5, 9)

引数に var を付けると、関数内で引数を変更可能になる。付けない場合と let を付けた場合は関数内での変更は不可。 ちょっと構文的に複雑になる。

  • 関数のネスト
func printMonth(firstDay:Int, _ days:Int) {
    func daystr(d:Int) -> String {
        if d <= 0 {
            return "    "
        } else {
            return (d < 10 ? "   \(d)" : "  \(d)")
        }
    }
    
    var d = 1 - firstDay
    while d <= days {
        var line = ""
        for _ in 0 ..< 7 {
            line += daystr(d)
            if ++d > days { break }
        }
        print(line)
    }
}

printMonth(4, 31)
//daystr(1) <- error

クロージャ。この例で行くと daystr 関数は外部からは呼べない。とある関数内でしか使わない関数はこの記法で良い。

関数のオーバーロード

ここはコード無しで。

所感・雑感

半日ゆっくり読み進めた。序章感があったので、大きなトピックはなし。関数のOptionalの部分は少し違和感はあるものの、外部引数名のくだりは自分は結構好みかも。 しっかり読むと理解が進む。明日はもうちょい進捗させよう。

Retty Tech Cafe #5 (テーマ: SRE) に行ってきた


3.12(土) に五反田のRettyオフィスで開催されたRetty Tech Cafe #5 に行ってきました。

http://connpass.com/event/26679/

タイムテーブルはこんな感じ。今回のテーマは "SRE" ということで、自分が一休.comで担当している業務に近しいものがあったので興味を持って参加されていただきました。

# Speaker Title
1 Retty CTO 樽石さん(@taru0216) Retty SRE のご紹介〜 元 SRE エンジニアによる SRE の概要と Retty での適用事例について 〜
2 All About 鈴木さん (@jp_taku2) オールアバウトをオンプレミスで支える技術
3 mercari 長野さん (@kazeburo) メルカリにおける、継続的なアプリケーションの改善を支える技術
4 Retty teemuさん Ensuring highly available services. Infrastructure and service monitoring

あんまりメモれてなかったのでスライドが公開されたらアップデートしたいです。

Retty SRE のご紹介〜 元 SRE エンジニアによる SRE の概要と Retty での適用事例について 〜

CTO 樽石さんによるSREの紹介。樽石さんはRedhat -> Google -> 楽天 -> Rettyという経歴でGoogle時代にSREを担当されていたそうです。

Retty

食を通じて世界中の人々をHappyに。 というコンセプトの元、実名のクチコミを特徴としたグルメ系キュレーションサービスを展開していて、食という領域はまだまだ伸びると思っている。とのこと。

https://retty.me/

懇親会で中の人とお話する機会があったのですが、全社では60名くらいでエンジニアは20名くらいだとおっしゃっていました。一休.comとくらべて比率的にはエンジニアが多い印象ですね。

SRE

直訳するとサイト信頼性エンジニアリング。

ユーザがサービスを快適に利用できるようにシステムを設計・構築・運用するエンジニアリング活動 

を指す。

指標は主に3つ

  1. QPS(Traffic)
  2. 稼働率(Availability)
  3. 遅延(Latency)

  4. インフラエンジニアとの比較

    • 似ているスキルセットやマインドを持っていることが多い
    • ITサービスの基盤に関わるエンジニアがインフラエンジニア
    • 基盤の信頼性が低いとサービスに関わるのでSRE的考えが自然発生していく
  5. アプリSREの例(単純例)

    • アプリからサーバAPIにリクエストを送ったらエラーが返ってきたのでリトライした

SREというワードはあまり耳慣れませんが、アプリケーションエンジニアにしても、インフラエンジニアにしても、サービスに長く関わっているとこういった改善活動は必要になってきて、普段意識せずにやっていることがSREの一環だった、ということが多いのではないかと思いました。

  • 3つの指標を達成するために重要な2つの要素
    • 障害発生時の応急処置・調査
      • サービス規模が拡大すると障害発生から対応までのスピードが重要性を増す
    • 障害に強い開発
      • 指標を定量化するための開発なども行うようになる

SRE in Retty

ふりかえってみると、4つくらいのフェーズがあったように思う。

  1. シンプルな構成だった時代
  2. キャッシュとスケールアウトを進めた時代
  3. 監視力を強化していった時代
  4. ビッグデータ解析ができるようになった時代 <- イマココ

フェーズのはじめの方はインフラ的な要素が多く

などを推進してSRE活動を行ってきた。

後半のフェーズではインフラだけでなく、アプリのクラッシュ率改善のようなアプリ側でのSRE活動が成果を生んだりした。

future of SRE in Retty

これからRettyは海外展開をしようとしている。で、まずは海外展開可能なインフラの整備から進めていて、Cloud on Cloudでサービスを提供できるようにしたい。

  • Cloud on Cloud
    • バックエンドがAWS/GCP/AzureなどのどのIaaSでもConfigファイル一発で切り替えて使えるようなサービスラッパー

これをCTOの樽石さんが1人でやっているらしく、「誰か一緒にやりませんか」的な採用募集でのクロージングでした。

オールアバウトをオンプレミスで支える技術

オールアバウトインフラエンジニア @jp_taku2 さんによるセッション。障害からの改善、自動化といった事例を紹介されていました。

オールアバウトとインフラ構成

メディアサービス。

  • 月間 1億5020万PV
  • 記事 170,000本

くらいの規模感で運用している。

構成

LBの下にVarnishがプロキシ兼キャッシュサーバとして存在していて、その下にPHPアプリケーションがApacheで動いているという構成でした。DBはMySQL

Varnish

  • cacheサーバ兼プロキシサーバとして活用している

障害事例と改善の話

  • SPOFとなっていたルータが落ちた
    • SPOFをなくして冗長構成にして改善
  • NFSが落ちた
    • NFSをS3へ移行して改善
  • 高負荷障害
    • Varnish、CDNをフル活用して改善

監視改善の話

  • zabbixによるログ監視(初期)はアラートが飛びすぎてメールが埋もれていた
    • Criticalなエラーのみをメールで通知
    • Elasticsearch+Kibanaを使ってアラートを可視化
    • Fluentd -> PHPスクリプト -> メールでサマリを通知

といった形で徐々に監視を改善していった。

自動化事例

Chef導入前

  • 構築用のShellScriptがメンテされていない
  • 属人化してブラックボックス化したサーバ(つらい)

Chef導入後

  • サーバ構築手順をChefのレシピでコード化
  • BitBucketでコードを管理してプルリクベースでレビュー
  • レビューを通った変更のみが本番/Stagingへデプロイ

属人性が排除され、サーバに直接ログインして設定変更等する必要がなくなった。

Jenkins

特に説明はなし。JenkinsジョブでChefのレシピ適用をしているとのこと。


メルカリにおける、継続的なアプリケーションの改善を支える技術

ご存知 @kazeburo さんによるメルカリのSREについてのお話。個人的にはこのセッションがとても勉強になりました。

http://tech.mercari.com/entry/2015/11/18/153421

主にはこの話をベースにした発表でした。

Mercai & Infrastructure

  • 3200万DLのアプリ
  • GMV(総取扱額)月間100億以上
  • 1日数十万出品以上

1,200,000 reqs/min (API/HTTPS)

をさばいている(スゴイ!)。

Infrastructure

mercariさんはJpはAWSではなく、さくらインターネットの物理サーバを使っているそうです。

Jp, Usで同じ構成をとっている。

Software

  • nginx
  • Apache
  • Solr
  • ...
  • widebullet
  • Gaurun

などなど、多くのSoftwareを使っていて、OSSを積極的に活用している。mercari内で作られたOSSも多数あり、 github.com/mercari で公開している。

10+ Deploys/day

Cookpadでも同じような話がありましたが、mercariでも1日に1x回デプロイしているそうです。

PHPのデプロイ

動的言語なので、変更ファイルを配置したらデプロイできるのだが、事はそんなに単純でもない。

  • 依存関係を解決しないといけない

    • 参照するモジュールを増やした場合、参照される新規モジュールが既にデプロイされている必要があったり...etc
  • 解決方法案

    • Blue-Green Deployment
    • Symlink Swapping Deployment
      • ref. Capistrano
      • キャッシュのコントロールが結構シビア
    • Request Pausing Deployment
      • デプロイ中のサーバにリクエストが来ないようにする方式

mercariでは Request Pausing Deployment の方式を採用している。

  • ngx_dynamic_upstream

    • @cubicdaiya さん作のNginxモジュール
    • up/downコマンド1発で対象サーバへのリクエストをOn/Offできる
      • AWSのELBのように反映に時間がかかることなく、一瞬での切り替えが可能なスグレモノ
  • ansibleのデプロイジョブ

    • Inside ChatOps
      • SlackでBotとやり取りすると、対象のプルリクをチェックして問題なければBotがmergeしてansbleのデプロイジョブをスタートする

Monitoring

  • Log monitoring

    • Fluentd で一旦ログ中継サーバへ
    • 中継ログサーバからBigQuery, Norikraへ転送し、活用している
    • NorikraからX分間隔でSlack, Mackerel, DBへ
  • Agent based monitoring

    • Mackerel, NewRelic, Kurado(自作)のデータをpagerduty, slackへ通知
  • Mackerel
    • 25を超えるpluginやutility commandを書いてる
  • Kurado
    • グラフを画像で表示できるOSS(kazeburoさん作)

まとめ

  • SRE@mercari
    • 自分達で問題発見・解決して、サービスの信頼性を向上
    • スケールさせるミドルウェアの開発と運用

Ensuring highly available services. Infrastructure and service monitoring

ほぼメモれていないので割愛。スミマセン。。

所感

SREについて

SREについて少しだけ理解できました。

自分たちが日頃行っているサービス改善や監視強化、ログの見える化といった作業もSREの活動であることもわかりましたし、SREによって

  • アプリの評価(レビュー)が改善できた
  • 特定のAPIが遅いことがわかって改善できた

という成果を生み出すことができるということも再認識できました。

オールアバウト

オールアバウトさんはオンプレでサービスを運用していて、現在クラウドに移行する計画を進めているとのことでした。一休の状況も少し似ている部分があるので、別途情報交換の機会を作ってお互いのサービスにフィードバックできるようにアクションしたいです。

mercari

@kazeburo さんのセッションはさすがでした。

  • mackerel, NewRelic, Kurado(自作のグラフダッシュボード)などでサービス・システム的なメトリクスを見える化して改善を促進する
  • 10+ deploy/day なデリバリーについての足回りを整える

といった自分が担当している部分に直結する話が多く、めちゃ参考になりました。良い部分は少しでも取り込んで改善につなげたいですね。

Retty

良い勉強会を企画してくださり、ありがとうございました。

Rettyさんのオフィスはステキでした。懇親会でもピザとビールに加えてオシャンな料理が振る舞われたり、ウェルカムスイーツが出てきたり、普段の勉強会と比べて女子力高めでビックリしました。

懇親会でRettyのエンジニアの方々と少しお話をさせていただいたのですが、みなさん楽しそうにサービスの話をしていて、とても好感が持てました。

一休でもレストラン予約の事業をやっているので、「レストランのエンジニアと合同で情報交換とかできたら良いですね〜」なんて話をしていたので、うまいこといけば実現するかもしれません。

おまけ

  • ウェルカムスイーツ

f:id:kentana20:20160312202742j:plain

  • オープンなスペースでの勉強会でした

f:id:kentana20:20160312202821j:plain

  • 懇親会時にふるまわれた料理(美味しかったです)

f:id:kentana20:20160312202905j:plain