Module & Swift庫 (6)

Module & Swift庫 (6)

Module

Module定義

Module(模塊)-最小的代碼單元趟薄。
一個(gè)Module是機(jī)器代碼和數(shù)據(jù)的最小單位刀疙,可以獨(dú)立于其他代碼單位進(jìn)行鏈接。
通常剧罩,Module是通過編譯單個(gè)源文件生成的目標(biāo)文件贷洲。例如,當(dāng)前的test.m被編譯成目標(biāo)文件test.o時(shí)缴啡,當(dāng)前的目標(biāo)文件就代表了一個(gè)Module。

但是瓷们,有一個(gè)問題业栅,Module在調(diào)用的時(shí)候會(huì)產(chǎn)生開銷秒咐,比如我們在使用一個(gè)靜態(tài)庫的時(shí)候.

include 與 import的區(qū)別

  1. #import導(dǎo)入頭文件的好處 -> 頭文件會(huì)編譯生成一份二進(jìn)制文件, 只用編譯一份.
  2. #include導(dǎo)入頭文件, 不同的文件會(huì)重新編譯導(dǎo)致編譯多份.

module腳本

  1. 打開module原理工程
  2. 運(yùn)行腳本module
    # -fmodules:允許使用module語言來表示頭文件
    # -fmodule-map-file:module map的路徑。如不指明默認(rèn)module.modulemap
    # -fmodules-cache-path:編譯后的module緩存路徑
    clang  -fmodules -fmodule-map-file=Cat.modulemap -fmodules-cache-path=../prebuilt -c use.c -o use.o
    
  3. 文章參考
  4. 終端運(yùn)行clang -fmodules -fmodule-map-file=module.modulemap -fmodules-cache-path=../prebuilt -c use.c -o use.o -> .o文件
  5. 去緩存目錄里面找.pcm文件 -> 就是頭文件編譯之后的產(chǎn)物
  6. .modulemap -> 描述頭文件跟module之間的映射關(guān)系
    // 用來描述頭文件與module之間映射的關(guān)系
    // A -> A.h
    module A {
       header "A.h"
    }
    
    // B -> B.h
    module B {
       header "B.h"
       //  導(dǎo)入 -> 重新導(dǎo)出(把使用的其他頭文件一并暴露出來) 通常使用通配符 export * 導(dǎo)出所有的
       export A
    }
    

看下AFNetworking的modulemap

module.modulemap

```
// framework module 名稱 AFNetworking
framework module AFNetworking {
    // umbrella <目錄> 傘柄  <目錄>/.h
    // AFNetworking-umbrella.h 傘柄 AFNetworking-umbrella.h/.h 傘骨
    umbrella header "AFNetworking-umbrella.h"

    // 重新導(dǎo)出
    export *
    // module: 子module*
    module * { export * }
    
    //explicit: 可以指定子module的名稱
    //explicit module ASControlNode_Subclasses {
        //header "ASControlNode+Subclasses.h"
        //export *
    //}
}
```

注意:

  1. -fmodules是Xcode默認(rèn)開啟的
  2. 當(dāng)開啟的時(shí)候 #include/#import/@import -> 轉(zhuǎn)化成 @import

Module實(shí)操

  1. App 與 Framework放在一起
    1. File -> Save As Workspace -> 取名 -> 關(guān)閉工程重新從workspace打開
    2. 工程左下角 + -> 庫文件
    3. 編譯APP的時(shí)候,同時(shí)編譯庫文件
      1. 將庫文件的編譯產(chǎn)物.framework -> 拖拽到 -> APP的Targets/General/Frameworks
      2. 如果沒有編譯,也沒關(guān)系,手動(dòng)編譯一下庫文件
  2. 手寫modulemap
    1. 配置Targets -> Build Settings -> module -> Module Map File
    2. 編譯 -> 默認(rèn)生成module.modulemap
  3. APP使用
    1. 記得暴露庫的頭文件 -> 庫 -> Build Phases -> Headers -> 查看頭文件是否暴露

Swift庫使用Module與OC混編

  1. swift -> 調(diào)用OC時(shí), 需要一個(gè)橋接文件, 但是在swift中是沒有橋接文件一說的.
  2. 解決方法,定義modulemap文件LGSwiftFramework來暴露 -> 記得配置Build Settings -> module map file -> 編譯,可以正常使用OC文件 -> 問題:此時(shí)外面的使用Swift庫的也能使用了
  3. 只有自己使用的情況LGSwiftFramework.private -> 注意命名中間一定要有private,這是是規(guī)則 -> 配置 private module map file ->注意:此時(shí)外界也是可以訪問到的,private只是有助于區(qū)分私有的
    //_Private固定寫法規(guī)則,私有的modulemap文件
    framework module LGSwiftFramework_Private {
        module LGOCStudent {
        header "LGOCStudent.h"
        export *
        }
    }
    
  4. 還有一種方法, 這里只講理論 -> OC語言特性 協(xié)議 -> Swift -> 協(xié)議(暴露出去) -> OC

