Swift與OC混編

為什么要選擇Swift

從2014年蘋果推出Swift1.0到今年9月份的Swift5.1已經(jīng)過去了5年,平均每年一個大版本,半年一個小版本的迭代速度已經(jīng)讓Swift語言日臻完善。從TIOBE指數(shù)來看,今年Swift站在了排行榜的第10位,11月份占有率為1.49%;OC已經(jīng)從當年的雙連冠跌落到了今年的第13位。國外Swift的使用早已在流行,不論是WWDC的技術(shù)分享還是Raywenderlich的在線課程都已經(jīng)把語言的使用轉(zhuǎn)為Swift。而國內(nèi)apple開發(fā)者主要使用的語言仍是OC,這主要是因為現(xiàn)有的成熟應用當年是由OC構(gòu)建并且有很深厚的技術(shù)沉淀,擁抱新變化往往會付出沉重的代價。當然也有一些年輕的團隊使用Swift并且取得了很大的成功[例如頭條,美團等]。
蘋果官方是這么宣傳Swift的
用Swift不論在手機、桌面、服務器還是在任何跑代碼的地方編寫軟件都是很奇妙的。它是安全的、快速的、交互式的語言,它結(jié)合了最好的現(xiàn)代語言智慧,這智慧來自于廣泛的蘋果開發(fā)者文化和開源社區(qū)的各種貢獻。編譯器為性能而優(yōu)化,語言為開發(fā)者優(yōu)化,這兩者全然不打折扣。
這描述的有點廣告了。Swift的優(yōu)勢具體來說可以列出以下幾點(相對于OC)

  • Swift更接近自然語言可讀性好,方法名稱更短,語言更現(xiàn)代化,代碼行數(shù)更少
  • Swift不需要區(qū)分頭文件和實現(xiàn)文件,這使項目中創(chuàng)建的文件顯著減少便于管理
  • Swift在處理集合、字符串等基本類型使更加靈活便捷
  • Swift運行速度更快
  • Swift支持動態(tài)庫
  • Swift Playground可以像Python的Notebook一樣成為優(yōu)秀的模擬實驗平臺,SwiftUI像Flutter、HTML一樣用DSL來寫頁面。
  • 開源讓更多的開發(fā)者擁抱Swift,也從社區(qū)吸收了更多的營養(yǎng)
  • 變量總是在使用前就初始化
  • Optional確保nil可以被直接處理
  • Swift還擁有強大的類型推導和模式匹配能力
  • 強大的Enum、Struct、Protocol、Generics讓語言更加靈活,業(yè)務實現(xiàn)更加容易。
  • 在硬件層面上也對Swift編譯和優(yōu)化做了更好的支持,

早在2014年年末Mattt Thompson就在NSHipster上寫了一篇文章"The Death of Cocoa",他說“就像Objective-C一樣,Cocoa將會漸漸消亡。現(xiàn)在的問題不是它是否會消亡,而是消亡的時間”。Swift真的能重塑蘋果的標準庫嗎,這只能讓時間來檢驗了。

對于我們當下的開發(fā)者而言完全放棄OC顯然是不明智的,而不去使用新的技術(shù)和語言也是低效缺乏競爭力的。蘋果為開發(fā)者提供了一個好的解決方案:兩種語言并存。使Swift能夠以簡單有效的方法和Objective-C進行互操作是很有戰(zhàn)略意義的決定——同時在一定程度上也是必須的。這樣做將允許團隊中愿意冒險的工程師,以一種低風險的方式將Swift加入到已經(jīng)在運行的代碼中去,這對語言的普及有極大意義。工程師可以一邊探索語言的特性,一邊完善業(yè)務的開發(fā)。接下來我們將逐一探索Swift與OC混編的各種場景。

Swift與OC的混編場景

根據(jù)實踐經(jīng)驗我們可以總結(jié)出以下幾種混編場景

  1. OC&Swift Mixed In The Same Target
    • Project中OC 調(diào)用 Swift
    • Pod中OC 調(diào)用 Swift
    • Project中Swift Project 調(diào)用 OC
    • Pod中OC 調(diào)用 Swift
  2. OC&Swift Mixed In The Different Target
    • OC Project 調(diào)用 Swift Pod
    • Swift Project 調(diào)用 OC Pod
    • OC Pod 調(diào)用 Swift Pod
    • Swift Pod 調(diào)用 OC Pod
      這里把Pod替換為Framework也是一樣的,有時也把Project Target說成APP Target。1是同一target內(nèi)混編,2是target間混編。

主工程里OC&Swift相互調(diào)用

在OC工程里創(chuàng)建一個Swift類會跳出如下彈框


點擊Create Bridging Header按鈕生成橋接文件MainProject-Bridging-Header.hMainProject-Swift, MainProject是工程名稱。同時會在Build Settings里添加Swift Complier配置:

如果沒有點擊Create Bridging Header按鈕,而是點擊了Don't create,那么將不會創(chuàng)建橋接文件,但是Build Settings里添加Swift Complier配置項,只是該配置項里沒有Bridging Header,這時需要開發(fā)者自行創(chuàng)建橋接文件,并配置好路徑。

