ObjecTips

Swift & Objective-C で iOS とか macOS とか

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 に気付く。
compleitonHandlercompletionHandler に修正。

- (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 のいずれか