ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Swift用にObjective-Cフレームワークを洗練させる
Objective-CのヘッダーをSwift内で美しく動くよう、洗練させましょう。扱いの難しいObjective-Cフレームワークを、使い心地の良いAPIに転換する方法をお伝えします。より豊富な型情報、慣用名、Swiftへのより良いエラーを提供するために使うことのできる注釈の置き場についてご説明します。まだあなたが気がついていないかもしれないSwift APIを正しく動かすためのカギを握るObjective-Cのコンベンションについてもご紹介します。 このセッションを最大限に活用するには、SwiftとObjective-Cに慣れていることが望ましいです。 SwiftとObjective-Cでできることについてのさらなる情報は、Developer Documentationでご確認ください。またWWDC18 の “Behind the Scenes of the Xcode Build Process”も合わせてご覧ください。
リソース
-
ダウンロード
こんにちは WWDCへようこそ “Swift用にObjective-Cフレームワークを 洗練させる” Swift Compilerチームのブレントです このセッションでは― Swift用にObjective-Cフレームワークを 洗練させる方法についてお話しします 私たちは6年前のこの月に初めて Swiftを導入しました それ以来 すごいことが起こっています 大勢がSwiftのデベロッパになったのです ただしAppleのプラットフォームが長い間― Objective-C限定プラットフォームとして 使われていたため 多くのユーザーは Swiftをすぐには導入できませんでした 多くのユーザーが今でも使っている Objective-Cコードを― 使うSwiftクライアントは増え続けています
Appleは同じ状況を 共有しているからだと思います Appleは世界一多くのObjective-C フレームワークを持っているでしょう Objective-Cフレームワークを インポートするだけでなく― より“こなれた”Swift APIに 変換するように設計しました マクロとキーワードをヘッダーファイルに追加し 変換をカスタマイズする方法で設計しました
カスタムSwiftコードを使ってObjective-C フレームワークを拡張する機能を構築し 既存のAPIをラップするか Objective-Cでは 表現できない新しいAPIを追加しています
何よりも このすべて設計は 弊社のためだけでなく皆様のためでもあります これが今日の私のトピックです AppleのObjective-Cフレームワークの技術により Swiftへのインポートが どのように改善されたかを説明します それには私がSpaceKitと呼んでいる 小さなフレームワークを使います 見てみましょう
SpaceKitは NASAの初期の粗い宇宙計画を 記述するモデルAPIが― いくつか含まれた Objective-Cフレームワークです 宇宙飛行士 ミッション ロケットなどを 記述することができます ヘッダーを見ると Objective-Cフレームワークが #importディレクティブ @interfaceブロック セレクタを持つメソッドでいっぱいです しかしSwiftにインポートされる際 コンパイラで 自動的にSwift APIに変換されるため ヘッダーの生成されたインターフェイスを見れば Xcodeで適切な外観を持つAPIだと分かります よってエディタの左上にある “関連項目”メニューの― 下にあるサブメニュー “生成インターフェイス”を開き Swift 5インターフェイスを要求します Xcode12のSwiftバージョンが5.3だからです Swiftのヘッダーファイルがあれば 結果的にそれを確認します ここにはクラスやイニシャライザ プロパティ メソッドなどがあります しかしこれは表示のボディではなく ただのインターフェイスです このフレームワークをインポートする時に Swiftが確認する内容を知るのに役立ちます
このセッションでは SpaceKitの生成インターフェイスや このような擬似ヘッダーの例を紹介します
詳細を見ればSwiftではすでに すばらしいことが 実行されていると分かるでしょう 例えば これらのパラメータはすべて Objective-CのNSStringおよびNSDateでした しかしコンパイラはSwiftで使う構造体への 橋渡しとなっています
イニシャライザとしてインポートされた initメソッドであり― メソッド名を“Swift”に近いスタイルに 書き換えられています そしてObjective-Cエラー 処理変換の規則に従うメソッドが スローメソッドに変換されたものです ただしこのフレームワークには 改善の余地もあります APIには 暗黙にラップ解除しないオプションが 多数あります “AnyHashable”型の“Any”は これらのコレクションでは本当に曖昧です
このスローメソッドでは時々 スローすべきでないものもスローしますし
このメソッド名は完璧ではないかもしれません
他と見比べると この2つのイニシャライザは やたらと冗長に見えますし このNSエラー情報は try-catchで使うのは難しいでしょう
オブジェクトから離れて SpaceKitのよりCスタイルの部分を見てみると もっと手を加えるべきだと分かります
この種類では NS_Enumが使われているので それは非常に優れたNS_Enumとして示されます しかしストリング定数のコレクションでは 同じ処理を使用できます そして これらの定数の1つが 完全に消えています
このフリーフローティングユーティリティ関数は Swiftには理想的ではなく このUIntリザルトは厄介です これらの問題を すべて改善する方法を紹介します APIの型を より正確に指定すれば Swiftクライアントがフレームワークを 正しく使えるようになります SwiftがAPIが従うことを前提としている Objective-Cの規則で 大事な点は2つあります Objective-Cがアクセス可能な重要なものを Swiftがインポートしていない可能性がある時の 対処法も紹介します フレームワークの 最後の仕上げが上手くできれば 善良なSwift市民になったように 感じられるでしょう では 始めましょう 暗黙的にラップ解除されず APIのどこにでも現れるオプションを使います それはフレームワークの機能が非表示といった 重要な側面が残されます これらのオプションができた経緯や 削除する方法とは? idタイプとブロックを含む Objective-Cポインタ型には 有効な値の場合と “null”または“nil”を 呼び出すゼロ値を含む場合があります これはSwiftのオプションの種類に 非常に似ています 実際にはObjective-C以外の すべてのポインタ型はオプションであり すべての非ポインタ型は オプションではありません もちろん多くの場合 プロパティやメソッドは 実際にはnil入力を処理しないか nilの結果を返しません よってSwiftがObjective-Cポインタ型を インポートすると デフォルトでは暗黙的にラップ解除された オプションとしてマークされます この値がnilである可能性があると 示すためですが 本当にそうかは不明です
幸いなことにObjective-Cは null以外 null許容の2つのnull許容注釈を提供します これによって nilが特定のプロパティの実用的な値か― メソッドパラメータか メソッドリザルトであると言えます
Objective-Cは これらの注釈を適用せず あなたの意図をドキュメント化するのみです しかしSwiftはこの情報を考慮した上で 型をオプションにするかどうかを決定します
Objective-Cヘッダーファイルを編集し これらの注釈を適用します
プロパティの場合 注釈はプロパティの属性リストに入ります メソッドパラメータまたはリザルト型の場合 型の名前の直前に移動します
この名前プロパティを使用して 始めてみましょう このクラスのインスタンスには 名前が含まれていないこともあるため Null許容注釈と型をオプションに変化する 生成されたインターフェイスに追加します すばらしいですね しかしその後“ビルド”を実行すると 新しい警告が表示されます 大丈夫 何も壊していません Xcodeはヘッダーのnull許容注釈が 使われ始めたのを認識し― まだ入力する必要のある場所をすべて 教えてくれているのです
それを参照しながら進めましょう 最初の注釈をヘッダーのどこかに追加した後 一度に1つずつ警告を潰しながら “nullable”または“nonnull”の いずれかを入力します
これが完了したら もう1つの クリーンアップ手順があります “NS_ASSUME_NONNULL_BEGIN”マクロを ファイルの先頭に追加し “NS_ASSUME_NONNULL_END”マクロを 最後に追加することです その後 2つの間にある非NULLを すべて削除します このようにファイルを 少しクリーンアップするだけで これらのキーワードの1つを見るたびに 常にSwiftでオプションを受け取ります これらを追加した場合 この定数のように このような注釈が機能しない場合があります null以外の注釈をこの前に置こうとすると コンパイラエラーが発生し 生成されたインターフェイスが 全く表示されないでしょう 私が示した注釈は メソッドとプロパティで機能します しかし定数やグローバル関数や ブロックといった他の場所では― これらの注釈の限定子のバージョンを 代わりに使う必要があります “_Nonnull”とすればObjective-Cの どの場所でも どのポインタ型にも機能します これらは“ポインタのポインタ”型でも必要です その場合は キーワードを ポインタの各レベルに対して1つ指定します 内側のポインタはnilですが 外側のポイントはそうではありません Swiftはオプションと 安全でない可変ポインタ型を正しくネストします グローバルに戻ります 適用したアンダースコア限定子は Swiftでオプションではなくなります 希望どおりです これを実行する時は 注釈が正しいか 確認する必要があります 例えば このヘッダーを一瞬見たら 次のように思うでしょう “すべてのミッションにカプセルが含まれるから カプセルプロパティを非nullに使おう” しかし 後でカプセルを ローンチしなかったミッションの存在に気づき カプセルをnilに設定することによって それをモデル化します つまり非null以外の注釈が間違っていたのです ではSwiftがオプションにできないと考える値に 対してObjective-Cが“nil”を返す場合は? Objective-C側のNSストリングまたは NSアレイなら 特別なケースとなります 空のSwift文字列または配列を 受け取ります
これは問題となる可能性があります SpaceKitは この文字列が SKCapsule定数の1つと等しい文字列であり どの同じ空の文字列とも 等しくないと予想しているためです よって正しく処理できない可能性を持つ値で 終了するでしょう
他の型の場合 それよりもずっと 奇妙な結果にもなりえます
通常 安全でない操作でしか見ないような 無効なオブジェクトで終了する可能性があります
Objective-Cオブジェクトの場合は 気づかないかもしれません なぜならObjective-Cメソッドの呼び出しで nilが無視されるからです しかしnullポインタの参照を解除したり その他の予期しない動作を取得する場合は クラッシュします コンパイラへの影響は予測不能なので リリースモードに切り替えたり Xcodeバージョンを変更したりすると この種のバグの現象が変わるかもしれません
要はヘッダーに何かがnilになれないと書けば Swiftでは強制的にラップ解除されず nilを返した場所で クラッシュは見られないでしょう
SwiftはObjective-Cヘッダーの情報を 疑うことなく信じ 実行します 幸い Objective-Cコンパイラと Clang静的アナライザもNULL許容注釈を参照し Objective-Cコードで 多くの違反コードを指摘することができます よって何かがnilになれないと言っても それをnilにするObjective-Cコードがある場合 これらのツールが 間違いを指摘してくれるのです nullを代入し注釈追加を完了したら 新しい警告または静的アナライザの結果を フレームワークでの実装ファイルと アクセス権の付与されたObjective-C クライアントの両方で探すといいでしょう これらの結果により あなたの注釈に誤りがあったのか 未知の微妙なバグがあったのかが 分かるでしょう 警告やアナライザの結果として表示された内容が 実際に起こるかどうか分からない時や とても複雑な古いコードがnilを返す エッジケースを把握することができない時は― どうすればよいのでしょうか 先ほど非nullとnull許容注釈について 言及しました 実際には“null unspecified”と呼ばれる 第三の選択肢があり 暗黙的にラップ解除されたオプションとして Swiftに値をインポートさせます Swiftで暗黙的にラップ解除されたオプションを 使うすべての場所で これを使う必要があります オブジェクトのライフサイクルの とても 早い段階でnilにしかならない値と同様です APIがnilを返すことに確信が持てない場合も これを使えば― Swiftクライアントは引き続き ラップを解除せずに結果を使うことができます フレームワークが本当にnilを返すことになれば 確実に使用サイトでクラッシュしますが 起こりそうにない不正行為を 後になって受け取ることは回避できます
プロジェクトを通じて すべての null許容注釈を追加してきました すばらしいですね 次にこの“Any”の配列と 他のコレクションに対処したいと思います Objective-Cは Swiftのような 一般的な構文をサポートしています Objective-CのSKAstronautsの NSアレイにする場合は SwiftのSKAstronautsの Swift配列を受け取ります これはNSSetとNSDictionaryでも機能するので 多くのAPIを改善できます 次はプロジェクトの別の領域を見てみましょう ここにあるSKRocketStageCountと 呼ばれる関数は― 名前から予想されるとおり カウントを返します カウントは負の数になれないので NSUIntegerとして返します SwiftではこれがUIntを返すということであり この関数がSwiftの規約を破ることになります 従来からObjective-CとSwiftの両方で 符号なし型を使ってきたのは― 整数が ビットのコレクションを表し それに対してビット単位の演算を実行する場合や 符号付き算術演算が邪魔になる 可能性のある他の計算を行う場合です 通常 このレベルで作業している時は 値に含まれる正確なビット数に配慮します NSUIntegerのサイズは アーキテクチャによって異なるため このやり方でこれを使う人は ほぼいません むしろユーザーがObjective-Cで NSUIntegerを使う主な目的は― 数値の値が負の値になりえないことを 示すためです Objective-Cがこのスタイルを有効にできるのは 自動変換と注意深く設計された オーバーフロー動作のおかげです しかし これらの特徴は 深刻な セキュリティバグを生じさせる可能性があるため Swiftには含まれていません その代わりSwiftでは 明示的に 符号なし型から符号付き型へ変換しなければ 符号付き算術演算ができません 符号なしの算術演算が負の結果を生成する場合 実行を停止します これにより SwiftでIntとUIntを混合するのに NSIntegerとNSUIntegerをObjective-Cで 混合する方法をとることは より困難になります よって慣習的なSwiftスタイルでは その方法はとらず― 決して負にならない値であったとしても 代わりにIntを使用します
Appleのフレームワークの場合 Swiftがそれらをインポートする時に すべてのNSUIntegerをIntに変換するという ブランケットルールを適用していました 皆さんのフレームワークの場合にNSIntegerを 使うようにヘッダーを更新するかどうかは 皆さん次第ですが 更新することをお勧めします Objective-Cには ほぼ影響を与えませんが Swiftには大きな影響を与えます
より広いコンテキストから見ると この機能には それよりも大きな問題があります クライアントがそれを誤用しやすい という点です SKRocketStageCount機能は これらの SKRocket定数での使用が想定されています それは名前からも明らかです
しかしSwiftはこの関数が文字列を取るのを 見ているので それを理解していません これらの定数はすべて文字列ですが かなりたくさんの文字列が入っています その1つをSKRocketStageCountに渡しても いいことは何もないでしょう 純粋なSwiftのフレームワークでは これを防ぐことができます これらの定数をString型の生の値を持つ列挙型 または構造体に変換し― その関数を変更して その型だけを取ります SpaceKitに対してそれを行う場合 これらのAPIを 手作業でラップする方法もありますが もっと簡単な方法があります
まず新しいtypedefを導入して 定数をまとめてグループ化し それを使用する定数を含む すべての場所を変更します 実質的にはそれ自体では何も実行されません typedefは型エイリアスとして Swiftにインポートされます そして両方の言語で それはちょうど 元の型の正確な同義語となっています しかし それは単なる準備段階です
次は typedefの後に NS_STRING_ENUMマクロを追加します これはSwiftのtypedefを劇的に再形成します 次に 定数が入れ子になった構造体として インポートし 生の文字列値を持って列挙型のように見えたり 感じたりするものを作ります
これはステージカウント関数が任意の文字列を 取らなくなったことを意味します SKRocketのインスタンスしか 取り入れていないので ミッションは達成されました この機能でユーザー固有の カスタム文字列列挙型を定義しますが Appleのフレームワークでは その多くを定義しています ここにFoundationから一般的なものを いくつか挙げていますが iOS SDKだけでも 最低50は存在します APIのNSStringパラメータ または 定数を探すことをお勧めします どちらか1つは本当に存在しているべきであり 一致するように更新する必要があります Swiftの想定と違い Objective-Cの規則に誤って従わなかったために トラブルに巻き込まれてしまう例を 見てみましょう SKAstronautの生成されたインターフェイスを 見ると 何か面白いものが見えます このクラスでは 2つのイニシャライザが見られます どちらも人名が渡されますが その形式は異なります 1つはPersonNameComponentという― 与えられた名前や姓などに関するプロパティを 持つFoundationの型を取ります もう一方は“name”という ラベルの付いた文字列を取ります これらのイニシャライザの1つが 他方を呼び出すのは明らかです ただし SKAstronautをサブクラス化する場合 Swiftはどちらも上書きします これは少々 無駄骨のように思えます しかし実際は このクラスのイニシャライザに 関する 第二の問題があります 生成されたインターフェイス内の 可視化されないイニシャライザです SKAstronautのコード補完を見ると パラメータのない3番目の イニシャライザが表示されます このinitは生成されたインターフェイスには 存在せず 元のヘッダーにも含まれていません 宇宙から来たもののように思えますが 実際には ここ スーパークラスから生じたものです SKAstronautは NSObjectからそれを受け継ぎ クライアントが呼び出すのですが 実際には正しく動作しない場合があります これら2つの問題の根本原因は同じです Objective-Cでは イニシャライザの規則によって 常に正しく初期化されるサブクラスの記述方法を クライアントが把握している状態になります
この規則では イニシャライザを 2つのカテゴリに分けます 指定と利便性です 指定されたイニシャライザをすべて オーバーライドすることで 利便性の高いイニシャライザを 安全に継承する必要があります “これは聞き覚えがあるな”と 思っている人もいるでしょう その記憶は正解です Swiftクラスはイニシャライザに 同一の基本モデルを使用していますからね 細かい点では違いがあります 例えば 指定されたinitを片方の言語でマークし もう一方の言語で利便性を示します Swiftクラスには 同じイニシャライザから成る 2つのカテゴリが存在し 基本的に同じように動作します
残念ながら 言語には大きな違いがあり Objective-Cの指定されたイニシャライザが 言語の規則になっていないのです 各クラスは少なくとも1つのイニシャライザを 指定どおりにマークして従う選択が必要です Objective-Cクラスの多くは オプトインしないため クラスをサブクラス化する方法を クライアントが知らないことになります これはクラスにとって よくないことですが 特にフレームワークにとっては最悪です クライアントがソースコードを読み取るか あなたの行動をリバースエンジニアリングするか 単に推測しなければならないからです バグ付きのサブクラスで終わるなら すべて いい方法です とても悪い状況ですが 必要なことをオーバーライドし忘れた場合に フレームワークのメンテナンス担当者が 警告を受け取らないということです
オーバーライドをし忘れたイニシャライザを クライアントが使用する場合 初期化中にクラスが スキップされることになります その代わりに いつも参照しているivarが nilになります
必要なことをすべて オーバーライドしたとしても 間違いが日常茶飯事なので クライアントは それを本当に確信することはできません ですから この問題を修正する最初のステップは ヘッダーに指定されたイニシャライザを マークして この規則にオプトインすることです 指定する必要があるのか不明な場合は 実装を見てみましょう 通常 指定されたイニシャライザは “super”クラスでinitを1つ呼び出し― 便利なイニシャライザは“self”で 1つを呼び出します そのボディを見れば initWithNameComponentsを指定し initWithNamesをconvenienceに すべきであることが分かります
それを念頭に置いてヘッダーファイルに戻り NS_DESIGNATED_INITIALIZERで指定された イニシャライザをマークしてください convenienceイニシャライザは 変更されないままになります SpaceKitでは initWithNameComponentを 指定してマークする一方で initWithNameは単独でそのままにしておきます
修了後 init(name:)がconvenienceイニシャライザで あることをSwiftが認識し キーワード“convenience”でマークします
この時点で Objective-C実装ファイルで 警告が表示され始めるかもしれません オーバーライドの必要があるスーパークラス指定 イニシャライザに関する警告です これらは潜在的なバグです 誰かがその1つを使っていたら オブジェクトは 正しく初期化されなかったでしょう
ご自分のクラスで これらのイニシャライザの いずれかをサポートしたい場合は 普通に実装してください そうでない場合はdoesNotRecognizeSelectorを 呼び出すオーバーライドを実装してください
そしてヘッダーファイルに戻り NS_UNAVAILABLE属性によって宣言します その後 スーパークラスのconvenience イニシャライザに同じことを行います あなたが無効にしたものを 呼び出す可能性があるためです これらのイニシャライザを使用不可として マークすることは 継承しないことと同じです 指定されたイニシャライザをオーバーライド しなければ Swiftが自動的に行います これらの変更により SwiftとObjective-C クライアントは どのイニシャライザが動作し― また どれをサブクラス内でオーバーライドする 必要があるか 把握できるようになります いいこと尽くしです
次にObjective-Cエラー処理規約について ご説明します 先ほどは投げるべきではない時に 投げることがあると言いました
むしろ説明する行動がその理由です
多くのObjective-C開発者が エラー処理の規則を誤解しています 彼らはメソッドで失敗が通知される場合は “false”を返してエラーをnon-nil値に 設定する必要があると考えています
falseは単独で戻ってくると考えたり 失敗ではなく 単なるfalseかまたはnilかと 彼らは考えたりします
しかし実際にはそのような規則はありません この規則は メソッドがfalse値を返した場合 偽の値やエラー値が“nil”であっても それはfalseです 呼び出し元では問題になっていることが 分からないので 実際にはエラーnilを残すことは お勧めしません しかし もしそうするなら falseの戻り値はやはりfalseです SwiftがスローでインポートしたObjective-C メソッドへの呼び出しを生成する時に メソッドがこの規則に 正しく従うことを前提としています 従って それはメソッドが falseを返す場合 常に投げます Swiftではnilを投げることができません エラーがない場合はSwiftが非公開の Foundationエラー型を投げます 型はパブリックではないので それに対する ステートメントを書くことはできません しかしこの型のタイプとケースが ログに表示された場合は デバッガまたはエラーメッセージが 表示されます これは一部のObjective-Cコードが 失敗でなかったのにfalseを返したか 失敗した理由が不明であった ということです
それではSpaceKitメソッドの場合で 考えてみましょう ドキュメントには 作業はスキップしたものの 実際には失敗していない場合に falseを返すことがあると書いてあります
Swiftでは 戻り値falseは エラーを投げることを想定しています そしてメソッドはエラーを設定しなかったので 今お話している内部Foundation “nilのエラー”の1つになります 何ができるでしょうか? いくつかのオプションがあります 最も簡単なのはfalseが常に失敗を意味し かつメソッドが適切に規則を遵守するように 特別なケースをただ削除する方法です これはクライアントが実際にこのケースを 検出する必要がある場合 おそらく機能しません 別の方法としてNS_SWIFT_NOTHROWを使用し エラー規則を遵守しない Swiftに伝えることができます Swiftはメソッドを通常の方法でインポートし エラー処理コードを手動で記述します これはまだbreakの種類を残しますが メソッドを廃止する場合は よい解決策かもしれません よりよい代替物を書いてください
原本を非推奨の形式で 保持したかどうかに関わらず 置換を改善する場合は error handling規則を遵守し 別の方法で追加情報を返せるように メソッドのシグネチャを変更する必要があります 例えばブール値のoutパラメータを追加して ファイルが実際に保存されたかどうかを 示すことができます その後戻り値をエラー規則で 指定した方法で使用します 完璧ではないですが Objective-Cから可能な ベストプラクティスです もし Swiftコードをもう少し書きたい場合は このメソッドから 完璧なSwiftインポートを受け取ります その方法をご覧ください
それではSKMissionの ヘッダーを見てみましょう 進行中に私が更新していたのが お分かりいただけると思います 今 古い非推奨のメソッドと 余分なパラメータを持つ新しいメソッドがあり 規則に従っています
すばらしいです さて もっと優れたSwiftバージョンを 書きましょう まずSwiftメソッドが含まれるように プロジェクトにSwiftファイルを追加します 自分がObjective-Cファイルを追加したのと 同じようにするつもりでしたが 代わりにSwiftテンプレートを選択します
このファイルはSpaceKitを インポートする必要はありません これはフレームワークの一部だからです しかしSpaceKitのObjective-Cのビットが すべて自動的に表示される訳ではありません SwiftがSpaceKitのアンブレラヘッダーに すべてを自動的にインポートします アンブレラヘッダーの名前は フレームワークと同じですが このフレームワークの場合は SpaceKit.hになっています
このヘッダーはこのフレームワーク内で すべてのパブリックヘッダーをインポートします そのためSwiftファイルには これらのヘッダーの宣言がすべて表示されます フレームワーク内にアンブレラヘッダーが 含まれるのは常にグッドプラクティスですが Swiftから使用される フレームワークであることが重要です アプリとテストのターゲットには アンブレラヘッダーがないので Xcodeはこの関数を提供するターゲットに 特別なブリッジングヘッダーの追加を提供します つまりアンブレラヘッダーに インポートすべきでない― パブリックヘッダーが1つあるのです
これが生成されたヘッダーです Swift内で@objcでマークしたものを 宣言するSpaceKit-Swiftヘッダーです 問題はここで循環依存が生じていることです Swiftでは生成されたヘッダーを作る際 必ず最初にアンブレラヘッダーに すべてをインポートします そのため生成されたヘッダーを アンブレラヘッダーにインポートした場合 Swiftは まだ生成されていないファイルを 読み込もうとします それであなたの1日は無駄になってしまいます
生成されたヘッダーを他のヘッダーに インポートしないでください
アンブレラヘッダーに 含まれていない場合でも モジュールを有効にしている Objective-Cクライアントは どちらにしても自動的に それをインポートします それで上手くいきます 今 抱えているタスクに戻ります
私はSKミッションを拡張しsave(to:)と 呼ばれる新しいメソッドを追加すると 両方ともブールを投げて返します Objective-Cエラー規則が 戻り値を引き継ぐため Objective-Cで“Bool”を返せませんでした しかしそれはObjective-Cの規則です
Swiftメソッドでは 戻り値は メソッドがエラーを投げたかどうかは 関係がないので問題はありません それではこれを実装しましょう まずwasDirty値を受け取るには Bool変数が必要です
次にObjective-Cメソッドを 呼び出します
そして最後にwasDirtyを返します “ビルド”を実行すると… 型エラーですね どうしたのでしょう
当社のプラットフォームのいくつかでは SwiftのBoolとObjective-CのBoolは メモリ表現が実際には少し異なっているのです 通常Swiftでは この動作をスムーズにするために 少しコンバージョンを挿入します しかし ここではSwiftブールへのポインタを取り Objective-Cにそれを渡してみましょう Objective-Cが直接読み取り 書き込むようにするためです Swiftがそこに変換を挿入する方法がないので 代わりにObjCBoolと呼ばれる型を使用します そしてObjective-CのBool表現と一致します これを動作させるためには…
変数の型をObjCBoolに変更する必要があります
そしてReturnステートメントで―
そのboolValueプロパティを使用して Swiftブールを返します
ビルドし そして…
よし 機能します しかし さらに改善できます
Swiftユーザーのために この優れた 新しいメソッドを用いたにも関わらず Objective-Cは なお利用可能です Swiftクライアントは混乱して どちらを使うべきか迷うこともありますので 非表示とする方がいいでしょう
しかしObjective-Cクライアントには 使用をやめてほしくないですし Swiftも完全にブロックしたくありません なぜならこのメソッドは まだ使う必要があるからです そこで私がすることは SKMissionヘッダーの見直しと
メソッドに注釈を付けることです NS_REFINED_FOR_SWIFTでこれを行います
NS_REFINED_FOR_SWIFTは非常に簡単です メソッドの Swift名の先頭に アンダースコアが2つ追加されます Xcodeにリーディングアンダースコア付きの 何らかの記載があると 通常Xcodeではこれをコード補完や 生成されたインターフェイスなどの エディタ機能で非表示になります そのため 今プロジェクトをビルドしたら Swiftコードにエラーが発生します それでは見てみましょう
保存先にwasDirtyパラメータが 含まれていないというエラーが出ます これはNS_REFINED_FOR_SWIFTの マクロが機能しているということです 呼び出しているメソッドが save(to:)という名前ではなく __save(to:)だと 認識しています コード補完を使用する場合 メソッドのwasDirtyバージョンが あったという形跡もありません しかし コード補完にメソッドが 表示されない場合でも 名前の前に2つのアンダースコアを追加し 再度ビルドすると― 機能するようになります
さてSwiftクライアントでは 優れたラッパーが使用されます やはりObjective-Cメソッドを呼び出し Objective-Cだけでは達成できなかった インターフェイスを渡します SwiftでAPIをより上手く表現できる場合は いつでもこのテクニックを使ってください 次にフレームワークに表示される可能性のある 問題を見てみましょう Swiftは ヘッダーから可能な限り すべてのものをインポートできます しかしインポートする方法が理解できない場合 スキップして次に進みます 通常Objective-Cの機能を Swiftに自動的に変換するような よい方法や自然な方法がない場合に この動作が発生します 例えばSwiftは 関数または サイズが不明なC配列を宣言する― Cの可変パラメータと構造体メンバーの メソッドをスキップします
セミコロン付きの@class または @protocolのような前方宣言の場合 ヘッダーファイルに表示されます しかしクラスやプロトコルは 完全には宣言されません Swiftにはインポートするのに 十分な型情報を含んでいません そしてこれらの型のいずれかを 使用しようとするメソッドや プロパティまたはカテゴリ全体を 最後に落とすことになる可能性があります
Swiftが同一のものに対する 矛盾した2つの宣言を表示した場合に どちらを使うべきかを推測するのではなく 両方をスキップすることがよくあります Xcode 12では これらの競合を検出する上で Clangはより優れたものになっています アップグレードの時に 突然 型やメソッドが消えた場合 それは調査対象になる場合があります
最後に Swiftはいくつかのマクロを インポートしますが すべてではありません ただSpaceKitがトラブルに遭遇する場合です 前には 文字列定数のグループを NS_STRING_ENUMを使用して型に変換しました SpaceKitは実際には このような定数のセットを2つ持っていますので 他のセットにも同じ処理を 渡すことにしようと思います しかし これらはマクロで定義されており しかも問題が存在します そのうちの1つは未解決です どのような問題でしょうか Swiftは書かれる可能性のあるマクロを 必ずしもすべてインポートすることはできません 基本的にマクロは Objective-Cソースコード内の任意の場所で 使用可能なテキストのスニペットにすぎません 同じマクロであっても異なる場所で使用されると 異なったものになる可能性があります そしてSwiftがマクロの使用方法を 理解する方法はありません しかしSwiftはマクロを認識しており このマクロは定数を宣言するために よく使用される特定のパターンと一致します これらのうちの1つを見ると Swift定数としてインポートされています SKCapsuleマクロの最初の3つは 単一の文字列リテラルという Swiftが認識するパターンの1つと一致します そのためSwiftは定数として インポートします しかし4番目のマクロは別のマクロで置換され その後 文字列リテラルを連結します Swiftはマクロ置換の 他のObjective-C機能との相互作用について すべて理解している訳ではありません これにより別のマクロに名前を付けたり マクロに他のものを含ませることができますが “両方とも”ではありません Swiftではコードパターンが認識されないため このマクロをスキップして次に進みます このインポート不可能なマクロを 修正する方法は数多く存在します 最も簡単な方法は文字列リテラルを 他の文字列と同じ方法で生じさせることでしょう しかしこれらをstring-enumのケースに 変換する場合は 実際の定数に変換することをお望めします その後これらをSKRocket定数と 同じように文字列列挙して それぞれの方法で進められます
この時点で あなたは自分の型を強化し 正しくないコードを修正しました 可視化される必要があるものを すべて確認しました
ここからが楽しいパートです フレームワークを改善し Swiftクライアントを 可能な限り素敵なものにします 名前付けから始めます Swiftのmethod naming規則は Objective-Cとは若干異なります どちらの言語もすべての引数が ラベル付けされている場合 比較的長い名前を使用します しかしSwiftの名前は それまでよりも短くなる傾向があり また型を見て明らかな情報は 省略される傾向があります 2つの言語には技術的な 相違点もあります Swiftの場合は各メソッドに ベース名が付けられていますが デフォルトではすべての引数に ラベルが付けられています Objective-Cセレクタは基本的に 別のベース名がない引数ラベルが 付けられています 従ってベース名に含まれる情報は 最初の引数ラベルに含まれています
このような相違点を支援するために SwiftではObjective-Cメソッドを インポートする時に自動的に名前が変更されます これにより型名と一致する接頭辞と 接尾辞が取り除かれ そしてベース名および引数ラベルにセレクタの 最初の部分を分割する方法を理解するため 英語の文法と語彙の表が 使用されています 通常かなりよい結果になりますが 基本的に審美的な判断を下している コンピュータプログラムとなっています そのため時折 あなたとは 反対の判断を下すでしょう 例えば多くの開発者は このメソッドセレクタが 正しく分割されていないと言うかもしれません “flown”という単語はベース名ではなく 引数ラベルの一部である必要があります これはメソッドが以前のミッションの リストをフェッチし そのミッションが特定の宇宙飛行士によって 飛ばされたミッションであるためです すべての開発者が同意するとは限りません 個人的意見です この問題を解決するには メソッドの後にマクロNS_SWIFT_NAMEを付けて これにベース名と引数ラベルを渡します メソッドを呼び出さずに参照しようとした場合は Swiftで記述する方法が表示されます Swiftはそれ自体を生成する名前ではなく 指定された名前でメソッドをインポートします しかしNS_SWIFT_NAMEは 方法であるだけではありません それはほとんど何にでも 適用することができるのです
例えばこの列挙型を取ります Swiftはすでにこれを使用して かなりよい仕事をしています オーサーがNS_ENUMを使用したため Swift列挙型としてインポートします しかし名前を少し調整したい場合は NS_SWIFT_NAMEを使用して 調整することができます さて 名前からSKプレフィックスを 削除するために NS_SWIFT_NAMEを使用できました しかし実際にはお勧めしません Objective-C型の名前の多くは フレームワークのプレフィックスを “クエリ”や“レコード”のような単語と 組み合わせたものです プレフィックスを削除し 失われた精度を補足するため 名前に他の何かを追加します
型の場合に最もよく使われるのは それらを入れ子にすることです 例えば このSKFuelKind列挙型が連携した SKFuelKindクラスが存在した場合は SKFuel.Kindに変更することができます これはSwiftで呼び出す型である 可能性が高くなります
もう1つのいい用途は 型名の外観が Swift型とは全く異なるライブラリでの使用です 小文字の型名に似ており Cライブラリで時々見るものです NS_SWIFT_NAMEは グローバル定数 変数 関数でも使用できます 例えばNS_SWIFT_NAMEを SKFuelKindToString機能へ適用できます その名前から余分な情報を削除する以外に 引数ラベルやObjective-Cが 関数をサポートしているものも 追加する目的があります
しかし実際に 興味深い領域に入りました グローバル定数や変数 特にグローバル関数に適用されると NS_SWIFT_NAMEは 驚くべき強大な力を受け取り クライアントがあなたのフレームワークの 使用方法を劇的に再構築できます はじめに型のObjective-C名を指定し その後にドットを付け 次にObjective-Cの名前を入力して グローバル関数を静的メソッドに変換します
その後引数ラベルの1つを “self”に変更する代わりに インスタンスメソッドに変えます するとSwiftで呼び出された インスタンスを渡す場所が理解されます
そしてそのメソッドの前に “getter”を置くことで そのメソッドをプロパティに変換できます 変更可能なプロパティを作成するために セッターでも同様のことを行うことができます
これらのテクニックを 完全な機能のフレームワーク全体に適用し APIサーフェス全体を 劇的に再形成することができます Core GraphicをObjective-Cと Swiftの両方で使用する時 私の言ったことが分かると思います この名前変更機能を 無数のグローバル関数に適用して それらをはるかに使い勝手のよいメソッド プロパティそしてイニシャライザに変換しました
さて ここでNS_SWIFT_NAMEで できないことがあるのか疑問に思うでしょう 確かにあります このグローバル関数を“description”という インスタンスプロパティに名前を変えた場合でも 型をCustomStringConvertibleプロトコルに 一致させる目的でNS_SWIFT_NAMEは使えません Swiftではプロパティを使用して SKFuel.Kindsを文字列に変換します しかしカスタムSwiftコードの1行を使用し その適合性を強化できます では やってみましょう
追加する必要がある コードというのはこれです Fuel.Kindに拡張子を書きます そしてこれをCustomStringConvertibleに 対して一致させます そして私のObjective-Cヘッダーから すでにNS_SWIFT_NAMEを使用して 説明プロパティを提供していたので…
これで完了です ステップ3はありません 私はカスタムSwiftコードの 非常に簡単な使用方法を実証しましたが このようなSwiftファイルで必要となる Swift限定のAPIを書くことができます 例えばSwiftUIをインポートし フレームワークのAppKitビューまたは UIKitビューによる SwiftUIビューが記述できます もしくはコンプリートハンドラのようなものを 使用するAPIを取り 代わりにCombine Futureを 返すラッパーを書くことができます Swiftのクラスと型でこれらのテクノロジーの 使用についてのセッションはすべて提供しました ただし混合言語のフレームワークで使用した場合 実際には何も変わりません なのでクラッシュコースを提供するのではなく SwiftUIとコンバインの関連セッションを示して 残りはあなた次第となります 最後にエラーコード列挙型について 説明しましょう
多くのフレームワークと同様に SpaceKitではNSエラーで使用できる カスタムエラーコードを必要とします 他のフレームワークのエラーと衝突しないように SpaceKitではエラードメインと 特定のエラーコードNS_ENUMを含む 文字列定数が宣言されます さて この生成された インターフェイスをご覧になったら 何が問題なのかと 思われるかもしれません あなたは文字列定数を宣言し 文字列定数はそれ自体として入ってきました あなたはNS_ENUMを宣言し すべてのケースとコードがそのままの状態で Swift列挙型として入り その名前も正しく短縮されました 本当にインターフェイス自体には 問題はありません
しかし実際に使う方法を考えると 問題はより明確になります 例えば打ち上げられたミッションが 中止された場合は レスキュー部隊が宇宙飛行士を 救出するのを確認したいはずです これはざっと見るとこのように見えます あなたはmission.launchを呼び出し それがlaunchAbortedのエラーであれば エラーをキャッチしたいと思います しかしそのcatch句は 実際にはどのように見えるのでしょうか さて エラーからドメインと コードを抽出する必要があり まずNSErrorとして キャッチする必要があります
次にエラーがSKErrorDomainにあり 別のエラーコードに同じエラーコードを 使用する可能性があるドメインではないことを 確認する必要があります
次にエラーコードをintから SKErrorCodeに変換します そして最後にそれが 目的とするケースかどうかを確認します すべてのチェック事項を見てみましょう これは… 私がユーザーに書いてほしい ことではないですね
エラーに対して一致させるために このような努力は必要ありません
SwiftでSpaceKitを書いており Swiftエラー列挙型である場合 これよりも少ない行で同じことができます エラーの種類とケースに名前を付ければ Swiftでは投げられたエラーに対して これらが一致させられます これはより優れた方法です ただし もちろんあなたは Swiftエラー列挙型を保持していません Objective-C列挙型と エラードメイン定数を持っています これをSwiftエラー列挙型と同じようなものに 変えることができるのでしょうか? 可能です 本当に簡単といえます しなければならないことは NS_ENUMをNS_ERROR_ENUMと 置き換えることだけです その後エラードメイン定数を使用して raw typeを置き換えます
このことはエラーコード列挙型に対して 大きな効果があります SKErrorCodeはSKError内にネストされており Swiftで作られた全く新しい型になります
Xcode生成インターフェイスでは 列挙型にそのケースとして すべてのエラーコードが 含まれていることが分かります また これらは構造体上の静的定数として 繰り返されており ドメインの場合も このような静的定数があります
しかしSKErrorもエラーに準拠しているので ネイティブのSwiftエラーと同じように投げて キャッチすることができます
イニシャライザ エラーコードのプロパティ ユーザー情報辞書がこれに含まれます エラーコード列挙型には ~=オペレータが存在します これはcase句とcatch句で使用される 一致するオペレータです ここで もしエラーを切り替えたり キャッチしたりしている場合 SKError.Codeと 照合するように言われています マッチングはエラードメインとエラーコードの 両方が正しい場合にのみ成功します SKErrorには静的なコード定数が 含まれているため catch SKError.launchAbortedコードを 記述できます そしてSwiftは 該当するエラーコードに対し エラーを一致させるために このオペレータを使用します SKErrorのこれらの部分は Swiftコンパイラが合成するため 生成済みのインターフェイスでは 可視化されません ただし使用してもらうのが目的なので 実際にSwiftでかなり上手く動作する Objective-Cエラーコードを作ります すべてを取得するために唯一必要なのは 2つの識別子を変更することです 1行の差分としては悪くありません
しかしエラーコードから始めたので もう一度 より大きな教訓について 考えてみたいと思います 生成済みのインターフェイスには 問題発生を示す明白な兆候は見られません 改善の余地があったのは SKErrorCodeの活用方法を 検討した時のみなのです このセッションを通じて 皆様に生成された インターフェイスをご覧いただいています Swiftでヘッダーがインポートされる仕組みを 確認するいい方法だからです しかし生成済みのインターフェイスは フレームワークの内容を 理解するためのツールにすぎません 本当に重要なのは それを使用する時に クライアントが書き込む呼び出しです そのためフレームワークに取り組んでいる時に 生成済みのインターフェイスを確認し 使用サイトのことを 念頭に置く必要があります 心の中で想像してみてください ホワイトボードに走り書きします プレイコートでいじくり回し テストで体系化します フレームワークに対する クライアントの好みは コマンドをクリックして表示される インターフェイスではなく クライアントが書くコードに 基づくものです
まとめに入ります
Objective-Cフレームワークをお持ちであれば ヘッダー注釈のみ または Swiftコードを使用することで Swiftクライアントに対して 上手く機能します
これを実行している時は より強調した具体的な型を― Swiftクライアントに掲示する 機会を探してみてください Swiftでフレームワークが 正しく使用されるように Objective-Cの規則を遵守してください
そしてXcodeは生成済みのインターフェイスを 表示してはいますが APIの用途を振り返ってみてください そうすれば設計が推進されます
詳細は Swiftの仕様書の“Language Interoperability”セクションをご覧ください 今回の機能のすべてを より詳細に説明しています もっと具体的な感触を得て Swift APIを“こなれた”ものにするには Swift コアチームおよびコミュニティが推薦する 原則や規則の一部を紹介している― “API Design Guidelines”を ご参照ください そしてセッション“Behind the Scenes of the Xcode Build Process”では― SwiftがObjective-Cヘッダーを インポートする方法と またXcodeが混合フレームワークを 構築する方法について詳細を説明しています SwiftにObjective-Cフレームワークの ヘッダーの一部が足りないように見える場合や インポートしそうにない場合は 特に関連しています お時間をいただき ありがとうございます 皆様のローンチがすばらしいものになる お手伝いになればと思っております
-
-
4:43 - Describe nullability to control optionals (method and property annotations)
// // SKMission.h // // View the generated interface to see how Swift imports this header. // #import <Foundation/Foundation.h> @interface SKMission : NSObject @property (readonly, nullable) NSString *name; - (nonnull instancetype)initWithName:(nullable NSString *)name; @end
-
6:53 - Describe nullability to control optionals (ASSUME_NONNULL blocks)
// // SKMission.h // // View the generated interface to see how Swift imports this header. // #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface SKMission : NSObject @property (readonly, nullable) NSString *name; - (instancetype)initWithName:(nullable NSString *)name; @end NS_ASSUME_NONNULL_END
-
7:14 - Describe nullability to control optionals (qualifiers)
// // Misc.h // // View the generated interface to see how Swift imports this header. // #import <Foundation/Foundation.h> NSString * _Nonnull const SKRocketSaturnV; @interface ResourceValueContainer : NSObject - (BOOL)getResourceValue:(id _Nullable * _Nonnull)outValue error:(NSError**)error; @end
-
8:09 - Finding nullability mistakes with Objective-C tools
// // SKMission.h // #import <Foundation/Foundation.h> @interface SKMission : NSObject @property (strong, nonnull) NSString *rocket; @property (strong, nonnull) NSString *capsule; @end // // SKRocket.h // #import <Foundation/Foundation.h> extern NSString *_Nonnull const SKRocketSaturnV; // // SKMission.m // // Try building this file and then try analyzing it. // #import "SKRocket.h" #import "SKMission.h" @implementation SKMission @end @interface SKMissionConfigurator : NSObject @property (strong, nullable) SKMission *mission; @end @implementation SKMissionConfigurator - (void)testBadUseWithWarning { [self.mission setCapsule:nil]; } - (void)testBadUseWithStaticAnalyzer:(BOOL)missionIsSkylab1 { NSString *capsule = nil; if (!missionIsSkylab1) { capsule = SKCapsuleApolloCSM; } self.mission.capsule = capsule; } @end
-
11:07 - Use Objective-C generics for Foundation types
// // SKAstronaut.h // #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface SKAstronaut : NSObject // Stub declaration @end NS_ASSUME_NONNULL_END // // SKMission.h // // View the generated interface to see how Swift imports this header. // #import <Foundation/Foundation.h> #import <SpaceKit/SKAstronaut.h> NS_ASSUME_NONNULL_BEGIN @interface SKMission : NSObject @property (readonly) NSArray<SKAstronaut *> *crew; @end NS_ASSUME_NONNULL_END
-
11:33 - Use Int for numbers—unsigned types are for bitwise operations
// // SKRocket.h // // View the generated interface to see how Swift imports this header. // #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN NSInteger SKRocketStageCount(NSString *); NS_ASSUME_NONNULL_END // // NSData+xor.h // // View the generated interface to see how Swift imports this header. // #import <Foundation/Foundation.h> @interface NSData (xor) - (void)xorWithByte:(uint8_t)value; @end
-
13:23 - Strengthen stringly-typed constants
// // SKRocket.h // // View the generated interface to see how Swift imports this header. // #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN typedef NSString *SKRocket NS_STRING_ENUM; extern SKRocket const SKRocketAtlas; extern SKRocket const SKRocketTitanII; extern SKRocket const SKRocketSaturnIB; extern SKRocket const SKRocketSaturnV; NSInteger SKRocketStageCount(SKRocket); NS_ASSUME_NONNULL_END
-
15:24 - Specify initializer behavior
// // SKAstronaut.h // // View the generated interface to see how Swift imports this header. // #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface SKAstronaut : NSObject - (instancetype)initWithNameComponents:(NSPersonNameComponents *)name NS_DESIGNATED_INITIALIZER; - (instancetype)initWithName:(NSString *)name; - (instancetype)init NS_UNAVAILABLE; @property (strong, readwrite) NSPersonNameComponents *nameComponents; @property (readonly) NSString *name; @end NS_ASSUME_NONNULL_END // // SKAstronaut.m // #import "SKAstronaut.h" @interface SKAstronaut () @property (class, readonly, strong) NSPersonNameComponentsFormatter *nameFormatter; @end @implementation SKAstronaut - (id)initWithNameComponents:(NSPersonNameComponents *)name { self = [super init]; if (self) { _name = name; } return self; } - (id)initWithName:(NSString *)name { return [self initWithNameComponents:[SKAstronaut _componentsFromName:name]]; } - (id)init { [self doesNotRecognizeSelector:_cmd]; return nil; } - (NSString *)name { return [SKAstronaut.nameFormatter stringFromPersonNameComponents:self.nameComponents]; } + (NSPersonNameComponents*)_componentsFromName:(NSString*)name { return [self.nameFormatter personNameComponentsFromString:name]; } + (NSPersonNameComponentsFormatter *)nameFormatter { static NSPersonNameComponentsFormatter *singleton; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ singleton = [NSPersonNameComponentsFormatter new]; }); return singleton; } @end
-
20:00 - Follow the error handling convention
// // SKMission.h // // View the generated interface to see how Swift imports this header. // #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface SKMission : NSObject /// \returns \c YES if saved; \c NO with non-nil \c *error if failed to save; /// \c NO with nil \c *error` if nothing needed to be saved. - (BOOL)saveToURL:(NSURL *)url error:(NSError **)error NS_SWIFT_NOTHROW DEPRECATED_ATTRIBUTE; /// @param[out] wasDirty If provided, set to \c YES if the file needed to be /// saved or \c NO if there weren’t any changes to save. - (BOOL)saveToURL:(NSURL *)url wasDirty:(nullable BOOL *)wasDirty error:(NSError **)error; @end NS_ASSUME_NONNULL_END
-
22:40 - Refine an Objective-C API for Swift users
// // SKMission.h // // View the generated interface to see how Swift imports this header. // #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface SKMission : NSObject /// \returns \c YES if saved; \c NO with non-nil \c *error if failed to save; /// \c NO with nil \c *error` if nothing needed to be saved. - (BOOL)saveToURL:(NSURL *)url error:(NSError **)error NS_SWIFT_NOTHROW DEPRECATED_ATTRIBUTE; /// @param[out] wasDirty If provided, set to \c YES if the file needed to be /// saved or \c NO if there weren’t any changes to save. - (BOOL)saveToURL:(NSURL *)url wasDirty:(nullable BOOL *)wasDirty error:(NSError **)error NS_REFINED_FOR_SWIFT; @end NS_ASSUME_NONNULL_END // // SwiftExtensions.swift // import Foundation extension SKMission { public func save(to url: URL) throws -> Bool { var wasDirty: ObjCBool = false try self.__save(to: url, wasDirty: &wasDirty) return wasDirty.boolValue } }
-
31:35 - Fix method names with NS_SWIFT_NAME
// // SKMission.h // // View the generated interface to see how Swift imports this header. // #import <Foundation/Foundation.h> #import <SKAstronaut/SKAstronaut.h> NS_ASSUME_NONNULL_BEGIN @interface SKMission : NSObject - (NSSet<SKMission *> *)previousMissionsFlownByAstronaut:(SKAstronaut *)astronaut NS_SWIFT_NAME(previousMissions(flownBy:)); @end
-
33:12 - Rename and rework value types with NS_SWIFT_NAME
// // SKFuelKind.h // // View the generated interface to see how Swift imports this header. // #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface SKFuel : NSObject // Stub class @end typedef NS_ENUM(NSInteger, SKFuelKind) { SKFuelKindH2 = 0, SKFuelKindCH4 = 1, SKFuelKindC12H26 = 2 } NS_SWIFT_NAME(SKFuel.Kind); NSString *SKFuelKindToNSString(SKFuelKind kind) NS_SWIFT_NAME(getter:SKFuelKind.description(self:));
-
35:59 - Add conformances to Objective-C types using custom Swift code
extension SKFuel.Kind: CustomStringConvertible {}
-
37:02 - Improve error code enums
// // SKError.h // SpaceKit // #import <Foundation/Foundation.h> extern NSString *const SKErrorDomain; typedef NS_ERROR_ENUM(SKErrorDomain, SKErrorCode) { SKErrorLaunchAborted = 1, SKErrorLaunchOutOfRange, SKErrorRapidUnscheduledDisassembly, SKErrorNotGoingToSpaceToday };
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。