🤔そういやfatalErrorってどういう関数なんや?
こんにちは、🤔わからんこと掘り下げ太郎
です。
まさかよくわからないままprint()
みたいなノリで、fatalError()
を意図しない動作が起こりうる場所で使っているやつはいねえよなぁ?
⌘cmd
+ ^ctl
+ click
でfatalError()
の定義に飛んでみると
Swift > Assert ってファイルの中に記述されている。
public func assert(...) //1
public func precondition(...) //2
@inlinable public func assertionFailure(...) //3
public func preconditionFailure(...) -> Never //4
public func fatalError(...) -> Never //5
ちょうどprecondition()
とかもよくわかっていなかったし、順番に全部見ていく。
1. assert()
public func assert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line)
-
assert()
はデバッグ中に条件をチェックするために使用される関数。
- デバッグ中のみ有効でリリースビルドでは無効化される。
- conditionがfalseの時、プログラムの実行を停止しmessage、file、lineをprintする。
🤔@autoclosureって何?
@autoclosure
は引数を自動的にクロージャに変換してくれる機能。
@autoclosure
の嬉しい点:
- 簡潔明瞭:クロージャの記述を省略して関数の引数として直接式を渡せる。
- 遅延評価:関数呼び出し時ではなく、必要な時点でそのクロージャを実行できる。
- 条件付きで実行:遅延評価されるので、不必要な場合は実行されずサボれる。
file: StaticString = #file, line: UInt = #line
を詳しくみる。
#file
, #line
でassert()
が呼ばれたfile, lineを取得できるらしい。
#hoge
はプリプロセッサ指令といい「文字の集合であるソースコード上で機能する命令」だそうだ。
ということは以下のようなConditional Completion Blockもプリプロセッサ指令なのかな?
#if os(iOS)
...
#else
...
#endif
あとfile
の型のStaticString
ってなんだ?
⌘cmd
+ ^ctl
+ click
で定義に飛ぶと
@frozen public struct StaticString : Sendable {
...
}
流石にこれ以上掘り下げるのは面倒くさいのでChatGPT先生に聞いてみた。
(間違いがある可能性あり)
🤔StaticStringって何?
ChatGPT:
StaticString
は、Swiftの組み込み型であり、不変の文字列データを表します。StaticString
は文字列リテラルのように扱われますが、不変であり、メモリ上に固定された形式で格納されます。このため、実行時の文字列操作やメモリ管理のコストが最小限に抑えられます。
StaticString
は、主にフォーマット文字列として使用され、print()
やassert()
などの関数でフォーマット指定子と組み合わせて利用されます。フォーマット指定子は%
を使ってStaticString
内のプレースホルダーと対応します。
以下は、StaticString
の使用例です:
let name = "John"
let age = 30
let message: StaticString = "\(name) is \(age) years old."
print(message) // John is 30 years old.
上記の例では、message
はStaticString
型として宣言されており、name
とage
の値をフォーマットしています。
StaticString
は不変であるため、文字列の値を直接変更することはできません。また、StaticString
は文字列操作の機能が限定されているため、一般的な文字列操作メソッドやプロパティ(例:count
やisEmpty
)を使用することはできません。
StaticString
は主にパフォーマンスやメモリ使用量の最適化が必要な場合に使用されます。ただし、一般的な文字列操作や変更が必要な場合は、String
型を使用する方が適しています。
🤔@frozenって何?
ChatGPT:
@frozen
は、Swiftの属性(Attribute)の一種であり、データ構造体(struct)や列挙型(enum)が「凍結された」(変更不可)であることを示すために使用されます。
@frozen
属性をデータ構造体や列挙型に適用することで、その型の宣言がコンパイラによって最適化され、特定の条件下でより効率的に使用されることが保証されます。
主な利点としては以下のようなものがあります:
-
最適化:
@frozen
属性を使用することで、コンパイラはデータ構造体や列挙型が変更不可であることを知るため、特定のコードパスでの最適化が可能になります。例えば、列挙型の場合、特定の値に対するスイッチ文の最適化や、列挙型のサイズの最小化が行われることがあります。 -
パフォーマンス向上:
@frozen
属性を使用することで、メモリのレイアウトやアクセス方法などが最適化され、実行時のパフォーマンスが向上する可能性があります。 -
APIの安定性:
@frozen
属性を使用することで、特定のデータ型を変更不可と宣言することができます。これにより、他のモジュールやクライアントコードに対して、そのデータ型が安定していることが伝わります。
以下は、@frozen
属性の使用例です:
@frozen enum Status {
case success
case failure
}
@frozen struct Point {
var x: Int
var y: Int
}
上記の例では、Status
列挙型とPoint
構造体に@frozen
属性が適用されています。これにより、コンパイラはそれらの型が変更不可であることを認識し、最適化を行うことができます。
なお、@frozen
属性はリリースビルド時にのみ有効であり、デバッグビルド時には効果を発揮しません。また、@frozen
属性を使用する場合は、変更不可であることを十分に検討し、将来的な拡張性や互換性についても考慮する必要があります。
🤔Sendableって何?
ChatGPT:
Sendable
は、Swift 5.5で導入されたプロトコル(Protocol)であり、非同期処理や並行性に関連する機能をサポートするために使用されます。
Sendable
プロトコルを準拠する型は、非同期コンテキスト(Task
など)を介して安全に送信(送出)およびアクセスできることを示します。これにより、並行処理のためのデータ共有と同期を簡素化し、データ競合などの問題を回避することができます。
Sendable
プロトコルの主な特徴は以下の通りです:
-
送信可能性:
Sendable
プロトコルを準拠する型は、非同期コンテキストを介して送信(送出)できることを表します。このプロトコルを準拠する型のインスタンスは、非同期タスク内で非同期に移動したり、他の非同期タスクに渡したりすることができます。 -
データ競合の回避:
Sendable
を使用することで、並行してアクセスされるデータへの安全なアクセスを提供します。複数の非同期タスクが同時にデータにアクセスしても、データ競合が発生することがなくなります。
以下は、Sendable
プロトコルの使用例です:
@Sendable
func processItems(items: [Int]) async {
for await item in items {
// 非同期処理
print(item)
}
}
async {
await processItems(items: [1, 2, 3])
await processItems(items: [4, 5, 6])
}
上記の例では、processItems
関数に@Sendable
属性が付与されています。この関数は非同期に処理され、items
の要素を非同期に処理します。processItems
関数は非同期コンテキスト内で安全に呼び出されるため、複数の非同期タスクから同時に呼び出されても競合や問題は生じません。
Sendable
は非同期処理や並行性をサポートする上で重要な役割を果たします。しかし、すべての型がSendable
を準拠するわけではなく、データの共有やアクセスの安全性を考慮して使用する必要があります。
2. precondition()
public func precondition(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line)
assert()
とほぼ一緒だな。
ただし、precondition()
はリリースビルドでも有効なので注意。
3. assertionFailure()
@inlinable public func assertionFailure(_ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line)
これもassert()
と似ていて、playgroundやデバッグ時にプログラムの実行を止めてmessage
をprintする。
ただしassert()
と異なりcondition
を持たないので、いつエラー投げるかはassertionFailure()
の外側で定義する。
4. preconditionFailure()
public func preconditionFailure(_ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) -> Never
assertionFailure()
に似ている。
-O builds
(デフォルトのリリースビルドコンフィギュレーション)でもプログラムの実行を停止する。
5. fatalError()
public func fatalError(_ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) -> Never
今日知りたかったfatalError()
無条件でエラーぶん投げるから、使用には注意が必要そう。
まとめ
自分の場合、前提を満たしていない場合にエラーを投げたいことが多かったから、条件を引数に渡せる precondition()
を使うのが一番簡潔に書けそう。