WWDC
Swift
swift4

What’s New in Swift4.2 まとめ

Swift4.2で新しく追加されたものをざっくりまとめました。これからSwift4.2対応するプロジェクトなどがある時に参考程度になれば良いかなと思います。

実施環境

Apple Swift version 4.2 (swiftlang-1000.0.16.7 clang-1000.10.25.3)

Conditional conformances

SE-0143

みなさんSwift4.1で大喜びしたstruct, enum, classへの条件付きプロトコル適合(ある条件下の時のみプロトコルに準拠させる方法)ですが、下記のような動的なキャストやチェックを行うことはできませんでした。Swift4.2ではその実装が完了し、条件付きプロトコル適合が完成したみたいです。

// Swift4.1
protocol Bachelor {
    func givesRose()
}

extension Array: Bachelor where Element: Bachelor {
    func givesRose() { forEach { $0.givesRose() } }
}

class Man: Bachelor {
    func givesRose() { print("🌹") }
}

let men = [Man(), Man()]
men.givesRose() // 🌹🌹

// Swift4.2
func dynamicallyBachelor(_ men: Any) {
    if let men = men as? Bachelor {
        men.givesRose()
    } else {
        fatalError("Swift4.1")
    }
}

dynamicallyBachelor(men) // 🌹🌹

Collection of enum cases

SE-0194

Enumの全てのケースが配列で受け取れるCaseIterableというプロトコルが追加されました。今まで@sgr-ksmtさんのEnumのcaseを配列で返すProtocol Extensionにお世話になっていました。感謝です。

enum Fruit: CaseIterable {
    case apple
    case orange
    case banana
}

Fruit.allCases // [apple, orange, banana]

このプロトコルもまたAssociated Valueを持ったEnumは準拠できません。Associated Valueがある場合はcaseがいくらでも増やせるのと一緒ですからね、当たり前ですよね。

// error: type 'Fruit' does not conform to protocol 'CaseIterable'
enum Fruit: CaseIterable {
    case apple(Int)
}

Dynamic member lookup

SE-0195

JavaScript(辞書も含まれちゃうけど)やPythonを書いている人には馴染みのある、動的なメンバへドット表記でアクセサ呼び出しを行う糖衣構文。これがついにSwiftでもできるようになりました。最初はちょっと恐ろしいと思ったのですが、型安全に宣言できるのでやたらむやみに多用しなければ活かせそうな気がします。

@dynamicMemberLookup
struct Response {
    private var raw = ["foo": "foo", "baz": "baz"]
    subscript(dynamicMember name: String) -> String? {
        get { return raw[name] }
        set { raw[name] = newValue }
    }
}

var response = Response()
response.foo // foo
response.bar // nil
response.bar = "bar"
response.bar // bar

APIのレスポンスをデバッグする時や、他の言語のライブラリから値を取得する場合などに使うのがいいのですかね。仕様変更には強くはなりますが、やはり乱用したくないです。

Compiler diagnostic directives

SE-0196

コンパイル時にカスタムなワーニングとエラーを定義できるようになりました。SwiftLintを入れている人はTODO:コメントなどでレビュワーに作業中の意思表明をすることありますよね。エラーはプラットフォームが変更された時に使えないものがある時に使うんですかね、他にイメージつかないです。

func doSomething() {
    #warning("TODO: Implement logic.")
}

if canImport(UIKit)
    // Do something.
#else
    #error("Requires UIKit.")
#endif

Adding remove where method to Collection

SE-0197

コレクションからある条件下のアイテムを全て削除する為のメソッドが追加されました。今まではfilter(where:)を使っていましたが、可読性も悪く、メモリが無駄に割り当てられて効率も悪いらしいです。

var faces = ["😀", "😭", "😄"]
faces.removeAll(where: { $0 == "😭" }) // ["😀", "😄"]

Methods of withUnsafePointer and withUnsafeBytes for immutable arguments

withUnsafePointer(to:_:)withUnsafeBytes(of:_:)の引数がinoutで宣言されてMutableだった為、コピーした値を受け渡したりすることで非効率だということでImmutableに扱えるメソッドが追加されました。

let x: UInt16 = 0xabcd
let (firstByte, secondByte) = withUnsafeBytes(of: x, { pointer in
    return (pointer[0], pointer[1])
})

String(firstByte, radix: 16) // cd
String(secondByte, radix: 16) // ab

Toggle for Bool

SE-0199

今までBool値のToggle処理を行う際はflag = !flagのように書いていましたが、toggle()メソッドが追加されたことで地味に可読性が高くなりました。(既にExtension定義している人が多いと思いますが)

var flag = false
// Swift4.1
// flag = !flag
// Swift4.2
flag.toggle() // true

Random numbers and collections

SE-0202

