前言
iOS混編也是老生常談的問(wèn)題,本篇文章我們將從實(shí)際開(kāi)發(fā)中不同使用場(chǎng)景來(lái)探究混編的相關(guān)知識(shí)點(diǎn)匿值,以便在實(shí)際項(xiàng)目中混編相關(guān)報(bào)錯(cuò)問(wèn)題的處理赠制。
- 主工程內(nèi)混編
- 主工程與依賴(lài)庫(kù)混編
- 依賴(lài)庫(kù)內(nèi)部混編
主工程內(nèi)混編
主工程內(nèi)無(wú)外乎是Swift工程調(diào)用OC文件和OC工程調(diào)用Swift文件。
主工程為OC時(shí)挟憔,在第一次生成Swift文件時(shí)候钟些,系統(tǒng)會(huì)彈窗提示是否創(chuàng)建橋接文件ProjectName-Bridging-Header.h。
同樣的主工程為Swift時(shí)绊谭,在第一次生成OC文件時(shí)政恍,也會(huì)彈窗詢(xún)問(wèn)是否創(chuàng)建橋接文件。
橋接文件是為了將需要在Swift文件中使用到的OC文件的頭文件引入达传,編譯器則會(huì)將引入的所有oc文件進(jìn)行module化篙耗,即在swift的角度而言,引入的每個(gè)oc文件即為引入的每個(gè)module宪赶,這樣做的目的也主要是為了提高編譯速度宗弯,在編譯的過(guò)程中,編譯過(guò)的module會(huì)放入一個(gè)編譯好的modules數(shù)據(jù)結(jié)構(gòu)中逊朽,當(dāng)有重復(fù)的module引入時(shí)罕伯,dyld會(huì)將先前編譯好的module進(jìn)行l(wèi)ink產(chǎn)生最終的mach-o文件。
而如果是OC文件中使用Swift文件時(shí)叽讳,則需要在使用的OC文件中引入ProjectName-Swift.h文件追他,可在Targets -> Build Settings中查看。
該文件會(huì)在編譯的過(guò)程中產(chǎn)生岛蚤,其主要的目的是為了將Swift轉(zhuǎn)譯為OC邑狸,例如將一個(gè)類(lèi)暴露給OC的信息展示出來(lái),這樣在OC文件中就能直接使用了涤妒。
但由于Swift是靜態(tài)語(yǔ)言单雾,所以要想OC文件能訪(fǎng)問(wèn)到Swift的信息,例如一個(gè)類(lèi)的信息,則需要滿(mǎn)足以下若干條件:
- 類(lèi)繼承于NSObject基類(lèi)硅堆。
- 需要訪(fǎng)問(wèn)的屬性方法需要在繼承于基類(lèi)基礎(chǔ)之上再加上 @objc 修飾屿储。
- 如果想要使用方法交換,那么在以上基礎(chǔ)上還需要加上 dynamic 修飾(@objc 在前)渐逃。
依賴(lài)庫(kù)內(nèi)部混編
.modulemap文件
在正式探索這一塊知識(shí)之前够掠,我們先來(lái)了解下.modulemap文件,查看項(xiàng)目編譯后的AFNetworking.framework文件夾茄菊,標(biāo)準(zhǔn)的.framework依賴(lài)庫(kù)格式如下所示:
所有頭文件存放在Headers目錄下疯潭,點(diǎn)擊module.modulemap文件:
framework module AFNetworking {
umbrella header "AFNetworking-umbrella.h"
export *
module * { export * }
}
在此module文件中所寫(xiě)代碼意思就是將AFNetworking.framework中的頭文件暴露給外界,通過(guò)AFNetworking-umbrella.h這個(gè)傘
文件面殖,查看此文件如下:
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
#import "AFHTTPSessionManager.h"
#import "AFURLSessionManager.h"
#import "AFCompatibilityMacros.h"
#import "AFNetworkReachabilityManager.h"
#import "AFSecurityPolicy.h"
#import "AFURLRequestSerialization.h"
#import "AFURLResponseSerialization.h"
FOUNDATION_EXPORT double AFNetworkingVersionNumber;
FOUNDATION_EXPORT const unsigned char AFNetworkingVersionString[];
類(lèi)似于上方的橋接文件竖哩,AFNetworking-umbrella.h也是給外界暴露依賴(lài)庫(kù)的頭文件。
framework module AFNetworking
:標(biāo)準(zhǔn)寫(xiě)法脊僚,將AFNetworking設(shè)為一個(gè)名為AFNetworking的模塊相叁。
umbrella header "AFNetworking-umbrella.h"
:設(shè)置此模塊的傘文件,傘文件中引入了庫(kù)中的頭文件辽幌。
export *
:將AFNetworking庫(kù)中本來(lái)依賴(lài)的其余東西帶出去钝荡。
module * { export * }
:如果存在子模塊時(shí)候,繼續(xù)將子模塊及子模塊依賴(lài)的所有東西一起帶出去舶衬。
自定義.framework
例如上方工程中埠通,需要使用LcrTiger類(lèi),常規(guī)引入頭文件方法為#import "LcrTiger.h"逛犹、#import "Head/LcrTiger.h"端辱、絕對(duì)路徑#import "/Users/lichuanrong/Desktop/LcrApp/LcrApp/Head/LcrTiger.h"三種方式。
但是我們利用.modulemap文件將LcrTiger包裝成一個(gè)模塊虽画,然后利用@import Tiger; 來(lái)引入Tiger類(lèi)舞蔽。
Xcode配置文件LcrApp-Debug.xcconfig中給項(xiàng)目增加module文件路徑如下:
OTHER_CFLAGS = "-fmodule-map-file=${SRCROOT}/LcrApp/Head/TigerModule.modulemap"
TigerModule.modulemap代碼如下:
module Tiger {
header "LcrTiger.h"
export *
}
如果將export *注釋掉,則LcrTuger.m中的外部符號(hào)NSLog就會(huì)報(bào)錯(cuò):
所以export *的作用也就不用再解釋了吧码撰。
接下來(lái)我們手動(dòng)將Head文件夾封裝成.framework靜態(tài)庫(kù)渗柿,仿照上方AFNetworking.framework標(biāo)準(zhǔn)樣式。
- 將Head文件夾更改為Head.framework脖岛。
- 新建Headers文件夾存放頭文件LcrTiger.h朵栖。
- 修改TigerModule文件,添加framework 前綴柴梆。
framework module Tiger {
header "LcrTiger.h"
export *
}
- 修改config文件中TigerModule文件路徑陨溅。
同樣利用@import Tiger;即可引入此模塊。
子模塊及傘文件
新建LcrDog類(lèi)绍在,將LcrDog.h拖進(jìn)Headers文件夾內(nèi)门扇,新建Animal-umbrella.h傘文件雹有。
在Animal-umbrella.h文件中分別引入LcrTiger.h、LcrDog.h兩個(gè)頭文件臼寄,更改TigerModule如下:
framework module Animal {
umbrella header "Animal-umbrella.h"
export *
}
通過(guò)添加 umbrella 修飾符霸奕,將Animal-umbrella.h文件作為模塊Animal的傘文件
,其中以引入了外界需要使用的類(lèi)的頭文件吉拳,所以外界使用@import Animal;引入整個(gè)模塊即可铅祸。
這樣一個(gè)簡(jiǎn)單的.framework就制作好了。
優(yōu)化
如果一個(gè)庫(kù)文件內(nèi)也分幾個(gè)小模塊合武,那么就需要?jiǎng)?chuàng)建子Module,例如將上方的LcrTiger涡扼、LcrDog分別作為小模塊稼跳,如下所示:
framework module Animal {
umbrella header "Animal-umbrella.h"
export *
module LcrTiger {
header "LcrTiger.h"
export *
}
module LcrDog {
header "LcrDog.h"
export *
}
}
此時(shí)@import Animal;引入依然是沒(méi)問(wèn)題的,注釋掉Animal-umbrella.h文件中的引入都是沒(méi)啥問(wèn)題的吃沪。也可分別引入子模塊:
@import Animal.LcrTiger;
@import Animal.LcrDog;
- 當(dāng)需要顯式地引入相應(yīng)子模塊時(shí)汤善,就需要在相應(yīng)子模塊前添加explicit 修飾符,注釋掉Animal-umbrella.h文件中的引入票彪,如果還是利用@import Animal;的話(huà)红淡,那么就會(huì)報(bào)錯(cuò),解決方法就是@import Animal.LcrTiger;顯式地引入降铸。
- 如果子Module中不制定頭文件在旱,那么就不能注釋掉Animal-umbrella.h文件中的引入,因?yàn)檫@樣寫(xiě)的話(huà)推掸,系統(tǒng)就會(huì)默認(rèn)將傘文件中的頭文件看作是一個(gè)個(gè)子Module桶蝎。
framework module Animal {
umbrella header "Animal-umbrella.h"
export *
module * {
export *
}
}
//Animal-umbrella.h
#import <Head/LcrTiger.h>
#import <Head/LcrDog.h>
注:
(使用子模塊的好處就是如果庫(kù)中頭文件太多,可以將部分用到的文件放入子模塊谅畅,顯式引入子模塊即可登渣,提高代碼效率,例如庫(kù)中有100個(gè)頭文件毡泻,80個(gè)放入傘文件中胜茧,而我需要使用另外20個(gè),那么就可以將另外20個(gè)放入子模塊中仇味,那80個(gè)我不用就不引入呻顽,就不參與編譯,大大節(jié)省時(shí)間)
子模塊間關(guān)系
子模塊間如果有引用關(guān)系丹墨,例如LcrTiger中使用到LcrDog芬位,可利用@import LcrDog;先將LcrDog子模塊引入,然后export LcrDog發(fā)射出去带到,外界LcrDog也可用昧碉。
framework module Animal {
umbrella header "Animal-umbrella.h"
export *
}
module LcrDog {
header "Headers/LcrDog.h"
export *
}
module LcrTiger {
header "Headers/LcrTiger.h"
export LcrDog
export *
}
拓展--requires
framework module Animal {
umbrella header "Animal-umbrella.h"
export *
}
//點(diǎn)語(yǔ)法 默認(rèn)是Animal的子module
//requires objc:使用Animal.Swift 模塊的源文件必須是OC文件英染。
module Animal.Swift {
header "Headers/LcrTiger.h"
requires objc
}
創(chuàng)建SwiftFrameW.framework項(xiàng)目,項(xiàng)目初始語(yǔ)言為Swift被饿,新建Teacher.swift文件四康,以及OC類(lèi)Lcr:
注意:
在上文主工程內(nèi)混編
中,系統(tǒng)會(huì)彈窗提示是否需要橋接文件狭握,但在這個(gè)項(xiàng)目目前的過(guò)程中沒(méi)有闪金,也就是說(shuō)沒(méi)有橋接文件,swift文件如何使用oc信息论颅。
那么如上所示的想要在Teacher.swift文件中使用Lcr類(lèi)哎垦,則需要將引入到SwiftFrameW.h頭文件中,這個(gè)文件時(shí)framwork創(chuàng)建時(shí)就自帶的恃疯,作用類(lèi)似上文中的傘文件漏设,#import "Lcr.h" 發(fā)現(xiàn)報(bào)錯(cuò)如下:
Include of non-modular header inside framework module 'SwiftFrameW': '/Users/lichuanrong/Desktop/SwiftFrameW/SwiftFrameW/Objc/Lcr.h'
此時(shí)需要將Lcr.h頭文件移到public中,以便對(duì)外暴露今妄,在public中的頭文件最終編譯后存放在Headers文件夾中郑口,外界訪(fǎng)問(wèn)庫(kù)文件時(shí)就是通過(guò)訪(fǎng)問(wèn)Headers中的頭文件的。
再次編譯盾鳞,報(bào)錯(cuò)消失了犬性。
那么如果Lcr.m中使用Teacher,方法與常規(guī)主工程中方法一樣腾仅,需要引入SwiftFrameW-Swift.h文件乒裆,就是寫(xiě)法不一樣:
#import <SwiftFrameW/SwiftFrameW-Swift.h>
那如果是利用modulemap的方式呢?
- 新建module.modulemap文件推励。
- 新建xcconfig文件配置參數(shù)缸兔。(PROJECT里面設(shè)置好debug模式)
-
注釋SwiftFrameW.h里對(duì)Lcr.h的引用。
發(fā)現(xiàn)編譯錯(cuò)誤吹艇,因?yàn)闀簳r(shí)還沒(méi)引用惰蜜,即找不到相應(yīng)的頭文件。
要不就將SwiftFrameW.h里的注釋打開(kāi)受神,要不就將modulemap文件修改如下:
framework module SwiftFrameW {
umbrella header "SwiftFrameW.h"
export *
module Lcr {
header "Lcr.h"
export *
}
module * {
export *
}
}
這樣編譯就不報(bào)錯(cuò)了抛猖。
Public Private Project
上方是將Lcr.h頭文件放在Public中,編譯產(chǎn)物中鼻听,Lcr.h存放在Headers文件夾中财著,如果將它拖進(jìn)Private中,編譯依然沒(méi)問(wèn)題撑碴,編譯產(chǎn)物中Lcr.h就被放入到PrivateHeaders文件夾中撑教。
在Teacher中依然能夠訪(fǎng)問(wèn)到Lcr類(lèi)信息,但是如果將Lcr.h設(shè)為單獨(dú)的一個(gè)Private的模塊醉拓,如下:
利用import SwiftFrameW_Private 依然能通過(guò)編譯伟姐,說(shuō)明不管Lcr.h放在Public還是Private中都能被外界訪(fǎng)問(wèn)到收苏。
主工程與依賴(lài)混編
利用上方創(chuàng)建的主工程和framework靜態(tài)庫(kù)合并成一個(gè)xcworkspace,且主工程引入SwiftFrameW.framework愤兵,探索主工程與依賴(lài)庫(kù)之間的混編鹿霸。
如上圖所示,主工程依賴(lài)SwiftFrameW庫(kù)秆乳,之間的混編如下:
- 主工程O(píng)C訪(fǎng)問(wèn)依賴(lài)庫(kù)OC文件:
#import <SwiftFrameW/Lcr.h>
或者@import SwiftFrameW;
- 主工程O(píng)C訪(fǎng)問(wèn)依賴(lài)庫(kù)Swift文件:
#import <SwiftFrameW/SwiftFrameW-Swift.h>
懦鼠,注意Swift 中需要用public或open修飾,因?yàn)槭强鐃arget的屹堰。 - 主工程O(píng)C訪(fǎng)問(wèn)主工程Swift文件:
#import "工程名-Swift.h"
- 主工程Swift訪(fǎng)問(wèn)依賴(lài)庫(kù)OC和Swift文件:
@import SwiftFrameW
- 主工程Swift訪(fǎng)問(wèn)主工程O(píng)C文件:
工程名-Bridging-Header.h
內(nèi)引入頭文件即可肛冶。
更多詳情訪(fǎng)問(wèn):
Swift與OC混編
OC項(xiàng)目倒入Swift三方庫(kù)不兼容問(wèn)題
Swift調(diào)用OC