1. 程式人生 > >詳解swift和OC以及C語言的混編(不看後悔!)

詳解swift和OC以及C語言的混編(不看後悔!)

前言:

       Swift 語言出來後,可能新的專案直接使用swift來開發,但可能在過程中會遇到一些情況,某些已用OC寫好的類或封裝好的模組,不想再在swift 中再寫一次,或者有一些第三方使用OC寫的,沒有swift版本,怎麼辦?那就使用混編。這個在IOS8後是允許的.

       先簡單的入手,先研究在同一個工程目錄下混合使用的情況.這裡主要介紹swift類中呼叫OC方法和swift類中呼叫C函式以及OC類中呼叫swift的函式這三種類型的混編.另外,小編也是邊研究邊嘗試才做出結果的,因此命名並非規範命名,大家就不要糾結命名問題了.小編這裡使用swift建立的工程,工程名為SwiftTest

.(其實用OC建立工程也大同小異)

準備內容:

1.建立swift工程,工程名SwiftTest

2.建立一個swift的類

3.建立一個OC的類

4.建立兩個C語言的類(一個包含標頭檔案,另一個不包含標頭檔案)

建立結果如下圖:


接下來,先說一下,建立過程中的情況:

1.建立swift類,可以用快捷鍵 command+n

建立swift類的時候有兩種方式,如下圖


注意:選綠框中這兩個地方都可以建立swift類,語言選Swift,然後注意,一定要繼承於NSObject,這個非常重要,否則在OC中不做修改調不到這個類的方法,就比較麻煩了,還是一步到位,繼承NSObject吧.另外還要注意紅框的位置,建立時一定要手動選擇紅框中這一項(iOS的Source),別用預設的,預設的是(OS X 的Source),後邊會講為什麼.


然後建立成功就是這樣的了


這個是選擇iOS 的 Source ,然後用Cocoa Touch Class 建立的,如果是用Swift File 建立的類,那上面圖片的綠框中就是 import  Foundation 了,這個還好,影響不大(個人建議用Cocoa Touch Class,因為它匯入的UIKit是包含Foundation的,當然還是看你的喜好了).但是如果你用的是預設的 OS X 的 Source,然後用Cocoa Class建立(Cocoa Touch Class他倆圖示是一樣的,不看名字還真沒看出來他倆有區別),那上面綠框中就是import cocoa,並且混編的時候會報錯.

我上兩個圖(左邊預設,右邊選擇後的),大家就明白為什麼會範這種錯誤了


2.建立OC類

        這個不說怎麼建立了,都會吧!但有一點得說,那就是,在swift工程中,不再使用標頭檔案和.m檔案的方式了。所以也不需要使用import ""來匯入標頭檔案。那swift 如何能訪問到OC的類宣告呢?其實,swift也是需要使用標頭檔案進行訪問的,只不過不再需要使用顯式的方式使用import進行匯入。有兩種方式來實現這個標頭檔案的生成。

       方式一:在一個全新的Swift,利用第一次新建提示的方式自動新增橋接標頭檔案。

這個是在swift專案中,建立其他語言類的時候(OC,C等),系統會提示你新增一個橋接標頭檔案,如圖


然後點選藍色那個按鈕,就會生成一個橋接標頭檔案,這個檔案的格式為"你的工程名字-Bridging-Header.h",如圖中綠框所示


        有的可能是xcode配置問題,沒有提示,那也可以自己建立一個,格式得按照以上的格式,但還有一種方式,不僅能建立還可以改變這個格式,取一個自己喜歡的檔名,但需要修改一些配置.

方式二:新建一個頭檔案,名為:JeckHeader.h

        在targets->build settings ->Object-C Bridging Header 位置設為Swift/JeckHeader.h,如下圖所示,這個標頭檔案也就是橋接標頭檔案,程式碼一會兒再說.


3.建立C語言類