Swiftでランダムな値を生成する場合Foundationのarc4random系を使っていましたが、CのAPIでありクロスプラットフォームで使用できるものがなかったので改めてランダムな値を生成するメソッドと、独自のロジックでランダムな値を生成するGeneratorを定義できるプロトコルが追加されました。

let randomInt = Int.random(in: 1...10) // 1-10 random number
let randomInt32 = Int32.random(in: .min ... .max) // Int.min-.max random number
let randomDouble = Double.random(in: 0.0..<1.0) // 0.0-1.0 random number
let randomBool = Bool.random() // true or false

引数にRangeを渡すだけです。Int系だけでなくDoubleFloatも可能でBoolにまでも用意されています。またArrayDictionary、この前仲間入りしたStringなどのCollectionプロトコルに準拠しているものはrandomElement()でランダムに値を取り出せます。

let randomChar = "hello".randomElement() // Random character
let randomFruit = ["apple", "orange", "banana"].randomElement() // Random string

合わせてshuffled()shuffle()(mutatingな方)も追加されました。何でなかったのですかね。ほとんどの人はExtension定義して使っていたんじゃないですかね。

let shuffledChar = "hello".shuffled() // Shuffled character
var fruits = ["apple", "orange", "banana"]
fruits.shuffle() // Shuffled array

上記のランダム関数はRandom.defaultというデフォルトのGeneratorを介してランダマイズされたもので、これを独自のものに置き換えられるようになっています。その為にはRandomNumberGeneratorプロトコルに準拠しなくてはなりません。(基本的にDefaultを使うと思いますが)

/// Mimicking the old random function.
struct OldRandomNumberGenerator: RandomNumberGenerator {
    mutating func next() -> UInt64 {
        return UInt64(arc4random_uniform(.max))
    }
}

var oldGenerator = OldRandomNumberGenerator()
let oldRandomInt = Int.random(in: 0...10, using: &generator) // 1-10 random number

Last item method for Sequence

SE-0204

今までfirst(where:)などの先頭のアイテムに対して行えていたことが、最後尾のアイテムに対して行えるメソッドが追加されました。

let range = 0...9
let isEven: (Int) -> Bool = { $0 % 2 == 0 }
let lastEven = range.last(where: isEven) // 8
let lastEvenIndex = range.lastIndex(where: isEven) // inRange(8)

それに合わせて先頭のインデックス操作系の下記のメソッドがリネームされました。

  • index(of:)firstIndex(of:)
  • index(where:)firstIndex(where:)

Hashable enhancements

SE-0206

Swift4.1のHashableは下記の問題があったのでhashValueの廃止とHasherというハッシュ関数を持つ構造体が公開されました。よってカスタムなhashValueを定義するHashableの準拠の仕方が変わります。

  • ^&*によるビット演算が分かりにくい
  • 私のような人間が理解しないで行うとハッシュの衝突が起こる
  • 私のような人間が理解しないで行うとパフォーマンスを損なう
// Swift4.1
struct GridPoint {
    var x: Int
    var y: Int
}

extension GridPoint: Hashable {
    var hashValue: Int {
        return x.hashValue ^ y.hashValue &* 16777619
    }

    static func == (lhs: GridPoint, rhs: GridPoint) -> Bool {
        return lhs.x == rhs.x && lhs.y == rhs.y
    }
}
// swift4.2
struct GridPoint {
    var x: Int
    var y: Int
}

extension GridPoint: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(x)
        hasher.combine(y)
    }

    static func == (lhs: GridPoint, rhs: GridPoint) -> Bool {
        return lhs.x == rhs.x && lhs.y == rhs.y
    }
}

All satisfy algorithm for Sequence

SE-0207

全てのアイテムが条件を満たしているか調べる際に、今まではcontains(where:)で無理に行なっていたかと思いますが、かなり可読性が悪いのでSequenceallSatisfy(_:)メソッドが追加されました。

let range = 0...9
// Swift4.1
let isSmallerThanTen = !range.contains(where: { $0 >= 10 }) // true
// Swift4.2
let isSmallerThanTen = range.allSatisfy({ $0 < 10 }) // true

Adding offset method to MemoryLayout

SE-0210

MemoryLayout型はメモリのサイズやストライドなどを取得できるプロパティやメソッドを持ったEnum(ただの名前空間)みたいです。Swift4.2ではそこにStored Propertyのオフセットアドレスを、Swift4.0から追加されたPartialKeyPathで取得できるメソッドが追加されました。使ったこともないのであまりよくわからないですが。

struct GridPoint {
    var x: Int // Offset: 0
    var y: Int // Offset: 8
}

let offset = MemoryLayout<GridPoint>.offset(of: \GridPoint.y) // 8

眠いからおしまい。