OC調(diào)用Swift接口
  1. Swift類里的接口應該加上@objc修飾
class Dog: NSObject {
    
    @objc let legNumber = 4
    var temper = "temper-good"
    @objc var friend = Cat()

    @objc func eat() {
        print("The dog is eating")
    }
}
  1. 在OC類中首先要引用MainProject-Swift.h
#import "MainProject-Swift.h"
  1. 最后就可以在OC類里引用Swift類
#import "MainProject-Swift.h"

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    Dog *dog = [[Dog alloc] init];
    [dog eat];
}
@end
  1. 結(jié)論
    我們再看看MainProject-Swift.h到底是什么。MainProject-Swift.h是一個自動生成的頭文件,工程里沒有對應的實體文件,可以Command+LeftClick點擊MainProject-Swift.h進去看到其中有一段
@class Cat;

SWIFT_CLASS("_TtC11MainProject3Dog")
@interface Dog : NSObject
@property (nonatomic, readonly) NSInteger legNumber;      // @objc let legNumber = 4
@property (nonatomic, strong, getter=friend, setter=setFriend:) Cat * _Nonnull friend_;  //@objc var friend = Cat()
- (void)eat;                  //@objc func eat()
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end

Cat這個Swift類中加了@objc修飾的屬性和方法都在MainProject-Swift.h中自動生成了對應的OC屬性和方法,所以通過引用MainProject-Swift.h就可以去調(diào)用這些接口了。

Swift調(diào)用OC接口

Swift調(diào)用OC類略有不同

  1. MainProject-Bridging-Header.h橋接文件里引入需要被Swift調(diào)用的類
#import "Cat.h"
  1. Swift可以直接使用MainProject-Bridging-Header.h里引入的類
class Dog: NSObject {
    @objc var friend = Cat()
    func friendEatAction() {
        friend.eat();
    }
}

這個橋接過程比較簡單。

如果主工是Swift編寫的

這種情況下OC&Swift的相互調(diào)用與上述并無二致。在創(chuàng)建OC類的時候需要同時生成一個橋接文件和一個Swift轉(zhuǎn)OC的接口文件,其他過程就不再贅述了。

OC主工程里調(diào)用Swift Pod

  1. 首先要確保OC主工程處于混編模式,否則再引入swfit pod,執(zhí)行pod install時會報錯(這個問題在問題2中有說明),此時最好是新建一個swift文件,自動創(chuàng)建橋接文件。

  2. 創(chuàng)建Swift類和接口。由于Swift接口是OC類調(diào)用的,所以Swift接口要有@objc修飾符; 另外接口調(diào)用是跨Target的,所以要有publicopen關(guān)鍵字。如下代碼,class前面和接口前面有@objc public修飾的就可以在主工程里使用。

@objc public class Dog: NSObject {
    @objc let legNumber = 4
    var temper = "temper-good"
    @objc public func eat() {
        print("The dog is eating")
    }
}
  1. OC調(diào)用Swift接口, Swift Pod工程的build setting里也有一個Interface Header文件,首先要在主工程里引入這個文件,讓后就可以在OC里面使用Swift相關(guān)接口了。(除此之外還有@import導入方式,這是module機制)


#import "ViewController.h"
#import <SwiftPodFirst/SwiftPodFirst-Swift.h>
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    Dog *dog = [[Dog alloc] init];
    [dog eat];
}

如果調(diào)用沒有public的接口就會產(chǎn)生如下報錯,OC可以訪問的接口可以進入接口頭文件SwiftPodFirst-Swift.h查看。

No visible @interface for 'Dog' declares the selector 'temper'

Swift主工程里調(diào)用OC Pod

  1. 首先要確保Swift主工程處于混編模式,具體方法不再贅述。
  2. 在Swift主工程橋接文件SwiftMainProject-Bridging-Header.h中添加#import引用
#import <OCPodSecond/Cat.h>    // 這里OCPodSecond是OC pod,Cat是OC類
  1. Swift主工程中使用OC類
import UIKit

class ViewController: UIViewController {
    let cat = Cat()   // Cat是OC類
    override func viewDidLoad() {
        super.viewDidLoad()
        cat.eat()       // 是OC對象的方法
    }
}

一個由OC語言創(chuàng)建的Pod里有Swift代碼,再由一個OC主工程使用這個Pod(這是項目里最常用到的場景,也是最復雜的一個場景)

  1. 在之前的OCPodSecond里添加一個Swift類,使OC Pod變成混編模式。
  2. 在第1步之后會自動創(chuàng)建OCPodSecond-Swift.h接口頭文件,但并不會有創(chuàng)建OC-Bridging-Header.h的提示,那只好自己創(chuàng)建一個橋接文件并在build setting里關(guān)聯(lián)路徑
  3. 遵循之前的原則為Swift類添加@objc public接口
// 這個類是OCPodSecond里的Swift類
@objc public class Pig: NSObject {
    @objc public func eat() {
        print("The pig is eating!!")
    }
}
  1. 最后在主工程里調(diào)用