Swift庫

定義

在Xcode9之后, Swift開始支持靜態(tài)庫.
Swift沒有頭文件的概念, 那么我們外界要使用Swift中用public修飾的類和函數(shù)怎么辦?
Swift庫中引入了一個(gè)全新的文件.swiftmodule.
.swiftmodule包含序列化過的AST(抽象語法書, Abstract Syntax Tree), 也包含SIL(Swift中間語言, Swift Intermediate Language).

  1. 兩個(gè)Swift庫

    1. 兩個(gè)庫都有同樣的swift命名文件
    2. 分別編譯(有腳本) -> 靜態(tài)庫
      1. 腳本的作用 -> 將編譯后的庫拷貝到一個(gè)文件夾(具體參考該工程設(shè)置)
  2. libtool -static LGSwiftA LGSwiftB -o libLGSwiftC.a

    1. 注意將兩個(gè)庫拷貝出來
    2. 確定命令 -> 有警告 -> 兩個(gè)庫有相同的文件命名
      1. 注意: 用libtool會(huì)報(bào)沖突, 但是不會(huì)替換
      2. ar -t libLGSwiftC.a -> 列出合并庫的.o文件列表
    3. 然后就有問題了,兩個(gè)靜態(tài)庫的Headers文件以及Modules文件怎么處理
      1. OC可以不要Modules文件, 只把Header文件放在一起就可以
      2. 但是Swift會(huì)多生成swiftModule文件,是個(gè)合并難題
      3. 將兩個(gè)庫多余的數(shù)據(jù)刪除,只保留Headers和Module,跟libLGSwiftC.a合成一個(gè)文件
      4. image.png
  3. 參考cocoapods的處理

    1. 會(huì)把所有庫的頭文件放在pods -> Headers 目錄下
  4. 將合并的頭文件拖到項(xiàng)目中的Frameworks

    1. 新建xcconfig -> 告訴項(xiàng)目頭文件所在地方
    2. xcconfig文件配置如下
      HEADER_SEARCH_PATHS = $(inherited) "${SRCROOT}/LGSwiftC/Public/         LGSwiftA.framework/Headers" "${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/Headers"
      // OTHER_CFLAGS:傳遞給用來編譯C或者OC的編譯器碘裕,當(dāng)前就是clang
      // OTHER_CFLAGS -> 相當(dāng)于Build Setings里的Other C flag
      OTHER_CFLAGS="-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/module.modulemap" "-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/module.modulemap"
      
      // SWIFT_INCLUDE_PATHS: 傳遞給SwiftC編譯器反镇,告訴他去下面的路徑中查找module.file
      // SWIFT_INCLUDE_PATHS相當(dāng)于Build Setting -> 搜import path
      SWIFT_INCLUDE_PATHS="${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework"  "${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework"
      
    3. 以上編譯沒有問題, 但是swift運(yùn)行庫LGSwiftA文件會(huì)報(bào)錯(cuò)
      1. 因?yàn)樯厦娴腛THER_CFLAGS是編譯C或者OC的
      2. 請看上面SWIFT_INCLUDE_PATHS的配置
        1. 注意將Modules文件夾里的文件配置到跟上一級Headers相同的目錄下,并刪除Modules

Swift庫文件合并總結(jié)

  1. 合并庫文件 -> libLGSwiftC.a
  2. 處理頭文件
    1. module文件已經(jīng)處理了, 讓編譯器找到module文件就可以了
    2. OC配置 -> OTHER_CFLAGS
    3. Swift配置 -> SWIFT_INCLUDE_PATHS

Swift配置.apinotes

當(dāng)我們Swift調(diào)用OC方法的時(shí)候, 調(diào)用的命名有可能有變化.

  1. 那么不想Swift進(jìn)行優(yōu)化怎么處理

    1. 宏 -> NS_SWIFT_NAME -> 規(guī)定名稱
    typedef NS_ENUM(NSUInteger, LGTeacherName) {
    LGTeacherNameHank,
    LGTeacherNameCat,
    };
    
    //屬性指示編譯器使用struct(swift_wrapper(struct)屬性),而與NS_TYPED_ENUM娘汞,編譯器被指示使用enum(swift_wrapper(enum)屬性)
    typedef NSString * LGTeacherNameString NS_TYPED_EXTENSIBLE_ENUM;
    
    extern NSString *getTeacherName(void);
    extern NSString * const LGTeacherCat;
    extern LGTeacherNameString const LGTeacherHank;
    
    // 宏來配置 -》SDK -〉 OC -》 修改原有的代碼 -〉發(fā)布新版本
    // 弊端 -> 工作量大
    @interface LGToSwift : NSObject
    
    - (instancetype)initWithName:(NSString *)name;
    
    - (id)objectForKeyedSubscript:(id)key; // subscript getter
    - (void)setObject:(id)obj forKeyedSubscript:(id)key;
    // 通過指定NS_SWIFT_NAME宏,我們可以添加一些詳細(xì)信息以使函數(shù)清晰可見夕玩,從而使其變得如下所示:
    // 規(guī)范
    - (nullable NSString *)teacherNameForIndex:(NSUInteger)index NS_SWIFT_NAME(teacherName(forIndex:));
    
    // NS_REFINED_FOR_SWIFT從現(xiàn)在開始你弦,Swift的Clang Importer將做一些額外的工作并將該方法導(dǎo)入為私有方法,并以雙下劃線字符開頭__燎孟,例如:
    //- (BOOL)changeTeacherName:(nullable NSDictionary<NSString *, id> *)options;
    - (BOOL)changeTeacherName:(nullable NSDictionary<NSString *, id> *)options NS_REFINED_FOR_SWIFT;
    
  2. 通過宏的方法不可取, 工作量大 -> 那么怎么辦 -> .apinotes

  3. 命名為SDK名稱.apinotes -> 已經(jīng)要將該文件放在SDK的根目錄里面

  4. image.png
  5. 該文件格式為yaml格式 -> 方法映射 OC到Swift或者反之

