ObjecTips

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

Swift + Core Data Code Generation で Objective-C からの呼び出しでクラッシュするケース

部分的に Swift で書き直しているプロジェクトで起きたケース。
.xcdatamodeld のエンティティ設定で Core Data の Code Generation を Objective-C から Swift に変更した後 Objective-C からの呼び出しでクラッシュ

まず Objective-C と Swift の Core Data Code Generation については以下を参照

koze.hatenablog.jp

Swift ソースを自動生成した場合、class func fetchRequest@nonobjc になっているのに注意。
クラスメソッドとしては以下の様にiOS 10以降で +fetchRequest が利用可能になっているが、

// Objective-C
@interface NSManagedObject : NSObject
+ (NSFetchRequest*)fetchRequest API_AVAILABLE(macosx(10.12),ios(10.0),tvos(10.0),watchos(3.0));
@end
// Swift
open class NSManagedObject : NSObject {
    @available(iOS 10.0, *)
    open class func fetchRequest() -> NSFetchRequest<NSFetchRequestResult>
}

呼ばれる実装の実態として自動生成によりサブクラス(モデルクラス)に実装されている fetchRequest メソッドが必要となるため、以下の様に Objective-C でメソッド呼び出しを行った場合に自動生成されたメソッドが呼ばれず意図した NSFetchRequest が取得出来ずクラッシュする模様。

// Objective-C
NSFetchRequest<Model *> *request = [Model fetchRequest];

対応パターン

1.

Objective-C ソースを生成する様に設定し Objective-C と Swift から +fetchRequest を利用する。

// Objective-C
NSFetchRequest<Model *> *request = [Model fetchRequest];
// Swift
let request: NSFetchRequest<Model> = Model.fetchRequest
2.

Swift ソースを生成する様に設定した場合、Objective-C からの呼び出しは
-fetchRequestWithEntityName: メソッドを使いエンティティ名を指定して fetchRequest を作成する様に実装する。

// Objective-C
NSFetchRequest<Model *> *request = [NSFetchRequest fetchRequestWithEntityName:@"Model"];
// Swift
let request: NSFetchRequest<Model> = Model.fetchRequest
3.

Swift ソースを生成する様に設定し Swift からしか呼び出さない様にする。

// Swift
let request: NSFetchRequest<Model> = Model.fetchRequest

Swift への移行が進めば3番になっていくかも知れないがまずは1番か2番だろう。