search
LoginSignup
11

More than 3 years have passed since last update.

posted at

updated at

どーーーしても、Objective-CからSwiftのインスタンスをいじりたい場合に気をつけること

仕事で外部のObjective-Cライブラリを使う必要があり、Swiftと連携していたのですが、SwiftのクラスをObjective-Cで使うのって大変なんですね。
色々と地雷を踏み倒したのでメモしとしきます。

本記事は、特に「引数・戻り値にSwiftクラスを使いたい(=インスタンスへのポインタを渡して、中身をいじりたい)」という方向けです。(意外と参考記事が無くて苦労したので)
参考記事に無かった情報を中心に書きます。

想定シーン

  • Objective-Cのライブラリを(今更)使いたい
  • ライブラリをカスタマイズするにあたり、Swiftで生成したインスタンスをObjective-Cライブラリ内で使いたい
  • 特に、引数・戻り値にSwiftのクラスを含めたい(インスタンスへのポインタを渡して、中身をいじりたい)場合

参考記事

SwiftとObjective-Cの相互利用する際の注意
Objective-CベースのプロジェクトからSwiftのクラスを呼び出す
Objective-CコードからSwiftのクラス/プロトコルを使う方法(フレームワーク開発編)

チュートリアル形式での解説

Swiftで作ったバケツ:wastebasket:クラスのインスタンスに、Objective-Cの処理の中でランダムな整数を入れていきます。

バケツSwiftクラスを作成する

BucketClass.swift
import Foundation

//数字をためていくためのバケツクラス

import Foundation

@objcMembers
class Bucket:NSObject {

    override init() {
    }

    var content = [Int]()

    func addIntegerToContent(integer:Int){

        self.content.append(integer)

        return
    }
}

気をつけるポイント1

  1. Swift4からは@objcMembersと書くことで全体に@objcが適用されます
  2. NSObjectを継承しましょう
  3. 一旦ビルドします

ランダムな整数をバケツに追加するObjective-Cファイル

AddInteger.m
#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ヘッダー名を確認しましょう。

ブリッジヘッダーの中身

test-SwiftObjc-Bridging-Header.h
#import <Foundation/Foundation.h>

@class Bucket;

@interface AddInteger : NSObject
- (void) random: (Bucket *)bucket ;
@end

 

バケツを作ってObjective-Cに渡す

ViewController.swift
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を連携することになった場合のポイントでした。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
11