Swift4.2で新しく追加されたものをざっくりまとめました。これからSwift4.2対応するプロジェクトなどがある時に参考程度になれば良いかなと思います。
実施環境
Apple Swift version 4.2 (swiftlang-1000.0.16.7 clang-1000.10.25.3)
Conditional conformances
みなさん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
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
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
コンパイル時にカスタムなワーニングとエラーを定義できるようになりました。SwiftLintを入れている人はTODO:
コメントなどでレビュワーに作業中の意思表明をすることありますよね。エラーはプラットフォームが変更された時に使えないものがある時に使うんですかね、他にイメージつかないです。
func doSomething() {
#warning("TODO: Implement logic.")
}
if canImport(UIKit)
// Do something.
#else
#error("Requires UIKit.")
#endif
Adding remove where method to Collection
コレクションからある条件下のアイテムを全て削除する為のメソッドが追加されました。今までは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
今までBool値のToggle処理を行う際はflag = !flag
のように書いていましたが、toggle()
メソッドが追加されたことで地味に可読性が高くなりました。(既にExtension定義している人が多いと思いますが)
var flag = false
// Swift4.1
// flag = !flag
// Swift4.2
flag.toggle() // true
Random numbers and collections
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
系だけでなくDouble
やFloat
も可能でBool
にまでも用意されています。またArray
やDictionary
、この前仲間入りした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
今まで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
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
全てのアイテムが条件を満たしているか調べる際に、今まではcontains(where:)
で無理に行なっていたかと思いますが、かなり可読性が悪いのでSequence
にallSatisfy(_:)
メソッドが追加されました。
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
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
眠いからおしまい。