Skip to content

Instantly share code, notes, and snippets.

@stzn
Last active July 3, 2022 00:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stzn/75aee9519254808db3746b9f7e5ce88d to your computer and use it in GitHub Desktop.
Save stzn/75aee9519254808db3746b9f7e5ce88d to your computer and use it in GitHub Desktop.
About new features in Swift5.6 & 5.7

ジェネリクス

Swift5.6

存在型にanyキーワードを明示的に指定可能に

SE-335

存在型とプロトコル制約の区別を明確にできる。

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

Selfやassociatedtypeの要件のあるプロトコルを存在型として利用可能に

(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 Result Typeが構造的位置でも利用可能に

SE-328

Opaque Type(someを使う型)を戻り値の型の構造的位置でも利用可能に(同じ戻り値の型に複数のOpaque Typeを含めることも可)。

func getSomeDictionary() -> [some Hashable: some Codable] {
  return [ 1: "One", 2: "Two" ]
}

参考: https://github.com/stzn/SwiftPodcast/blob/main/episodes/Swift%20Opaque%20Result%20Typeの拡張と戻り値の新しい書き方.md

Opaque Typeをパラメータで利用可能に

SE-341

関数や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の型は実装側で決める

参考: https://github.com/stzn/SwiftPodcast/blob/main/episodes/Swift%20Opaque%20Parameter%20Types%20引数でもsomeが使えるように.md

ジェネリック関数にデフォルト値や型が指定できるように

func compute<C: Collection>(_ values: C = [0, 1, 2]) {
  ...
}

呼び出し側で引数を指定しなかった場合、computeはコンパイルに成功してC[Int]に推論される。

参考: https://github.com/stzn/SwiftPodcast/blob/main/episodes/Swift%20引数のデフォルト式からジェネリックパラメータの型を推論する.md

存在型の暗黙的開示

SE-352

存在型の値を使用してジェネリック関数を呼び出すことができるように(以前はできなかった)。

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が必要だった
}

Swift5系だけ挙動が異なる https://github.com/apple/swift-evolution/blob/main/proposals/0352-implicit-open-existentials.md#avoid-opening-when-the-existential-type-satisfies-requirements-in-swift-5

参考: https://github.com/stzn/SwiftPodcast/blob/main/episodes/Swift%20any版primary%20associated%20types.md

全ての存在型を型として利用可能に

SE-0309

関連型と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 typesによる軽量な同じ型制約の指定

SE-346

プロトコルは一つ以上の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への制約の例。

参考: https://github.com/stzn/SwiftPodcast/blob/main/episodes/Swift%20Primary%20associated%20types%20プロトコル関連型を指定する新しい構文.md

存在型へのprimary associated typesを使った制約

SE-0353

上記をより一般化して、primary associated typesを使ったプロトコルを存在型にも利用できる。

let strings: any Collection<String> = [ "Hello" ]

動的キャスト(isas?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

標準ライブラリの型への主要なassociated typeの導入

SE-0358

標準ライブラリのさまざまなプロトコルで、primary associated typesを宣言するようになった。 例: SequenceCollectionは、単一のprimary associated typeElementを宣言する。

たとえば、これにより、some Collection<Int>any Collection<Int>のように書くことができる。

Concurrency

Swift5.6

actorやTaskの境界をまたがるnon-Sendableな型を使用した際にワーニングを出力する

データ競合を起こす可能性がある

class MyCounter {
  var value = 0
}

func f() -> MyCounter {
  let counter = MyCounter()
  Task {
    counter.value += 1  // warning: capture of non-Sendable type 'MyCounter'
  }
  return counter
}

@preconcurrencyを使ってSendableチェックを段階的に導入できるように(既存ソースの破壊を抑える)

SE-0337

既存の宣言では、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
  }
}

actor-isolationチェックがdeferでも正しく行われるように(isolationの状態をdefer内部でも共有できる)

// 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...
  }
}

global actorが必要なインスタンスメンバにデフォルト式が設定されていた場合にSwift5系ではワーニングが出力される

SE-0327

@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

SE-336

distributed actordistributed 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

SE-343

トップレベルのスクリプトでもConcurrencyが利用可能に。

トップレベルのコードでawaitを使うと、asyncのコンテキストと見なされて、トップレベルの変数(グローバル変数やstatic変数)は全て@MainActorが付与される。