#yaml
---
# Name: -> SDK的名稱
Name: OCFramework
# Classes -> 你要修改的類名
Classes:
- Name: LGToSwift
  SwiftName: ToSwift
  # 方法名的修改
  Methods:
  - Selector: "changeTeacherName:"
    Parameters:
    - Position: 0
      Nullability: O
    MethodKind: Instance
    SwiftPrivate: true
    #Availability: nonswift -> 意思是Swift中不能用,可以不寫,寫了就是加限制的意思
    Availability: nonswift
    AvailabilityMsg: "這個(gè)不能用"
  - Selector: "initWithName:"
    MethodKind: Instance
    DesignatedInit: true
    
# 更準(zhǔn)確的參考官方文檔
# https://clang.llvm.org/docs/APINotes.html

總結(jié)

  1. module -> 頭文件->目標(biāo)文件的關(guān)系
  2. modulemap ->頭文件 -> 目標(biāo)文件的映射
  3. module:定義一個(gè)module
    export:導(dǎo)出當(dāng)前代表的頭文件使用的頭文件
    export * :匹配目錄下所有的頭文件
    module * :目錄下所有的頭文件都當(dāng)作一個(gè)子module
    explicit : 顯式聲明一個(gè)module的名稱
  4. Swift庫使用OC代碼:不能使用橋接文件
    1. oc的頭文件放到modulemap下
    2. oc的頭文件放到私有的modulemap下
    3. 協(xié)議的方式 投機(jī)取巧
  5. Swift靜態(tài)庫的合并
    難點(diǎn):.swiftmodule 文件(Swift的頭文件)
    1. libtool 合并靜態(tài)庫本身
    2. 用到的頭文件和Swift頭文件和modulemap文件通過目錄的形式放到一起
    3. OC要用合并的靜態(tài)庫:clang: other c flags :-fmodule-map-file <modulemap path>
    4. Swift要用合并的靜態(tài)庫 : SwiftC :other swift flags 顯式告訴SwiftC <modulemap dir>
      1. 為什么OC根Swift要分開傳遞, 因?yàn)閮蓚€(gè)語音的編譯器不一樣
      2. OC -> Clang
      3. Swift -> Swift
  6. OC映射到Swift方式
    1. <工程名稱>.apinotes

**官方文檔: **
APINotes.html
Modules.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末禽作,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子揩页,更是在濱河造成了極大的恐慌旷偿,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爆侣,死亡現(xiàn)場離奇詭異萍程,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)兔仰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門茫负,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人乎赴,你說我怎么就攤上這事忍法。” “怎么了榕吼?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵饿序,是天一觀的道長。 經(jīng)常有香客問我羹蚣,道長原探,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任度宦,我火速辦了婚禮踢匣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘戈抄。我一直安慰自己离唬,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布划鸽。 她就那樣靜靜地躺著输莺,像睡著了一般戚哎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嫂用,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天型凳,我揣著相機(jī)與錄音,去河邊找鬼嘱函。 笑死甘畅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的往弓。 我是一名探鬼主播疏唾,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼函似!你這毒婦竟也來了槐脏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤撇寞,失蹤者是張志新(化名)和其女友劉穎顿天,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蔑担,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡牌废,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了啤握。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片畔规。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖恨统,靈堂內(nèi)的尸體忽然破棺而出叁扫,到底是詐尸還是另有隱情,我是刑警寧澤畜埋,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布莫绣,位于F島的核電站,受9級特大地震影響悠鞍,放射性物質(zhì)發(fā)生泄漏对室。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一咖祭、第九天 我趴在偏房一處隱蔽的房頂上張望掩宜。 院中可真熱鬧,春花似錦么翰、人聲如沸牺汤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽檐迟。三九已至补胚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間追迟,已是汗流浹背溶其。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留敦间,地道東北人瓶逃。 一個(gè)月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像廓块,于是被迫代替她去往敵國和親金闽。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

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