1. 百度App Objective-C/Swift 組件化混編之路(二)- 工程化

        共 12510字,需瀏覽 26分鐘

         ·

        2022-02-15 04:26

        ????關(guān)注后回復(fù) “進(jìn)群” ,拉你進(jìn)程序員交流群????


        作者丨張渝、郭金

        來(lái)源丨百度App技術(shù)


        前文《百度App Objective-C/Swift 組件化混編之路》已經(jīng)介紹了百度App 引入 Swift 的影響面評(píng)估以及落地的實(shí)施步驟,本文主要以依賴管理工具為支撐,介紹百度App 如何實(shí)現(xiàn)組件內(nèi)的 Objective-C/Swift 混編、單測(cè)、二進(jìn)制發(fā)布和集成,以及組件間的依賴和引用。


        百度App 自研的依賴管理工具 EasyBox 工具鏈已經(jīng)把混編作為功能子集,如果你感興趣,可以閱讀百度App 技術(shù)公眾號(hào)往期文章《百度App iOS工程化實(shí)踐: EasyBox破冰之旅》。掌握 Xcode 編譯、鏈接選項(xiàng)等相關(guān)知識(shí)點(diǎn),有助于理解混編的實(shí)現(xiàn)過(guò)程。

        一. 組件Target類型 和 Module化

        為解決大規(guī)模并行開發(fā)問(wèn)題,百度App 將工程進(jìn)行了組件化拆分,并實(shí)現(xiàn)組件的二進(jìn)制化,一個(gè)組件即為一個(gè)獨(dú)立的功能單元和編譯單元,具有兩種形態(tài),源碼形態(tài)和二進(jìn)制形態(tài),開發(fā)過(guò)程中可以按需進(jìn)行組件的源碼/二進(jìn)制切換。所以我們要解決這兩種形態(tài)下的組件內(nèi)混編和組件間混合調(diào)用問(wèn)題。

        在介紹混編之前,我們先來(lái)了解兩個(gè)重要的概念:組件 Target 類型和 Module。

        1.1 組件 Target 類型

        EasyBox 工具鏈會(huì)為源碼形態(tài)的組件生成一個(gè) Xcode 子工程和對(duì)應(yīng)的 Target,Target 可以是以下類型中的一種:

        • dynamic_library:動(dòng)態(tài)庫(kù),Xcode 7 之前擴(kuò)展名為 .dylib, Xcode 7 后是 .tbd ;目前官方環(huán)境并不允許為 iOS 平臺(tái)添加這種類型。

        • static_library:靜態(tài)庫(kù),擴(kuò)展名 .a

        • static_framework:靜態(tài)庫(kù),擴(kuò)展名 .framework

        • dynamic_framework:動(dòng)態(tài)庫(kù),擴(kuò)展名 .framework

        .a 與 .framework 的區(qū)別是:Framework 是分層目錄,它將共享資源(例如動(dòng)態(tài)共享庫(kù),nib 文件,圖像文件,本地化字符串,頭文件和參考文檔)封裝在一個(gè)程序包中。動(dòng)態(tài)庫(kù)與靜態(tài)庫(kù)的區(qū)別是:系統(tǒng)根據(jù)需要將動(dòng)態(tài)庫(kù)加載到內(nèi)存中,可以被多個(gè)應(yīng)用程序同時(shí)訪問(wèn),并在所有可能的應(yīng)用程序之間共享資源的一份副本。靜態(tài)庫(kù)則是鏈接到某個(gè)應(yīng)用程序的二進(jìn)制中。

        這些 Target 可能還存在一個(gè)或多個(gè)伴生 Target :

        • bundle

        • octest_bundle

        • unit_test_bundle

        • ui_test_bundle

        What's the Xcode target?

        https://developer.apple.com/library/archive/featuredarticles/XcodeConcepts/Concept-Targets.html

        對(duì)于伴生 Target,與 Swift 混編相關(guān)的只有單測(cè);而對(duì)于主 Target,按照 Target 的文件組織形式可以分兩類:

        • Library(擴(kuò)展名為 .a)

        • Framework(擴(kuò)展名為 .framework)

        當(dāng) Target 中只有 Objective-C 源碼(.h、.m)時(shí),無(wú)論哪種 Target,源文件之間都可以通過(guò) import 頭文件的方式進(jìn)行引用,但 Swift 語(yǔ)言是強(qiáng)制以 module 形式 引用的,所以在 Swfit 中需要將 Target 的產(chǎn)物轉(zhuǎn)換為一個(gè)獨(dú)立的 module,供其他 module 依賴并引用。所以要實(shí)現(xiàn) Swift 混編,每個(gè)組件對(duì)應(yīng)的主 Target (源碼或二進(jìn)制)都必須以一個(gè) module 的形式存在。下面介紹如何實(shí)現(xiàn) Target 內(nèi)的 module 混編、以及 Target 之間的 module 依賴。

        1.2 Module 化

        1.2.1 基本概念

        • module:是一個(gè)編譯單元,或構(gòu)建產(chǎn)物,對(duì)一個(gè)軟件庫(kù)的結(jié)構(gòu)化替代封裝,供鏈接器使用(更多介紹請(qǐng)查閱 Clang-Module:https://clang.llvm.org/docs/Modules.html#introduction

        • umbrella header:module 對(duì)外公開的根頭文件,包含了這個(gè) module 中所有其他公開頭文件的引用。以 Foundation 框架的根頭文件 <Foundation/Foundation.h>為例:

        對(duì)編譯器來(lái)講,每次編譯過(guò)程一個(gè) module 只會(huì)加載一次,避免多次引入并加載相同的頭文件帶來(lái)的編譯耗時(shí)問(wèn)題。所以 module 化后編譯效率更高。

        • modulemap:描述 module 和 module header 間的關(guān)系,描述現(xiàn)有 header 如何映射到 module 的邏輯結(jié)構(gòu)。modulemap 結(jié)構(gòu)如下:

           
        framework module SwiftOCMixture {  umbrella header "SwiftOCMixture.h"
        export * module * { export * }}

        module SwiftOCMixture.Swift { header "SwiftOCMixture-Swift.h" requires objc}

        ModuleMap采用模塊映射語(yǔ)言,但是到現(xiàn)在( 2020 年 Q3 為止)該語(yǔ)法依然不夠穩(wěn)定,所以建議:編寫 modulemap 時(shí)需要盡可能使用少的關(guān)鍵字實(shí)現(xiàn) module 功能,比如 framework、umbrella、header、extern、use。

        建議 modulemap 內(nèi)聲明一個(gè)umbrella header,便于快速引用對(duì)應(yīng)的頭文件,但必須將所有公開的頭文件填充到 umbrella header 文件內(nèi)。否則將得到一個(gè)警告:

        <module-includes>
        Umbrella header for module 'XXX' does not include header 'absolute path to a public header'

        不包含 umbrella header 的 module ,modulemap 中不必添加 module * { export * }
        包含 umbrella header 的 framework,不用配置任何(包括 MODULEMAP_FILE )即可自動(dòng) module 化

        1.2.2 module 相關(guān)的 build setting 參數(shù)

        上古時(shí)期,程序員通過(guò) Makefile 來(lái)控制程序的編譯鏈接過(guò)程?,F(xiàn)如今在 IDE 的封裝下,復(fù)雜度大大降低,只需要通過(guò) IDE 來(lái)控制關(guān)鍵變量和自定義變量,在 Xcode 中,這個(gè)控制變量被稱為 build setting,build setting 和 Module 化相關(guān)的變量主要有這些:
        • 對(duì)module自身的描述:

          • DEFINES_MODULE:YES/NO,module 化需要設(shè)置為 YES

          • MODULEMAP_FILE:指向 module.modulemap 路徑

          • HEADER_SEARCH_PATHS:modulemap 內(nèi)定義的 Objective-C 頭文件,必須在 HEADER_SEARCH_PATHS 內(nèi)能搜索到

          • PRODUCT_MODULE_NAME:module 名稱,默認(rèn)和 Target name 相同

        • 對(duì)外部module的引用:

          • FRAMEWORK_SEARCH_PATHS:依賴的 Framework 搜索路徑

          • OTHER_CFLAGS:編譯選項(xiàng),可配置依賴的其他 modulemap 文件路徑 -fmodule-map-file=${modulemap_path}

          • HEADER_SEARCH_PATHS:頭文件搜索路徑,可用于配置源碼中引用的其他 Library 的頭文件

          • OTHER_LDFLAGS:依賴其他二進(jìn)制的編譯依賴選項(xiàng)

          • SWIFT_INCLUDE_PATHS:swiftmodule 搜索路徑,可用于配置依賴的其他 swiftmodule

          • OTHER_SWIFT_FLAGS:Swift 編譯選項(xiàng),可配置依賴的其他 modulemap 文件路徑 -Xcc -fmodule-map-file=${modulemap_path}


        本文的后續(xù)部分也會(huì)用到 build setting 中的其他關(guān)鍵變量。

        1.2.3 非 framework 的 module 處理

        包含 Swift 源碼的非 framework 的 module,建議在 buildphase 的 script 里處理編譯后的兩個(gè)事情:

        • 編譯生成的 interface header,拷貝作為公開頭文件,供其他 Target 訪問(wèn)編譯生成的 Swiftmodule,配置追加到 modulemap 文件中

        至此,我們已經(jīng)了解了單個(gè)組件的 module 化過(guò)程。

        二. 組件內(nèi)混編

        根據(jù)官方說(shuō)明,Target 內(nèi)支持 Objective-C 和 Swift 語(yǔ)言的混編,無(wú)外乎解決兩個(gè)問(wèn)題:

        • Objective-C 可以引用 Swift 的類和方法

        • Swift 可以引用 Objective-C 的類和方法

        下面我們針對(duì) Framework 和 Library(非 Framework 靜態(tài)庫(kù))兩種類型,分別介紹下組件內(nèi)的混編實(shí)現(xiàn)。

        2.1 Framework

        針對(duì) Framework 類型的 Target 內(nèi)混編,我們要做的就是什么都不做

        簡(jiǎn)單吧,對(duì)于全新生成的有 umbrella header 的 Framework 默認(rèn)就是 Module化 的,不需要做任何操作即可實(shí)現(xiàn) Target 內(nèi)混編。對(duì)于沒(méi)有umbrella header的Framework,需要參照 如何實(shí)現(xiàn) Module化 進(jìn)行 Module 化改造。

        • Objective-C 引用 Swift 在頭文件內(nèi)添加引入 Swift 的 Interface 頭文件即可,可以訪問(wèn) Swift 中以 @objc public 或 @objc open 修飾的類和方法,或者 class 修飾為 @objcMembers public


          #import <xxx/${ModuleName}-Swift.h>

        因?yàn)?Xcode 在編譯時(shí)已經(jīng)對(duì) framework 進(jìn)行 Module 化處理,并自動(dòng)生成該 Interface 頭文件,編譯成功時(shí)拷貝 Headers 文件夾內(nèi)

        • Swift 引用 Objective-C 直接使用對(duì)應(yīng)的類和方法

        2.2 Library

        針對(duì) Library 類型的 Target 內(nèi)混編,我們首先依然需要參照如何實(shí)現(xiàn) Module 化改造。

        • Objective-C 引用 Swift 與 Framework 的引用方式一致,在頭文件內(nèi)添加引入 Swift 的 Interface 頭文件即可,可以訪問(wèn) Swift 中以 @objc 修飾的類和方法,或者 class 修飾為 @objcMembers

        • Swift 引用 Objective-C 有顯式和隱式兩種方式 1、通過(guò)顯式配置橋接文件 BridingHeader,在橋接文件內(nèi) import 對(duì) Swift 類公開的頭文件,用于 Swift 訪問(wèn) Objective-C 頭文件 (Importing Objective-C into Swift:https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/importing_objective-c_into_swift

        不足:無(wú)法開啟跨 Swift 版本兼容的功能

        • OTHER_SWIFT_FLAGS 的標(biāo)記:-import-underlying-module 該構(gòu)件標(biāo)記由 Xcode 隱式創(chuàng)建下層 Module,并隱式引入當(dāng)前 Module 內(nèi)所有的 Objective-C 的公開頭文件,Swift 可以直接訪問(wèn)。該標(biāo)記需要配合 USER_HEADER_SEARCH_PATHS 或者 HEADER_SEARCH_PATHS 來(lái)搜索當(dāng)前 module 所需的公開頭文件

           
        OTHER_SWIFT_FLAGS = $(inherited) -import-underlying-module

        不足:因?yàn)殡[式創(chuàng)建下層 module,也會(huì)將 Swift 的類和方法包含到 Swift 的 Interface 頭文件中,需要在 Swift 的類和方法之前添加 @objc open,經(jīng)測(cè)試發(fā)現(xiàn),這樣會(huì)造成 module 將近一秒延遲(即修改 Swift 的部分接口后 Interface 文件不立即變更)。

        三. 組件間依賴

        組件間依賴調(diào)用的核心依然是 Module 化,否則 Swift 無(wú)法調(diào)用其他組件,下面介紹組件間依賴調(diào)用相關(guān)的 Build Settings 參數(shù)。

        單測(cè)也是組件間依賴的一種,單測(cè)的 Target 依賴其他需要測(cè)試的組件,并且該組件以源碼形態(tài)集成

        集成單測(cè),除了配置組件間依賴的 Build Settings ,還需要注意兩個(gè)要點(diǎn)

        • 第一,需要鏈接對(duì)應(yīng)的靜態(tài)庫(kù)到目標(biāo) testbundle

        • 第二,如果當(dāng)前單測(cè)是 Objective-C 源碼,而依賴的庫(kù)文件包含 Swift 相關(guān)的庫(kù)或 Target,必須在單測(cè)的 Target 內(nèi)添加空的 Swift 占位源文件(空文件真的可以,后綴為 .swift),否則鏈接時(shí)會(huì)報(bào)錯(cuò)。

        3.1 依賴 Framework 組件

        如果依賴組件的Target類型是Framework,So Easy,因?yàn)镕ramework已經(jīng)是一個(gè)module了(包含umbrella header),直接配置BuildSettings:

        • FRAMEWORK_SEARCH_PATHS: 依賴的Framework搜索路徑,在對(duì)應(yīng)的路徑下查找xxx.framework文件

        • OTHER_LDFLAGS:當(dāng)依賴的組件是源碼時(shí),可以有效將依賴的組件順序編譯,根據(jù)Xcode 10.2的升級(jí)說(shuō)明(https://developer.apple.com/documentation/xcode-release-notes/xcode-10_2-release-notes)


        // 當(dāng)依賴組件是二進(jìn)制時(shí),可以不用設(shè)置該項(xiàng)OTHER_LDFLAGS = $(inherited) -framework xxxA -framework xxxB ...

        3.2 依賴Library組件

        當(dāng)依賴組件的Target類型是Library,配置稍微復(fù)雜一點(diǎn):

        3.2.1 當(dāng)前組件包含Objective-C源碼

        • OTHER_CFLAGS:配置當(dāng)前Target依賴的其他Module

        OTHER_CFLAGS = $(inherited) -fmodule-map-file="${path_dir}/xxxA/module.modulemap" -fmodule-map-file="${path_dir}/xxxB/module.modulemap" ...
        • OTHER_LDFLAGS:同 3.1 依賴 Framework 組件

           
        OTHER_LDFLAGS = $(inherited) -l"xxxA" -l"xxxB" ...
        • HEADER_SEARCH_PATHS:配置當(dāng)前 Target 的頭文件搜索路徑,包含依賴的其他 Module 內(nèi)配置的頭文件搜索路徑

        HEADER_SEARCH_PATHS = $(inherited) "${xxxA_public_header_dir}" "${xxxB_public_header_dir}" ...
        3.2.2 當(dāng)前組件包含 Swift 源碼
        • OTHER_SWIFT_FLAGS;配置當(dāng)前 Target 依賴的其他 Module

        OTHER_CFLAGS = $(inherited) -Xcc -fmodule-map-file="${path_dir}/xxxA/module.modulemap" -Xcc -fmodule-map-file="${path_dir}/xxxB/module.modulemap" ...
        3.2.3 依賴 swiftmodule

        當(dāng)依賴的 Library 中包含 Swift 源碼,那么該源碼編譯后將生成 swiftmodule,或依賴 Library 二進(jìn)制中包含 swiftmodule,那么當(dāng)前組件需要配置:

        • SWIFT_INCLUDE_PATHS:依賴組件 swiftmodule 的搜索路徑,需要配置該路徑,目錄下包含 *.swiftmodule

        
           
        SWIFT_INCLUDE_PATHS = $(inherited) "${xxxA_swift_module_dir}" "${xxxB_swift_module_dir}" ...

        3.2.4 編譯順序控制

        當(dāng)依賴的組件是 Library,并且包含 Swift 的源碼,需將當(dāng)前 Target 的 Scheme 編譯條件配置為非并行編譯 uncheck Parallelize Build(如下圖所示),達(dá)到控制編譯順序的目的,避免因?yàn)橐蕾嚱M件還未生成的 *-Swift.h 文件(依賴組件編譯成功后生成),造成當(dāng)前組件源碼的編譯錯(cuò)誤。


        四. 混編組件二進(jìn)制打包

        為了提升產(chǎn)品線的編譯速度,業(yè)界內(nèi)很多產(chǎn)品線均做了組件二進(jìn)制化,即將組件源碼編譯為多種架構(gòu)的二進(jìn)制,并合并架構(gòu)后以二進(jìn)制的方式引入工程,避免了大量源碼的重復(fù)編譯,提升編譯效率,對(duì)于 Swift 的組件來(lái)說(shuō),如何做二進(jìn)制化?

        4.1 module 化

        參考 1.2 Module 化要點(diǎn)

        4.2 兼容性

        雖然 ABI 穩(wěn)定了,但是根據(jù) Swift 的設(shè)計(jì),各自 Swift 編譯器打出的二進(jìn)制并不能在其他版本使用,需要使用到跨 Swift 版本調(diào)用的 interface 文件(在編譯產(chǎn)物 swiftmodule 文件夾中),設(shè)置 BUILD_LIBRARY_FOR_DISTRIBUTION = YES 即可生成,但該標(biāo)記與bridging 沖突,即在混編的 Library 且使用 bridging header 的工程中不可用;如果真要使用 Library 又想 Swift 二進(jìn)制跨 Swift 版本兼容,參考 2.2 介紹的 -import-underlying-module

        4.3 SWIFT_OBJC_INTERFACE_HEADER 文件合并

        對(duì)于 Framework ,Swift 源碼編譯產(chǎn)生的 Objective-C Interface 文件會(huì)被自動(dòng)拷貝到公開頭文件夾,只需要合并多架構(gòu) Interface 頭文件即可;但對(duì)于 Library 則需要先手動(dòng)移動(dòng)頭文件再合并 Interface 頭文件,建議在 BuildPhase 添加 Script Phase 在編譯完成后拷貝操作:

        
           
        // 僅供參考COMPATIBILITY_HEADER_PATH="${公開頭文件目錄}/${PRODUCT_MODULE_NAME}-Swift.h"ditto "${DERIVED_SOURCES_DIR}/${PRODUCT_MODULE_NAME}-Swift.h" "${COMPATIBILITY_HEADER_PATH}"

        不同架構(gòu)的 *-Swift.h 文件的合并方式:

        1. 以 #ifdef 架構(gòu) 的方式進(jìn)行(當(dāng)各架構(gòu)提供的接口沒(méi)有區(qū)別的情況下,可直接使用模擬器架構(gòu))

        2. 合并為 XCFramework 的形式

        4.4 swiftmodule文件合并

        對(duì)于包含 Swift 源碼的產(chǎn)物中將包含 swiftmodule 文件夾,直接合并兩個(gè) swiftmodule 目錄即可,不同架構(gòu)以不同的文件名呈現(xiàn)

        對(duì)于開啟 BUILD_LIBRARY_FOR_DISTRIBUTION 的 module 來(lái)說(shuō),swiftmodule 文件夾內(nèi)包含 *.interface 即為跨 Swift 版本兼容文件

        4.5 合并二進(jìn)制

        使用 lipo 命令進(jìn)行二進(jìn)制架構(gòu)的常規(guī)合并,這里不做贅述

        4.6 二進(jìn)制包

        如下圖:模擬器架構(gòu) Framework 形態(tài)的 *.swiftmodule(.a的 *.swiftmodule與之類似),其中 x86_64-apple-ios-simulator.swiftinterface是跨 Swift 版本調(diào)用的 interface 文件 


        4.7 小知識(shí):swiftmodule 的傳遞依賴性

        已知:有組件 A 依賴組件 B,組件 B 依賴組件 C 在 Objective-C 中,B 對(duì)外暴露的頭文件中引用了 C 的公開頭文件,我們叫組件 B 傳遞依賴 C,結(jié)果就是編譯組件 A 時(shí)必須同時(shí)能找到組件 B 和組件 C 的頭文件,否則編譯失敗。

        然而 Swift 并沒(méi)有公開頭文件一說(shuō),只要組件 B import C,導(dǎo)致 swiftmodule 中也明確標(biāo)記了 import C,當(dāng)組件 A import B 時(shí),也同時(shí) import C ,如果組件 A 找不到組件 C 的 module,那組件 A 將編譯失敗。

        五. 總結(jié)

        對(duì)于百度App 的開發(fā)者來(lái)說(shuō),不用去關(guān)心混編的是如何實(shí)現(xiàn)的,只需要跟正常開發(fā)一樣,組件內(nèi)引用所需的頭文件(#import <ModuleXX/xx.h>)或module(@import ModuleXX),組件間在聲明依賴后亦可直接引用頭文件或 module ,EasyBox 工具鏈會(huì)根據(jù)源碼文件或配置進(jìn)行module 化和 Xcode Build setting 相關(guān)的處理,以下情況將判定為需要 module 化:

        • 存在 .swift 的源碼文件的組件

        • 存在 .swiftmodule 或 *-Swift.h 文件的二進(jìn)制組件

        • 宿主工程的 Boxfile 中顯式配置 module 化

        • 組件的 boxspec 描述中聲明 modulemap 文件

        對(duì)于混編組件的二進(jìn)制打包,開發(fā)者們也不用去關(guān)心如何處理編譯產(chǎn)物,諸如 *-Swift.h、二進(jìn)制架構(gòu)、*.swiftmodule、*.interface等,EasyBox 工具鏈打包命令 box package 會(huì)全權(quán)處理,降低開發(fā)者們的配置難度和協(xié)同成本。

        六. 常見問(wèn)題

        6.1 Swift 組件內(nèi)調(diào)用 Objective-C,只能調(diào)用 Objective-C 的公開頭文件,就不能調(diào)用私有頭文件嗎?

        • 如果組件以源碼的方式被集成,是可行的。

          • Framework 中將私有頭文件聲明為一個(gè)私有 module(modulemap內(nèi)聲明),由組件內(nèi)的 Swift 源碼 import 該私有 module 即可

          • Library 中使用 bridging header

        • 如果組件是以二進(jìn)制方式被集成,則不可以

          • 集成 Framework 二進(jìn)制,由于 Swiftmodule 的傳遞依賴的這個(gè)特性,這種調(diào)用方式將導(dǎo)致其他組件依賴這個(gè)組件的二進(jìn)制時(shí),無(wú)法找到對(duì)應(yīng)的私有 module,導(dǎo)致編譯失敗

          • 集成 Library 二進(jìn)制,由于編譯二進(jìn)制時(shí)無(wú)法同時(shí)開啟 Bridging Header 和 BUILD_LIBRARY_FOR_DISTRIBUTION,開啟 Bridging Header 后該二進(jìn)制將無(wú)法在不同的 Swift 版本下被集成

        6.2 到底使用 Framework 還是 Library?

        建議直接全部使用 Framework ,因?yàn)?Framework 針對(duì) Swift 混編支持非常簡(jiǎn)單

        對(duì)于最低支持版本在 iOS8 及以下的 App,由于 Apple 限制 ipa 中二進(jìn)制包大小為 80M,為了縮小二進(jìn)制體積,一般都采用內(nèi)置動(dòng)態(tài)庫(kù),如果動(dòng)態(tài)庫(kù)也建議使用 Framework,而非動(dòng)態(tài)庫(kù)的 Library

        6.3 App 鏈接一個(gè) Swift 二進(jìn)制時(shí)報(bào)錯(cuò)?

        當(dāng)一個(gè)組件或產(chǎn)物需要鏈接其他 Swift 的產(chǎn)物時(shí),比如 App、單測(cè)、動(dòng)態(tài)庫(kù)等,需要告訴 Xcode 開啟 Swift 鏈接功能,開啟方法就是添加一個(gè) Swift 文件,否則報(bào)錯(cuò)。

        七. 參考

        • 官方文檔

          https://swift.org

        • What are Frameworks?

          https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WhatAreFrameworks.html

        • Clang Module 

          http://clang.llvm.org/docs/Modules.html

        • Importing Objective-c Into Swift 

          https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/importing_objective-c_into_swift

        • Xcode Release Notes 

          https://developer.apple.com/documentation/xcode_release_notes

        • Xcode Build Settings

          https://xcodebuildsettings.com/#category-core-build-system


        相關(guān)文章:


        Reviewer:袁晗光、王文軍、陳松、李政

        -End-

        最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來(lái),可以說(shuō)是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

        點(diǎn)擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

        在看點(diǎn)這里好文分享給更多人↓↓

        瀏覽 65
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 女仆狂揉下部羞羞漫画 | 欧美日本一区 | 国产成人无码精品久久久电影 | 欧美黄色一区二区 | 国产91网红主播在线观看 |