為什么要選擇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é)出以下幾種混編場景
- 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
- 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.h
和MainProject-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接口
- 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")
}
}
- 在OC類中首先要引用
MainProject-Swift.h
#import "MainProject-Swift.h"
- 最后就可以在OC類里引用Swift類
#import "MainProject-Swift.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Dog *dog = [[Dog alloc] init];
[dog eat];
}
@end
- 結(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類略有不同
- 在
MainProject-Bridging-Header.h
橋接文件里引入需要被Swift調(diào)用的類
#import "Cat.h"
- 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
首先要確保OC主工程處于混編模式,否則再引入swfit pod,執(zhí)行
pod install
時會報錯(這個問題在問題2中有說明),此時最好是新建一個swift文件,自動創(chuàng)建橋接文件。創(chuàng)建Swift類和接口。由于Swift接口是OC類調(diào)用的,所以Swift接口要有
@objc
修飾符; 另外接口調(diào)用是跨Target的,所以要有public
或open
關(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")
}
}
-
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
- 首先要確保Swift主工程處于混編模式,具體方法不再贅述。
- 在Swift主工程橋接文件
SwiftMainProject-Bridging-Header.h
中添加#import
引用
#import <OCPodSecond/Cat.h> // 這里OCPodSecond是OC pod,Cat是OC類
- 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(這是項目里最常用到的場景,也是最復雜的一個場景)
- 在之前的OCPodSecond里添加一個Swift類,使OC Pod變成混編模式。
- 在第1步之后會自動創(chuàng)建
OCPodSecond-Swift.h
接口頭文件,但并不會有創(chuàng)建OC-Bridging-Header.h
的提示,那只好自己創(chuàng)建一個橋接文件并在build setting里關(guān)聯(lián)路徑 - 遵循之前的原則為Swift類添加
@objc public
接口
// 這個類是OCPodSecond里的Swift類
@objc public class Pig: NSObject {
@objc public func eat() {
print("The pig is eating!!")
}
}
- 最后在主工程里調(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ā)中最常遇到的問題了,導致這個報錯的原因很多:
- https://docs.swift.org/swift-book/RevisionHistory/RevisionHistory.html
- https://www.tiobe.com/tiobe-index/
- https://juejin.im/post/5bd3172e518825292d6afc11
- https://nshipster.com/the-death-of-cocoa/
- Importing Swift into Objective-C
- Importing Objective-C into Swift
- https://blog.csdn.net/cooldragon/article/details/50172649