Open12

Swiftの機能

KyomeKyome

Dynamic Member Lookup

  • Swift 4.2で導入
  • コンパイル時に存在しないプロパティに対して.シンタックスを使用してアクセスできる機能
  • @dynamicMemberLookupをつけてsubscript(dynamicMember key: String) -> Anyを実装すると使える
  • 別言語の関数呼び出しやJSONのようにキーがあるかないか静的解決できない場面で使える
@dynamicMemberLookup class SomeClass {
    subscript(dynamicMember key: String) -> Any {
        if key == "piyo" {
            return 123
        } else if key == "meu" {
            return "hello"
        }
}

piyomeuはプロパティとしては存在していないけれど、アクセス可能

let someClass = SomeClass()
print(someClass.piyo)
print(someClass.meu)
KyomeKyome

KeyPath

  • Swift 4で導入
  • インスタンス[keyPath: ]で動的にプロパティにアクセスできる
    • アクセスするプロパティを動的に切り替えられる
  • \.の記法でプロパティを指定する
  • WritableKeyPathは値型なのでvarで定義されたstructのプロパティを上書きできる
  • ReferenceWritableKeyPathは参照型なのでletで定義されたclassのプロパティを上書きできる
struct Dog {
    var breed: String
    var age: Int

    func bark() {
        print("Bow Wow")
    }
}

var dog = Dog(breed: "Beagle", age: 10)

let anyKeyPath: AnyKeyPath = \Dog.breed
print(dog[keyPath: anyKeyPath]) // Optional("Beagle")

let partialKeyPath: PartialKeyPath<Dog> = \.breed
print(dog[keyPath: partialKeyPath]) // Beagle

let _keyPath: KeyPath<Dog, String> = \.breed
print(dog[keyPath: _keyPath]) // Beagle

let writableKeyPath: WritableKeyPath<Dog, String> = \.breed
dog[keyPath: writableKeyPath] = "Retriever"
print(dog.breed) // Retriever
class Dog {
    var breed: String
    var age: Int

    init(breed: String, age: Int) {
        self.breed = breed
        self.age = age
    }

    func bark() {
        print("Bow Wow")
    }
}

let dog = Dog(breed: "Beagle", age: 10)

let referenceWritableKeyPath: ReferenceWritableKeyPath<Dog, String> = \.breed
dog[keyPath: referenceWritableKeyPath] = "Retriever"
print(dog.breed) // Retriever

ネストしたプロパティへのアクセス

struct Physical {
    var color: String
    var legs: String
    var tail: Bool
}

struct Dog {
    var breed: String
    var age: Int
    var physical: Physical

    func bark() {
        print("Bow Wow")
    }
}

let physical = Physical(color: "multi", legs: "middle", tail: true)
let dog = Dog(breed: "Beagle", age: 10, physical: physical)

print(dog[keyPath: \.physical.tail]) // true

let physicalKeyPath = \Dog.physical
print(dog[keyPath: physicalKeyPath.appending(path: \.tail)]) // true

ファンクションのように扱う

struct Dog {
    var breed: String
    var age: Int

    func bark() {
        print("Bow Wow")
    }
}

let dogs: [Dog] = [
    Dog(breed: "Beagle", age: 10),
    Dog(breed: "Retriever", age: 6),
    Dog(breed: "Hound", age: 13)
]

let breeds = dogs.map(\.breed)
print(breeds) // ["Beagle", "Retriever", "Hound"]

つまり、dogs.map { $0.breed }のように書かなくていい。

KyomeKyome

String Interpolation

文字列中に定数、変数、リテラル、式などを含めて新しい文字列を構築する。
CustomStringConvertibleに準拠してdescriptionを実装していると、それが展開される。

let name = "John"
print("Hello \(name)")

Multiline String Literals

複数行に渡った文字列を定義できる。行末に\をつけなければ改行が反映され、\をつけると改行なしとなる。

let summary = """
    Hello World!
    Swift is a programming language. \
    Multiline String Literals
    """

Extended String Delimiters

エスケープしないといけない特殊文字がそのまま文字として利用できる。

let text = #"backslash is \"#

willSet didSet

defer

return の省略

Optional

guard let, case let, if let, catch let

switchのcaseで毎回break書かなくていいやつ

_で引数ラベル省略できるやつ

$0で引数名省略できるやつ

,で&&や||の意味になるやつ

KyomeKyome

defer

  • スコープを抜ける前にさせたい処理の予約ができる
    func some() {
        defer {
            print("1")
        }
        // 中略
        print("2")
    }
    some()
    // 2
    // 1
    