這裡有一個需要注意的地方,建立C語言的類,和建立OC類差不多,如圖選擇C File 建立就好了


  但是,點選Next會出現下圖介面,看到那個藍色的"√"沒有,加上√,建立的C語言的類,類似OC,會有一組兩個檔案,一個是.c檔案一個是.h檔案,.h檔案就是這個C語言的標頭檔案,如果取消√,建立的C語言的類是沒有標頭檔案的.為方便學習,我把含標頭檔案的和不含標頭檔案的類,都分別建立了,後邊程式碼中會分別介紹他們怎麼用.


 到這裡,我們的準備工作做完了,接下來,結合程式碼,來研究一下,swift呼叫OC裡的方法,swift呼叫C語言的函式,OC呼叫swift函式,OC呼叫C語言的函式這幾種情況,如果前邊的準備工作做好了,那接下來會很容易理解.

然後結合程式碼講解比較直觀:

//  SwiftClass.swift 類中的程式碼,這裡邊只是添加了一個函式,OC的類會呼叫這個方法

import UIKit

class SwiftClass: NSObject {
    func sayHello(name:String) -> String {
        let greeting = "Hello" + name + "!"
        return greeting
    }

}
//  OCClass.m  OC的.m檔案,這裡實現了兩個方法並定義了一個C語言的函式,為了方便對比,方法裡實現了block,在這個類中演示:OC呼叫swift類中的方法

#import "OCClass.h"

#import "SwiftTest-swift.h"//細心的朋友一定注意到了,專案檔案中並沒有這個標頭檔案,但實際上專案中是有的,你也可以用command+滑鼠左鍵跳進去檢視,是隱藏的,如果你是按照我前邊的講的建立的swift檔案,那你在這裡是可以匯入這個標頭檔案的,格式為"工程名-swift.h",它就是專案中所有的swift類的標頭檔案.

@implementation OCClass

-(void)desc22{
    //宣告block
    int (^p)(int, int);
    //把函式賦值給block
    p = ^(int a, int b){
        return a + b;
    };
    //使用
    int result = p(10,40);
    NSLog(@"swift呼叫OC方法輸出result:%d\n",result);
    //OC中呼叫swift函式
    SwiftClass *sc = [[SwiftClass alloc] init];//建立swift物件
    NSString *str =[sc sayHello:@"jeck"];//用swift的物件呼叫自己的函式(方法)

    NSLog(@"OC中呼叫swift函式輸出 %@",str);    
}
//定義函式
int sum2(int a, int b){
    return a + b;
}

-(void)desc2{
    //2.宣告block
    int(^p)(int, int);
    
    //3.把函式賦值給block
    //p = sum2;
    p = ^(int a, int b){
        return a + b;
    };
    
    //4.使用
    int result = p(10,40);
    printf("swift呼叫OC方法輸出result:%d\n",result);
}
//  OCClass.h OC的標頭檔案,聲明瞭.m中的兩個方法和一個C語言函式,為了能被外界呼叫到

#import <Foundation/Foundation.h>

@interface OCClass : NSObject

int sum2(int a, int b);
-(void)desc22;
-(void)desc2;

@end
//  CClass.c  C語言類的.c檔案,定義了兩個函式

#include "CClass.h"
//1.定義函式
int sum3(int a, int b)
{
    return a+b;
}

void desc3(){
    //2.宣告函式指標
    int (*p)(int, int);
    
    //3.函式指標指向函式
    p = sum3;
    
    //4.使用
    int result = p(10,10);
    
    printf("swift呼叫有標頭檔案的C函式輸出:%d\n",result);
    
}
//  CClass.h  C語言類的標頭檔案,聲明瞭兩個函式,作用同OC,方便外界呼叫

#ifndef CClass_h
#define CClass_h

#include <stdio.h>
//和OC中類似,在C的標頭檔案中宣告兩個函式
int sum3(int a, int b);
void desc3();
#endif /* CClass_h */
//  CClassNo.c  這個類是沒有標頭檔案的c語言的類,實現了兩個函式

#include <stdio.h>
//1.定義函式
int sum1(int a, int b)
{
    return a+b;
}