#import "ViewController.h"
#import <SwiftPodFirst/SwiftPodFirst-Swift.h>
#import <OCPodSecond/Cat.h>
#import <OCPodSecond/OCPodSecond-Swift.h>

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    Dog *dog = [[Dog alloc] init];  // <SwiftPodFirst/SwiftPodFirst-Swift.h>
    [dog eat];
    
    Cat *cat = [[Cat alloc] init];  // <OCPodSecond/Cat.h>
    [cat eat];
    
    Pig *pig = [[Pig alloc] init];  // <OCPodSecond/OCPodSecond-Swift.h>  在OC Pod里添加的Swift類 Pig
    [pig eat];
}

運行一下試試,結(jié)果出現(xiàn)報錯提示,framework targets不支持bridging header(引文7),

<unknown>:0: error: using bridging headers with framework targets is unsupported
Command CompileSwiftSources failed with a nonzero exit code

Support Files里有個OCPodSecond-umbrella.h頭文件,這個頭文件替代了bridging header的作用,文件中會自動導入OC類。因此在Pod里混編更加簡單了,橋接文件和相關(guān)接口會自動生成。

另外,每當新增一個接口時,都有可能出現(xiàn)接口不可用的情況,這或許是因為語言之間的轉(zhuǎn)換沒有這么快,也可能是存在一些接口緩存文件沒有及時更新。遇到這種情況,最好是重新build一下或者把pod重新裝一遍再或者clean重啟一下工程。

OC Pod引用Swift Pod

這種情形與主工程調(diào)用Swift Pod沒有區(qū)別,只需引入相關(guān)的接口文件就可以xxx-Swift.h,此處不再多述。需要注意的是,在podspec文件里要添加dependency,否則會無法引用相關(guān)pod。

Swift Pod 引用 OC Pod

這種情況有點特殊,雖然umbrella header有Bridging header的作用,但是umbrella header里的#import指令是自動生成的,并且只有Pod內(nèi)部混編時才會自動引用Pod內(nèi)的OC header。所以這樣只能支持Pod內(nèi)部混編。

假設(shè)一個Swift Pod需要引用一個OC Pod,此時在Swift Pod的umbrella header里添加#import "<xxPod/xx.h>",雖然最后可以成功使用<xxPod/xx.h>接口并且通過編譯。但只要重新執(zhí)行pod install命令umbrella header就會被更新,里面自定義的import指令會被全部刪除。這意味著其他項目無法成功引用該Swift Pod。

這時我們需要modular的編譯方式,在podfile里把use_frameworks!改為use_modular_headers!,這樣就可以使用import 方式引用pod,import 方式同時兼容OC和Swift。

總結(jié)

最后混編路徑匯總成下圖,如果完全使用modular方式,可以把該圖做一些簡化。


問題1

[!] Unable to determine Swift version for the following pods:

- `SwiftPodFirst` does not specify a Swift version and none of the targets (`MainProject`) integrating
it have the `SWIFT_VERSION` attribute set. Please contact the author or set the `SWIFT_VERSION` attribute 
in at least one of the targets that integrate this pod.

需要指定pod的swift版本,以便提供合適的編譯器。修改方式是在集成的Target里指定SWIFT_VERSION。例如我們這里產(chǎn)生的原因是OC主工程不是混編模式,所以沒有swift相關(guān)的編譯項,這里修改的方式是在OC主工程里新建一個Swift類,讓主工程變?yōu)榛炀幠J剑瑫詣釉赽uild setting生成相關(guān)的swift編譯項。

問題2

Module 'SwiftPodFirst' not found

找不到某個Module,這是我們再開發(fā)中最常遇到的問題了,導致這個報錯的原因很多:

  1. https://docs.swift.org/swift-book/RevisionHistory/RevisionHistory.html
  2. https://www.tiobe.com/tiobe-index/
  3. https://juejin.im/post/5bd3172e518825292d6afc11
  4. https://nshipster.com/the-death-of-cocoa/
  5. Importing Swift into Objective-C
  6. Importing Objective-C into Swift
  7. https://blog.csdn.net/cooldragon/article/details/50172649
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內(nèi)容

  • 前言: Swift語言出來后,可能新的項目直接使用Swift來開發(fā),但可能在過程中會遇到一些情況,某些已用OC寫好...
    瘋狂的電腦閱讀 2,318評論 0 9
  • 概述 利用runtime特性實現(xiàn)iOS項目的組件化開發(fā),是由@casatwy大神提出來的,在他的博客中具體介紹...
    Mr杰杰閱讀 1,626評論 2 9
  • 級別: ★★☆☆☆標簽:「iOS」「Swift 」「Swift與OC混編」作者: dac_1033審校: QiSh...
    QiShare閱讀 1,015評論 2 8
  • 在使用Swift進行iOS開發(fā)的過程中,經(jīng)常涉及到Swift與OC混編的情況,有時主工程是OC的需要另外編入Swi...
    大成小棧閱讀 807評論 0 1
  • swift 語言出來后,可能新的項目直接使用swift來開發(fā),但可能在過程中會遇到一些情況,某些已用OC寫好的類或...
    iOS_小勝閱讀 2,091評論 2 1