参考: https://github.com/stzn/SwiftPodcast/blob/main/episodes/Swift%20%E3%83%88%E3%83%83%E3%83%97%E3%83%AC%E3%83%99%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AEConcurrency.md

noasync属性

SE-340

@available(*, noasync)を使ってasyncコンテキストで利用できない宣言をすることができるように。

デッドロックやsuspension pointを跨ってスレッドローカルな値が利用されることを防ぐ。(不定な挙動)

参考: https://github.com/stzn/SwiftPodcast/blob/main/episodes/Swift%20noasyncでSwift%20Concurrencyからの利用を防ぐ.md

Clock

SE-0329

時間と時計を表す新しい型を導入。これには、現在の概念と特定の瞬間の後にウェイクアップする方法を定義できる時計を定義するプロトコルClockが含まる。さらに、瞬間を定義するための新しいプロトコルInstantProtocolが追加。さらに、2つの指定されたInstantProtocol型間の経過時間を定義するために、新しいプロトコルDurationProtocolが追加。最も一般的に使用されるClock型は、システムの最も基本的なクロックを表すSuspendingClockContinuousClockSuspendingClock型は、マシンがサスペンドされている間は進行しないが、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()
}

使用例:

ManualClock TestManualClock

参考: https://github.com/stzn/SwiftPodcast/blob/main/episodes/Swift%20新しい時間の概念(Clock%2C%20Instant%20and%20Duration).md

non-isolatedなasync関数が実行されるExecutorの明確化

SE-0338

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チェックが実行されるため、
コンパイラはcSendableタイプでない場合、fの呼び出しでエラーが出力される。

参考: https://github.com/stzn/SwiftPodcast/blob/main/episodes/Swift%20%E3%81%9D%E3%81%AEasync%E9%96%A2%E6%95%B0%E3%81%AF%E3%81%A9%E3%81%93%E3%81%A7%E5%8B%95%E3%81%8F%3F.md

Regex

Regex型と概要

SE-0350

標準ライブラリに、新しい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フラグを使用して有効にできる。これを行うと、/を演算子として使用する既存の式の一部がコンパイルされなくなる。 回避策として、括弧または改行を追加する。

StringRegex、任意のCollection型をサポートする新しい文字列処理アルゴリズムがある。

一般的な構文

Swift5.6

Type placeholder

SE-0315

// This is OK--the compiler can infer the key type as `Int`.
let dict: [_: String] = [0: "zero", 1: "one", 2: "two"]

Swift5.7

オプショナルバインディングのショートハンド構文

SE-345

オプショナルバインディングで同じ変数名を使用する場合に変数への代入が不要になる

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

ポインタ周り

withMemoryReboundの可用性の拡張

SE-333

Unsafe(Mutable)RawPointerUnsafe(Mutable)RawBufferPointerに対してwithMemoryRebound<T>()が使用可能に。

ポインタAPIの可用性の改善

SE-334

  • UnsafeRawPointerUnsafeMutableRawPointerに前後のポインタを取得する関数を追加
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)
}
  • 型変換せずに==!=<<=>>=を使ってポインタ同士の比較が可能

Rawメモリからのアラインされていないロードとストア

SE-349

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

SE-0290

新しい#unavailableキーワードを使用して、反転したアベイラビリティ条件を記述できるように。

if #unavailable(iOS 15.0) {
    // Old functionality
} else {
    // iOS 15 functionality 
}

CodingKeyRepresentable

SE-0320

新しいプロトコルCodingKeyRepresentableに準拠する任意の型のキーを持つDictionaryをエンコードおよびデコードできるように。 以前は、エンコードとデコードはString型またはInt型のキーに制限されていた。

参考: https://github.com/stzn/SwiftPodcast/blob/main/episodes/Swift%20CodingKeyRepresentableでDictionaryを適切にエンコード、デコードする.md

※ 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) {}

プロトコルに準拠したnon-finalなclassでSelfと関連型(associated type)に同じ型の要件がある場合にワーニングが出力されるように

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())

これを正しくするにはCfinalにするか(ただしSubCは宣言できなくなる)、プロトコルPSelf == Self.A.Bの要件を含めなくする必要がある。

protocolメタタイプのoptionalメソッドへの参照とAnyObjectの動的にルックアップされるメソッドへの参照をサポート

プロトコルメタタイプの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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment