1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

classがSendableに適合するためにはfinalなclassである必要がある件

Posted at

はじめに

swift concurrencyの対応を行なっているとclassをSendableに適合させる必要がある場面があり以下のように実装してみたのですが、警告が表示されコンパイラに怒られました。

class Counter: Sendable {
    
    let value: Int = 0
}

警告の内容としては以下のとおりです。

Non-final class 'Counter' cannot conform to 'Sendable'; use '@unchecked Sendable'; this is an error in the Swift 6 language mode

今回は上記の警告での対応方法と、なぜこの警告が出たのかを調べたのでその内容を記事にしました。

警告の内容と対応方法

これはコンパイラの警告を日本語訳するとfinal出ないclassはSendableには適合できまへんということなので、素直にclassにfinalをつけるとコンパイラを黙らせることができます。
(コンパイラは結構ちゃんと教えてくれていました)

修正後のコードは以下のとおりです。

final class Counter: Sendable {
    
    let value: Int = 0
}

これだけでは流石に味気ない記事で終わってしまうので、冒頭でも述べたとおりなぜfinalなclass出ないとSendableに適合できないのか以降で考えてみます。

なぜclassはfinalでないとSendableに適合できないのか

これはSendableの意味とfinalなclassの意味を理解すれば、理由が見えてきます。

Sendableとは

データ競合しない型であることを保証するプロトコルです。
データ競合しないということは、型がイミュータブル(不変)であるということです。(複数のスレッドで同時に書き込みを含むアクセスがあったときにデータ競合が起きるため、書き込みが起きない型であれば書き込みができないのでデータ競合も起きない)

finalなclassとは

継承されないclassのことです。

finalがついていないclassは別クラスで継承できますが、finalがついたclassを継承しようとするとコンパイルエラーになります。
つまり継承されないことを保証したclassとなります。

final class Counter: Sendable {
    
    let value: Int = 0
}

class inheritableCounter {
    let value: Int = 0
}

class SampleCounter1: inheritableCounter {} // OK

class SampleCounter2: Counter {} // コンパイルエラー

継承すると、以下SampleCounter1のようにプロパティの拡張が行えたり、継承元の型として振る舞うことができます。

class InheritableCounter {
    let value: Int = 0
}

class SampleCounter1: InheritableCounter {
    var extensionValue: Int = 0
}

let counter: InheritableCounter = SampleCounter1()

ここまでくると、なぜfinalなclassでないとSendableに適合できないか見えてきました。

finalでないclassは継承によって ミュータブル(可変)な型になることが可能です。
そして、継承された型は継承元と同じ型として振る舞うことができる性質上、継承元がイミュータブルでも継承された型がミュータブルとなり継承元の型として振る舞うと継承元の型がイミュータブルであることつまりSendableであることが保証されなくなってしまいます。
よってclassをSendableに適合するにはfinalで継承されないことを保証してあげる必要があるわけでした。
(ちなみに、structは継承することができないので、finalをつけないとSendableになれないといった制約はありません)

class InheritableCounter { // 定数しかないので、イミュータブルなクラス
    let value: Int = 0
}

class SampleCounter1: InheritableCounter { // 変数を持ったミュータブルなクラス
    var extensionValue: Int = 0
}

let counter: InheritableCounter = SampleCounter1() // ミュータブルなSampleCounter1がInheritableCounterとして振る舞っている

おわり

元々はちょっとした警告の対応でしたが、なぜこれがダメなのかちゃんと考えると理由もちゃんとあって謎解きみたいで楽しかったです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?