仕事で外部のObjective-Cライブラリを使う必要があり、Swiftと連携していたのですが、SwiftのクラスをObjective-Cで使うのって大変なんですね。
色々と地雷を踏み倒したのでメモしとしきます。
本記事は、特に「引数・戻り値にSwiftクラスを使いたい(=インスタンスへのポインタを渡して、中身をいじりたい)」という方向けです。(意外と参考記事が無くて苦労したので)
参考記事に無かった情報を中心に書きます。
###想定シーン
- Objective-Cのライブラリを(今更)使いたい
- ライブラリをカスタマイズするにあたり、Swiftで生成したインスタンスをObjective-Cライブラリ内で使いたい
- 特に、引数・戻り値にSwiftのクラスを含めたい(インスタンスへのポインタを渡して、中身をいじりたい)場合
###参考記事
SwiftとObjective-Cの相互利用する際の注意
Objective-CベースのプロジェクトからSwiftのクラスを呼び出す
Objective-CコードからSwiftのクラス/プロトコルを使う方法(フレームワーク開発編)
#チュートリアル形式での解説
Swiftで作ったバケツクラスのインスタンスに、Objective-Cの処理の中でランダムな整数を入れていきます。
##バケツSwiftクラスを作成する
import Foundation
//数字をためていくためのバケツクラス
import Foundation
@objcMembers
class Bucket:NSObject {
override init() {
}
var content = [Int]()
func addIntegerToContent(integer:Int){
self.content.append(integer)
return
}
}
###気をつけるポイント1
- Swift4からは@objcMembersと書くことで全体に@objcが適用されます
- NSObjectを継承しましょう
- 一旦ビルドします
##ランダムな整数をバケツに追加するObjective-Cファイル
#import <Foundation/Foundation.h>
#import "test-SwiftObjc-Bridging-Header.h"
#import "test_SwiftObjc-Swift.h"
@implementation AddInteger
-(void) random:(Bucket *)bucket {
// 受け取ったバケツにランダムな整数を2回突っ込む
[bucket addIntegerToContentWithInteger:arc4random()];
[bucket addIntegerToContentWithInteger:arc4random()];
// バケツの中身を一旦表示
NSLog(@"%@",bucket.content);
return;
}
@end
###気をつけるポイント2
1.ブリッジヘッダーのインポート
Objective-CをNewFileから作成した際に自動生成されたブリッジヘッダーをインポートします。
もしブリッジヘッダーを作り忘れていたら、自分で指定できます。 (参考:Objective-cからSwiftを呼び出す方法)
2.Swiftヘッダーのインポートする
Objective-CからSwiftを参照する際に使用するSwiftファイル全体のヘッダーで、ビルド時に自動で生成されます。
(バケツクラス作成後に一旦ビルドしていれば表面上は見えませんが、生成されています)
・通常はファイル名が<Product Module Name>-Swift.h
になります。
・ブリッジヘッダーではなく、.mファイルにインポートします。
ここでも私は見事に地雷を踏んだのですが、プロジェクト名にアルファベット以外の文字を使っている場合はアンダースコアに(勝手に)変換されてしまいます。
私の場合は、test-SwiftObjc
というようなプロジェクト名だったのですが、test_SwiftObjc-Swift.h
のように変換されていて、not found
に陥ってしまいました。。
「BuildSettigs->Packaging->Product Module Name」でSwiftヘッダー名を確認しましょう。
###ブリッジヘッダーの中身
#import <Foundation/Foundation.h>
@class Bucket;
@interface AddInteger : NSObject
- (void) random: (Bucket *)bucket ;
@end
##バケツを作ってObjective-Cに渡す
import UIKit
class ViewController: UIViewController {
// Swiftクラスからバケツの生成
let bucket1 = Bucket()
override func viewDidLoad() {
super.viewDidLoad()
// Objective-Cのインスタンスを生成
let addInteger = AddInteger()
// randomメソッドにバケツのポインタを参照渡し
addInteger.random(bucket1)
// バケツの中身の表示
print(bucket1.content)
}
}
###print結果
// Objective-C内でランダムな整数をバケツに突っ込んだときの値
2018-07-17 16:09:47.446236+0900 test-SwiftObjc[2666:1347277] (
2251345403,
1753865346
)
// Swiftに帰ってきた時のバケツの中身
[2251345403, 1753865346]
ちゃんとswiftで生成したバケツにランダムな整数が追加されていることが確認できました。
最大のポイント
私の場合、ポイント1の3に書いた、「Swiftクラス(バケツ)を作成したあとに、一旦ビルドする必要がある」と気づくのことに一番時間がかかりました。
これに気づかずに半日以上潰しました。。。
一旦ビルドしてSwiftのヘッダーファイルを作成しないと、Objective-Cファイルを書く際にメソッド等が認識されず、下記のようなエラーがでます。
No visible @interface for 'Bucket' declares the selector 'addIntegerToContentWithInteger:'
ビルドすると、下記のようにちゃんと自動予測変換がでてきます。
書き起こしてみると本当にささいなことですね。
次にまた悩む人がいないように、ここで書き残しておきます。
Swift初心者なので、アドバイスがございましたら是非コメントください。
以上、ニッチすぎるけど、万が一Objective-CとSwiftを連携することになった場合のポイントでした。