void desc1(){
    //2.宣告函式指標
    int (*p)(int, int);
    
    //3.函式指標指向函式
    p = sum1;
    
    //4.使用
    int result = p(10,20);
    
    printf("swift呼叫C函式輸出result:%d\n",result);
}
//橋接標頭檔案SwiftTest-Bridging-Header.h
//匯入C類
#import "CClass.h"
//匯入OC類
#import "OCClass.h"
//宣告沒有標頭檔案的C語言類中的函式
void desc1();
int sum1(int a, int b);
//  ViewController.swift  這個是建立工程的時候,系統自帶的那個swift類,在這裡演示:swift呼叫OC方法,swift呼叫C方法

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        //swift呼叫oc方法
        let funOC = OCClass()
        funOC.desc2()
        funOC.desc22()
        let funOCClass2 = sum2(10, 1)
        print("swift呼叫OC類中的C函式輸出:\(funOCClass2)")
        
        //swift呼叫c函式(無標頭檔案)
        desc1()
        let funcCClassss = sum1(10, 2)
        print("swift呼叫沒有標頭檔案的C語言類輸出:\(funcCClassss)")//12
        
        //swift呼叫c函式(有標頭檔案)
        desc3()
        let funcCClass33 = sum3(10, 3)
        print("swift呼叫含有標頭檔案的C語言類輸出:\(funcCClass33)")
    }

到這裡,就已經彙編成功了,下面是執行的結果

swift呼叫OC方法輸出result:50

2016-05-26 15:31:00.791 SwiftTest[2962:140487] swift呼叫OC方法輸出result:50

2016-05-26 15:31:00.807 SwiftTest[2962:140487] OC呼叫swift函式輸出 Hellojeck!

swift呼叫OC類中的C函式輸出:11

swift呼叫C函式輸出result:30

swift呼叫沒有標頭檔案的C語言類輸出:12

swift呼叫有標頭檔案的C函式輸出:20

swift呼叫含有標頭檔案的C語言類輸出:13


最後,還得要強調一下:

1.Swift呼叫OC的方法,關鍵是橋接標頭檔案,這個必須建立正確並且配置正確,然後把你想要呼叫的OC或者C的標頭檔案(沒有標頭檔案也要宣告函式)匯入到橋接標頭檔案裡,Swift才能正常呼叫OC和C;

2.在OC中要想使用某個類,必須有標頭檔案,而swift檔案卻沒有標頭檔案,所在咱們想必也需要產生一個頭檔案,但對於OC呼叫swift  的標頭檔案比較特殊.因標頭檔案裡面的機制是自動生成的,不建議手寫.(注意:系統設定的標頭檔案,在工程中是看不到的.)

3.其實,可以選中targets->build settings ->packaging->Product Module Name, 在這裡檢視和設定模組名,這個名稱很重要 swift 的標頭檔案就是根據這個來命名的。(我的圖片為啥上傳不了了,我借幾張圖說明一下吧)



雖然你看圖中有這個import "SwiftModule-swift.h"但你在整個工程中是找不到這個檔案的,但可以使用CMD+ 滑鼠點選可看這個標頭檔案中的內容。




雖然你看圖中有這個import "SwiftModule-swift.h"但你在整個工程中是找不到這個檔案的,但可以使用CMD+ 滑鼠點選可看這個標頭檔案中的內容。


注:

凡是用Swift寫的類,如果不繼成自NSObject或NSObject 的派生類,哪麼編譯後將不會生成對應的轉換類。從而使得OC 中找不到相應的宣告。

如我的例子中 class Act 這樣不會被編譯到SwiftModule-swift.h中,但寫為 class Act : NSObject,就可以編譯出相應的宣告。另外可以使用@objc加以宣告,但這個還是一樣,類最好繼承NSObject下來。就像下面:

import Foundation  
  
@objc(Act)  
  
class Act   
{  
    func hasAct(tag:Int) -> String  
    {  
        switch (tag)  
        {  
        case 1:return "Movie"  
        case 2:return "CCTV"  
        case 3:return "Sport TV"  
        default:return "Area TV"  
        }  
    }  
  
    @objc(init)//原本以為加上這個alloc就可以找到,但不行的。。。  
    init()  
    {  
        println("act constructor is called.")  
    }  
      
    deinit  
    {  
        println("act destroyed is called.")  
    }  
}  


但是在使用時你就會發現

        act = [[Act alloc]init]; //報錯,找不到alloc,因此建議大家還是繼承NSObject.




雖然你看圖中有這個import "SwiftModule-swift.h"但你在整個工程中是找不到這個檔案的,但可以使用CMD+ 滑鼠點選可看這個標頭檔案中的內容。