- ジェネリクス
- Concurrency
- actorやTaskの境界をまたがるnon-Sendableな型を使用した際にワーニングを出力する
- @preconcurrencyを使ってSendableチェックを段階的に導入できるように(既存ソースの破壊を抑える)
- actor-isolationチェックがdeferでも正しく行われるように(isolationの状態をdefer内部でも共有できる)
- global actorが必要なインスタンスメンバにデフォルト式が設定されていた場合にSwift5系ではワーニングが出力される
- Distributed actor
- トップレベルコードのConcurrency
- noasync属性
- Clock
- non-isolatedなasync関数が実行されるExecutorの明確化
- Regex
- 一般的な構文
- ポインタ周り
- その他
Swift5.6
存在型とプロトコル制約の区別を明確にできる。
protocol P {}
func generic<T>(value: T) where T: P {
...
}
func existential(value: any P) {
...
}
参考: https://github.com/stzn/SwiftPodcast/blob/main/episodes/Swift%20存在型(Existential%20type)とanyキーワード.md
Swift5.7
(SE-0309)。
associated typeとSelf
要件を持つプロトコルは、any
キーワードを使うと値の型として利用可能に。
associated typeの型を返すプロトコルメソッドは、any
型で呼び出すことができる。
結果は、associated type型の上限に型消去され、これはassociated typeと同じ制約を持つ別のany
型になる。 例えば:
protocol Surface {...}
protocol Solid {
associatedtype SurfaceType: Surface
func boundary() -> SurfaceType
}
let solid: any Solid = ...
// 'boundary'の型は'any Surface'
let boundary = solid.boundary()
associated typeまたはSelf
を受け取るプロトコルメソッドでは、any
は使用できない。
ただし、SE-0352と組み合わせると、プロトコルに制約されたジェネリックパラメータを取る関数にany
型を渡すことができる。
ジェネリックのコンテキスト内では、型同士の関係は明示的であり、すべてのプロトコルメソッドで使用できる。
Opaque Type(some
を使う型)を戻り値の型の構造的位置でも利用可能に(同じ戻り値の型に複数のOpaque Typeを含めることも可)。
func getSomeDictionary() -> [some Hashable: some Codable] {
return [ 1: "One", 2: "Two" ]
}
関数やsubscriptの引数にOpaque Type(some
を使う型)が利用可能に。これでジェネリックパラメータを簡単に書けるようになる。
Before:
func horizontal<V1: View, V2: View>(_ v1: V1, _ v2: V2) -> some View {
HStack {
v1
v2
}
}
After:
func horizontal(_ v1: some View, _ v2: some View) -> some View {
HStack {
v1
v2
}
}
※ 引数のsome
の型は呼び出し側で、戻り値のsome
の型は実装側で決める
func compute<C: Collection>(_ values: C = [0, 1, 2]) {
...
}
呼び出し側で引数を指定しなかった場合、compute
はコンパイルに成功してC
は[Int]
に推論される。
参考: https://github.com/stzn/SwiftPodcast/blob/main/episodes/Swift%20引数のデフォルト式からジェネリックパラメータの型を推論する.md
存在型の値を使用してジェネリック関数を呼び出すことができるように(以前はできなかった)。
protocol P {
associatedtype A
func getA() -> A
}
func takeP<T: P>(_ value: T) { }
func test(p: any P) {
takeP(p) // 以前はany PはPに準拠していないというエラーが発生して、type eraserが必要だった
}
参考: https://github.com/stzn/SwiftPodcast/blob/main/episodes/Swift%20any版primary%20associated%20types.md
関連型とSelf
要件を持つプロトコルを、any
キーワードを持つ値の型として使用できるように。
関連型を返すプロトコルメソッドは、任意の型で呼び出すことができる。戻り値は、関連型の上限まで型消去される。これは、関連型と同じ制約を持つ別のany
型。例えば:
protocol Surface {...}
protocol Solid {
associatedtype SurfaceType: Surface
func boundary() -> SurfaceType
}
let solid: any Solid = ...
// 'boundary'の型は'any Surface'
let boundary = solid.boundary()
関連型またはSelf
を引数に取るプロトコルメソッドでany
は使用できない、SE-0352と組み合わせて、プロトコルに制約されたジェネリックパラメータをとる関数にany
型を渡すことができる。ジェネリックのコンテキスト内では、型の関係は明示的であり、すべてのプロトコルメソッドで使用できる。
参考: https://github.com/stzn/SwiftPodcast/blob/main/episodes/Swift%20PAT問題を解消して全てのプロトコルを存在型として使えるように.md
プロトコルは一つ以上のprimary associated typeのリストを宣言できる
protocol Graph<Vertex, Edge> {
associatedtype Vertex
associatedtype Edge
}
Graph <Int>
のようなプロトコルに制約のあるタイプは、プロトコル準拠要件の右側が期待されるあらゆる場所に書くことができる:
func shortestPath<V, E>(_: some Graph<V>, from: V, to: V) -> [E]
extension Graph<Int> {...}
func build() -> some Graph<String> {}
プロトコルに制約された型は、主要なassociated typeを制約する同じ型要件が伴うプロトコル自身への準拠要件と同等です。 上記の最初の2つの例は、次の例と同等:
func shortestPath<V, E, G>(_: G, from: V, to: V) -> [E] where G: Graph, G.Vertex == V, G.Edge == V
extension Graph where Vertex == Int {...}
some Graph<String>
を返すbuild()
関数は、where
句を使って書くことはできない。これは以前はできなかったOpaque Result Typeへの制約の例。
上記をより一般化して、primary associated typesを使ったプロトコルを存在型にも利用できる。
let strings: any Collection<String> = [ "Hello" ]
動的キャスト(is
、as?
、as!
)などのランタイムサポートを必要とする言語機能、および(Array<any Collection<Int>>
など)ジェネリック型のパラメータ化された存在型を使用するには、追加でavailabiltyチェックが含まれることに注意。以前のバージョンでジェネリック位置での使用するには、ジェネリック型消去ラッパ構造体で回避できる。これにより、実装がはるかに簡単。
struct AnyCollection<T> {
var wrapped: any Collection<T>
}
let arrayOfCollections: [AnyCollection<T>] = [ /**/ ]
参考: https://github.com/stzn/SwiftPodcast/blob/main/episodes/Swift%20any版primary%20associated%20types.md
標準ライブラリのさまざまなプロトコルで、primary associated typesを宣言するようになった。
例: Sequence
とCollection
は、単一のprimary associated typeElement
を宣言する。
たとえば、これにより、some Collection<Int>
やany Collection<Int>
のように書くことができる。
Swift5.6
データ競合を起こす可能性がある
class MyCounter {
var value = 0
}
func f() -> MyCounter {
let counter = MyCounter()
Task {
counter.value += 1 // warning: capture of non-Sendable type 'MyCounter'
}
return counter
}
既存の宣言では、Concurrencyに関連するアノテーション(クロージャを@Sendable
にするなど)を導入しても、
@preconcurrency
属性を使用してConcurrencyにまだ適用していないクライアントの挙動を維持できる
// module A
@preconcurrency func runOnSeparateTask(_ workItem: @Sendable () -> Void)
// module B
import A
class MyCounter {
var value = 0
}
func doesNotUseConcurrency(counter: MyCounter) {
runOnSeparateTask {
counter.value += 1 // no warning, because this code hasn't adopted concurrency
}
}
func usesConcurrency(counter: MyCounter) async {
runOnSeparateTask {
counter.value += 1 // warning: capture of non-Sendable type 'MyCounter'
}
}
-warn-concurrency
コンパイラオプションを使用すると、モジュール内のデータ競合の安全性に関する警告を有効にできる。
Sendable
アノテーションをまだ提供していないモジュールを使用する場合、インポートを@preconcurrency
でマークすることにより、
そのモジュールからimportした型の警告を抑制することができる
/// module C
public struct Point {
public var x, y: Double
}
// module D
@preconcurrency import C
func centerView(at location: Point) {
Task {
await mainView.center(at: location) // no warning about non-Sendable 'Point' because the @preconcurrency import suppresses it
}
}
// Works on global actors
@MainActor
func runAnimation(controller: MyViewController) async {
controller.hasActiveAnimation = true
defer { controller.hasActiveAnimation = false }
// do the animation here...
}
// Works on actor instances
actor OperationCounter {
var activeOperationCount = 0
func operate() async {
activeOperationCount += 1
defer { activeOperationCount -= 1 }
// do work here...
}
}
@MainActor
func partyGenerator() -> [PartyMember] { fatalError("todo") }
class Party {
@MainActor var members: [PartyMember] = partyGenerator()
// ^~~~~~~~~~~~~~~~
// warning: expression requiring global actor 'MainActor' cannot
// appear in default-value expression of property 'members'
}
Swift5.7
distributed actor
とdistributed func
が利用可能に。
Swift ConcurrencyのActorを拡張してリモートの分散システムでスレッドセーフかつ型安全に処理できるようにしたもの。
distributed actor Greeter {
var greetingsSent = 0
distributed func greet(name: String) -> String {
greetingsSent += 1
return "Hello, \(name)!"
}
}
func talkTo(greeter: Greeter) async throws {
// isolation of distributed actors is stronger, it is impossible to refer to
// any stored properties of distributed actors from outside of them:
greeter.greetingsSent // distributed actor-isolated property 'name' can not be accessed from a non-isolated context
// remote calls are implicitly throwing and async,
// to account for the potential networking involved:
let greeting = try await greeter.greet(name: "Alice")
print(greeting) // Hello, Alice!
}
トップレベルのスクリプトでもConcurrencyが利用可能に。
トップレベルのコードでawait
を使うと、asyncのコンテキストと見なされて、トップレベルの変数(グローバル変数やstatic変数)は全て@MainActor
が付与される。
@available(*, noasync)
を使ってasyncコンテキストで利用できない宣言をすることができるように。
デッドロックやsuspension pointを跨ってスレッドローカルな値が利用されることを防ぐ。(不定な挙動)
時間と時計を表す新しい型を導入。これには、現在の概念と特定の瞬間の後にウェイクアップする方法を定義できる時計を定義するプロトコルClock
が含まる。さらに、瞬間を定義するための新しいプロトコルInstantProtocol
が追加。さらに、2つの指定されたInstantProtocol
型間の経過時間を定義するために、新しいプロトコルDurationProtocol
が追加。最も一般的に使用されるClock
型は、システムの最も基本的なクロックを表すSuspendingClock
とContinuousClock
。SuspendingClock
型は、マシンがサスペンドされている間は進行しないが、ContinuousClock
はマシンの状態に関係なく進行する。
func delayedHello() async throws {
try await Task.sleep(until: .now + .milliseconds(123), clock: .continuous)
print("hello delayed world")
}
Clock
には、作業の実行の経過時間を測定するメソッドもある。SuspendingClock
およびContinuousClock
の場合、これは高精度で測定され、ベンチマークに適している。
let clock = ContinuousClock()
let elapsed = clock.measure {
someLongRunningWork()
}
使用例:
non-isolatedなasync関数は、常にglobal協調プールのExecutorで実行されるようになった。したがって、actor-isolatedなasync関数からnon-isolatedなasync関数を呼び出すと、actorからは離れる。 例えば:
class C { }
func f(_: C) async { /* 常にグローバル協調プール上で実行されている */ }
actor A {
func g(c: C) async {
/* 常にactor上で実行される */
print("on the actor")
await f(c)
}
}
この変更以前だと、f
からg
への呼び出しは、actorのg
は、actorが厳密に必要以上に長く働き続ける可能性があった。
今回の変更で、non-isolatedなasync関数は常にglobal協調プールにホップし、actorで実行されない。
これにより、non-isolatedなasync関数が@ MainActor
コンテキストでMain Thread上で実行されることを想定している場合、動作が変化する可能性がある。
(この想定はすでに技術的に正しくないが。)
さらに、actorからglobal協調プールグローバルへ実行に任せる際、Sendable
チェックが実行されるため、
コンパイラはc
がSendable
タイプでない場合、f
の呼び出しでエラーが出力される。
標準ライブラリに、新しいRegex<Output>
型を導入。
この型は拡張正規表現を表し、より流暢な文字列処理操作を可能にする。正規表現は、文字列からの初期化によって作成できる。
let pattern = "a[bc]+" // "a"の後に一つ以上の"b"か"c"がある
let regex = try! Regex(pattern)
もしくは正規表現リテラルを使う
let regex = #/a[bc]+/#
Swift 6では、/
は正規表現リテラルの区切り文字としてもサポートされる。このモードは、Swift5.7で-enable-bare-slash-regex
フラグを使用して有効にできる。これを行うと、/
を演算子として使用する既存の式の一部がコンパイルされなくなる。 回避策として、括弧または改行を追加する。
String
、Regex
、任意のCollection
型をサポートする新しい文字列処理アルゴリズムがある。
Swift5.6
// This is OK--the compiler can infer the key type as `Int`.
let dict: [_: String] = [0: "zero", 1: "one", 2: "two"]
Swift5.7
オプショナルバインディングで同じ変数名を使用する場合に変数への代入が不要になる
Before:
let foo: String? = "hello world"
if let foo = foo {
print(foo) // prints "hello world"
}
After:
let foo: String? = "hello world"
if let foo {
print(foo) // "hello world"
}
明示的に型を指定しなくても良くなった。
func map<T>(fn: (Int) -> T) -> T {
return fn(42)
}
func computeResult<U: BinaryInteger>(_: U) -> U { /* processing */ }
let _ = map {
if let $0 < 0 {
// do some processing
}
return computeResult($0)
}
map
関数に渡された引数の型から戻り値の型が推論できる。
参考: https://github.com/stzn/SwiftPodcast/blob/main/episodes/Swift%20複数行クロージャ内の引数と戻り値の型推論.md
Unsafe(Mutable)RawPointer
、Unsafe(Mutable)RawBufferPointer
に対してwithMemoryRebound<T>()
が使用可能に。
UnsafeRawPointer
、UnsafeMutableRawPointer
に前後のポインタを取得する関数を追加
extension UnsafeRawPointer {
public func alignedUp<T>(for: T.type) -> UnsafeRawPointer
public func alignedDown<T>(for: T.type) -> UnsafeRawPointer
public func alignedUp(toMultipleOf alignment: Int) -> UnsafeRawPointer
public func alignedDown(toMultipleOf alignment: Int) -> UnsafeRawPointer
}
- structの格納プロパティのポインタを取得可能に
withUnsafeMutablePointer(to: &myStruct) {
let interiorPointer = $0.pointer(to: \.myProperty)!
return myCFunction(interiorPointer)
}
- 型変換せずに
==
、!=
、<
、<=
、>
、>=
を使ってポインタ同士の比較が可能
UnsafeRawPointer
, UnsafeRawBufferPointer
などのメモリから直接データをロードできるようになった。
Before:
let result = unalignedData.withUnsafeBytes { buffer -> UInt32 in
var storage = UInt32.zero
withUnsafeMutableBytes(of: &storage) {
$0.copyBytes(from: buffer.prefix(MemoryLayout<UInt32>.size))
}
return storage
}
After:
let result = unalignedData.withUnsafeBytes { $0.loadUnaligned(as: UInt32.self) }
さらに、対応するstoreBytes(of:toByteOffset:as:)
の配置制限が解除されたため、Rawメモリの任意のオフセットへの保存が成功するようになった。
Swift5.6
新しい#unavailable
キーワードを使用して、反転したアベイラビリティ条件を記述できるように。
if #unavailable(iOS 15.0) {
// Old functionality
} else {
// iOS 15 functionality
}
新しいプロトコルCodingKeyRepresentable
に準拠する任意の型のキーを持つDictionaryをエンコードおよびデコードできるように。 以前は、エンコードとデコードはString
型またはInt
型のキーに制限されていた。
※ OSのバージョンに制限がある
@available(macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4, *)
public protocol CodingKeyRepresentable {
@available(macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4, *)
var codingKey: CodingKey { get }
@available(macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4, *)
init?<T>(codingKey: T) where T : CodingKey
}
Swift5.7
以前は、潜在的に利用不可な準拠をしているメンバへの参照や型消去の式は診断されず、実行時に潜在的にクラッシュする可能性があった。
struct Pancake {}
protocol Food {}
extension Food {
var isGlutenFree: Bool { false }
}
@available(macOS 12.0, *)
extension Pancake: Food {}
@available(macOS 11.0, *)
func eatPancake(_ pancake: Pancake) {
if (pancake.isGlutenFree) { // warning: conformance of 'Pancake' to 'Food' is only available in macOS 12.0 or newer
eatFood(pancake) // warning: conformance of 'Pancake' to 'Food' is only available in macOS 12.0 or newer
}
}
func eatFood(_ food: Food) {}
protocol P {
associatedtype A : Q where Self == Self.A.B
}
protocol Q {
associatedtype B
static func getB() -> B
}
class C : P {
typealias A = D
}
class D : Q {
typealias B = C
static func getB() -> C { return C() }
}
extension P {
static func getAB() -> Self {
// `Self.A.getB()`は`Self.A.B`を返すので`Self`と一緒
return Self.A.getB()
}
}
class SubC : C {}
// P.getAB()は`Self`が戻り値なので`SubC`が戻ってくるはずが、実際は`C`が返す
print(SubC.getAB())
これを正しくするにはC
をfinal
にするか(ただしSubC
は宣言できなくなる)、プロトコルP
でSelf == Self.A.B
の要件を含めなくする必要がある。
プロトコルメタタイプのoptional
メソッドへの参照、およびAnyObject
の動的にルックアップされるメソッドへの参照が、他の関数参照と同等にサポートされるようになった。このような参照の種類(以前は誤って即時オプショナルになっていた)は、単一の引数を受け取り、関数型のオプショナル値を返す関数型に変更された。
class Object {
@objc func getTag() -> Int { ... }
}
let getTag: (AnyObject) -> (() -> Int)? = AnyObject.getTag
@objc protocol Delegate {
@objc optional func didUpdateObject(withTag tag: Int)
}
let didUpdateObjectWithTag: (Delegate) -> ((Int) -> Void)? = Delegate.didUpdateObject