  • deferを複数定義した場合は、定義された逆順で処理される
    func some() {
        defer {
            print("1")
        }
        defer {
            print("2")
        }
        defer {
            print("3")
        }
        return
    }
    some()
    // 3
    // 2
    // 1
    
KyomeKyome

Constants and Variables

letvarの話

Declaring Constants and Variables

実は,で区切りで1行の中に複数の定数や変数を定義できる

var x = 0, y = 1, z = 2

Type Annotations

実は1行の中で同じ型の変数を,区切りで定義できる
この場合、最後の変数の後に型注釈をつける

var red, green, blue: Double
KyomeKyome

Nested comments

実はコメントはネストできる

/* これは最初の複数行コメントの開始です
/* これはネストした2番目の複数行コメントの開始です
これはネストした2番目の複数行コメントの終わりです */
これは最初の複数行コメントの終わりです */
KyomeKyome

Type Safety and Type Inference

  • コンパイル時に型チェックが行われるのでエラーに素早く気づける
  • コンパイラ時に式の型を自動で推論してくれるため、比較的型の宣言を明示的にする必要がない
  • 型推論の仕組みを知ることで計算コストを抑えることが可能
    • 部分的に推測した型の比較が多いとコストがかかるため、例えば以下のような場合は型注釈をしない方が良い
      let hoge: Double = Double(100) // 左辺と右辺の比較が必要
      let hoge: Double = 100 // 左辺と右辺の比較が必要
      let hoge = Double(100) // 左辺と右辺の比較が不要
      
KyomeKyome

Numeric Literals

  • プレフィックスなしだと10進数
  • 0bプレフィックスありだと2進数(例 0b11010
  • 0oプレフィックスありだと8進数(例 0o32
  • 0xプレフィックスありだと16進数(例 0x1A
  • 10進数でe2サフィックスをつけると×10²(例 1.23e2
  • 10進数でe-2サフィックスをつけると×10⁻²(例 1.23e-2
  • 16進数でp2サフィックスをつけると×2²(例 0x1Ap2
  • 16進数でp-2サフィックスをつけると×2⁻²(例 0x1Ap-2
  • 読みやすくするために0_を含めることが可能(例 000123.4561_000_000
KyomeKyome

Type Aliases

typealias FloatingPoint = UnsafeMutablePointer<Float>

Tuples

  • 複数の値を 1 つのまとまりにグループ化できる
  • タプル内の値にはどんな型も入れることができ、全ての型を同じにする必要はない
  • タプルを展開する時、アンダースコアで無視することが可能
  • 各値へのアクセス方法として、デフォルトでは0から始まるインデックスを使用する
  • 各値に名前付けができる
let http404Error = (404, "Not Found")
let (statusCode, _) = http404Error
print("The status code is \(http404Error.0)")

let http200Status = (statusCode: 200, description: "OK")
print("The status message is \(http200Status.description)")

Optionals

  • Optionalenumで定義されている
let a: Optional<Int> = .some(2)
let b: Int? = .none
print(a == 2) // true
print(b == nil) // true

Optional Binding

  • ifguardwhile文の 1 つのアクションで、オプショナル値に値が存在することを証明し、定数や変数にその内部の値を設定することを、まとめて行うことができる
if let constantName = someOptional {
    // 処理
}
guard let constantName = someOptional else {
    return
}
while let constantName = someOptional {
    // 処理
}

Providing a Fallback Value

  • nil結合演算子(??)を用いてnilだった場合にデフォルト値を提供できる
let name: String? = nil
let greeting = "Hello, " + (name ?? "friend") + "!"
print(greeting)

Force Unwrapping

  • !を用いた強制アンラップは実質的にfatalError(_:file:line:)の短縮系である

Implicitly Unwrapped Optionals

  • オプショナルに一度値が設定された後は必ず値が存在するということが明らかな場合、定義時に!を型の後ろに書くことで、暗黙的アンラップオプショナルを書ける
KyomeKyome

Assertions and Preconditions

  • アサーションを用いれば、続くコードが実行される前に必要な条件が満たされているかどうかを確かめられる
  • アサーションはプロダクションビルドでは実行されない
  • プリコンディションはプロダクションビルドでも実行される
let text = "Hello World"
assert(!text.isEmpty, "text must not be empty.")

if text.isEmpty {
    assertionFailure("text must not be empty.")
} else {

}

precondition(!text.isEmpty, "text must not be empty.")