async/await への自動変換
System Framework以外でも自動変換が行われるケースに遭遇したけど自動変換されるものとされないものの違いが分からなかったので調査。
Xcode 13 Release Notes には以下のように記載がある。 https://developer.apple.com/documentation/xcode-release-notes/xcode-13-release-notes
Swift translates an Obj-C method that delivers its results asynchronously via a completion handler into an async method that directly returns the result (or throws). (78028295)
For example, Swift translates the following Objective-C method from CloudKit:
- (void)fetchShareParticipantWithUserRecordID:(CKRecordID *)userRecordID completionHandler:(void (^)(CKShareParticipant * _Nullable, NSError * _Nullable))completionHandler;
completion handler で非同期な結果を配信するObjective-Cメソッドと記載がある。
調査
調査環境 - iMac (24-inch, M1, 2021), macOS Monterey 12.3.1, Xcode 13.3.1
サンプルプロジェクトを作って確認。
- プロジェクトテンプレートからiOSアプリのSwift指定でプロジェクトを作成
- Objective-Cのクラスファイルを作成して completion handler を使ったメソッドを実装
#import <Foundation/Foundation.h> @interface MyObject : NSObject - (void)myMethod:(void (^)(NSString * _Nullable, NSError * _Nullable))compleitonHandler; @end
- Bridging-Header.h ファイルで
#import "MyObject.h"
をしてSwiftから呼び出せるようにする - Swiftから
MyObject().my
まで入力して Xcode の autocompletion を表示
asyncへの自動変換は行われなかった。
と次の手を考えている時にメソッド名の typo に気付く。
compleitonHandler
を completionHandler
に修正。
- (void)myMethod:(void (^)(NSString * _Nullable, NSError * _Nullable))completionHandler;
すると!
変換された!
引数名が completionHandler
でないといけない!?
そこで引数名を completion
にしてみる
OK
引数名を completionBlock
にしてみる
OK
引数名を handler
にしてみる
NG
あれ?最初が completion
だったらなんでもいけるんじゃないのか?
引数名を completionFoo
にしてみる
NG
何でもはダメなのか、、
引数名を block
にしてみる
NG
とりあえず completionHandler
completionBlock
completion
の3つはOKという事は分かった。
Nullabilityの調査
少し意地悪をして返り値とエラーの両方が _Nonnull
だったら?
- (void)myMethod:(void (^)(NSString * _Nonnull, NSError * _Nonnull))completionHandler;
エラー無しで tuple で2つの値を返すasyncメソッドに変換された。
_Nullable
を書いていない場合
- (void)myMethod:(void (^)(NSString *, NSError *))completionHandler;
この場合も先ほどと同じく tuple
という事で Nullability を適切に記載しているというのも条件になる。
Swiftコードの調査
Objective-Cの実装を削除してSwiftのclassを作成する
class MyObject { }
メソッドを実装
func myMethod(_ completionHandler: @escaping (String?, Error?) -> Void) { }
これを呼び出してみる NG
@objc
を付けてみる
@objc func myMethod(_ completionHandler: @escaping (String?, Error?) -> Void) { }
NG
NSObject
を継承してみる
class MyObject: NSObject {
NG
NSString
NSError
にしてみる
@objc func myMethod(_ completionHandler: @escaping (NSString?, NSError?) -> Void) { }
NG
Resultにしてみる
func myMethod(_ completionHandler: @escaping (Result<String, Error>) -> Void) { }
NG
ドキュメントにはObjective-Cメソッドと記載があるので、Swiftでの実装だろうと @objc
宣言でObjective-Cメソッドとして扱えば自動変換してくれそうに思えるけどSwiftで実装されたものは自動変換してくれないみたい。Swiftコードのasync/awaitへの自動変換があると移行に面倒がなくて良いんだけど残念。
async/await への自動変換の条件まとめ
- Objective-Cで書かれている
- Nullabilityが正しく設定されている
- 引数名は
completionHandler
,completionBlock
,completion
のいずれか