生活随笔
收集整理的這篇文章主要介紹了
iOS之LLVM编译流程和Clang插件开发集成
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
LLVM 簡介
一、什么是 LLVM?
LLVM 是構架編譯器(compiler)的框架系統,以 C++ 編寫而成,用于優化以任意程序語言編寫的程序的編譯時間(compile-time)、鏈接時間(link-time)、運行時間(runtime)以及空閑時間(idle-time),對開發者保持開放,并兼容已有腳本。 LLVM 最早的時候是 Illinois 的一個研究項目,主要負責人是 Chris Lattner,他現在就職于Apple。Apple 目前也是 LLVM 項目的主要贊助者之一。 在理解 LLVM 時,我們可以認為它包括了一個狹義的 LLVM 和一個廣義的 LLVM。廣義的 LLVM 其實就是指整個 LLVM 編譯器架構,包括了前端、后端、優化器、眾多的庫函數以及很多的模塊;而狹義的 LLVM 其實就是聚焦于編譯器后端功能(代碼生成、代碼優化、JIT等)的一系列模塊和庫。
二、傳統編譯器設計
傳統編譯器分三個階段: 前端(Frontend)-> 優化器(Optimizer)-> 后端(Backend); 前端Frontend :負責分析源代碼,可以檢查語法級錯誤(包括詞法分析、語法分析、語義分析),并構建針對語言的抽象語法樹(AST:Abstract Syntax Tree);抽象語法樹可以進一步轉換為優化,最終轉為新的表示方式,然后再交給讓優化器和后端處理,LLVM 的前端還會生成中間代碼(intermediate representation,簡稱IR);最終由后端生成可執行的機器碼(可以理解為 LLVM 是編譯器 + 優化器, 接收的是 IR 中間代碼,輸出的還是 IR,給后端,經過后端翻譯成目標指令集);優化器 Optimizer :優化器負責進行各種優化,改善代碼的運行時間,例如消除冗余計算等;后端 Backend(代碼生成器 Code Generator) :將代碼映射到目標指令集,生成機器代碼,并且進行機器代碼相關的代碼優化; 源碼 Source Code + 前端 Frontend + 優化器 Optimizer + 后端 Backend(代碼生成器 CodeGenerator)+ 機器碼 Machine Code,如下所示:
LLVM 的優點在于,中間表示IR代碼編寫良好,而且不同的前端語言最終都轉換成同一種的IR。
三、iOS 的編譯器架構
OC、C、C++ 使用的編譯器前端是Clang,Swift是swift,后端都是LLVM,如下圖所示:
四、LLVM 的設計
LLVM 設計的最重要方面是,使用通用的代碼表示形式(IR),它是用來在編譯器中表示代碼的形式,所有 LLVM 可以為任何編程語言獨立編寫前端,并且可以為任意硬件架構獨立編寫后端,如下所示:
LLVM的優點: 中間表示IR代碼編寫良好,而且不同的前端語言最終都轉換成統一的中間代碼LLVM IR(LLVM Intermediate Representation); 如果需要支持一種新的變成語言,那么只需要實現一個新的前端; 如果需要支持一種新的硬件設備,那么只需要實現一個新的后端; 優化階段是一個通用的階段,它只針對統一的LLVM IR,不論是支持新的編程語言,還是支持新的硬件設備,都不需要對優化階段做修改; LLVM現在被座位實現何種靜態和運行時編譯語言的通用基礎結構(GCC家族、Java、.NET、Python、Ruby、Scheme、Haskell、D等); 相比之下,GCC的前端和后臺沒分的太開,前端后端耦合在一起,所以GCC為了支持一門新的語言,或者為了支持一個新的目標平臺,就變的特別困難;
五、Clang
clang 是 LLVM 項目中的一個子項目,它是基于 LLVM 架構圖的輕量級編譯器,誕生之初是為了替代 GCC,提供更快的編譯速度,它是負責 C、C++、OC 語言的編譯器,屬于整個 LLVM 架構中的編譯器前端,對于開發者來說,研究 Clang 可以給我們帶來很多好處。 相比于 GCC,Clang 具有如下優點: 編譯速度塊:在某平臺上,Clang 的編譯速度顯著的快過 GCC(Debug 模式下編譯 OC 速度比 GCC 快 3 倍); 占用內存小:Clang 生成的AST所占用的內存是 GCC 的五分之一左右; 模塊化設計:Clang 采用基于庫的模塊化設計,易于 IDE 集成及其他用途的重用; 診斷信息可讀性強:在編譯過程中,Clang 創建并保留了大量纖細的元數據;(metadata),有利于調試和錯誤信息更加友善; 設計清晰簡單,易于理解,擴展性強。
六、Clang 與 LLVM
LLVM編譯流程
一、通過命令打印源碼編譯階段
clang -ccc-print-phases main.m 輸入文件:找到源文件 ± 0: input, “main.m”, objective-c 預處理階段:這個過程處理包括宏的替換,頭文件的導入 ± 1: preprocessor, {0}, objective-c-cpp-output 編譯階段:進行詞法分析、語法分析、檢測語法是否正確,最終生成IR ± 2: compiler, {1}, ir 后端:這里LLVM會通過一個一個的pass去優化,每個pass做一些事情,最終生成匯編代碼 ± 3: backend, {2}, assembler 匯編代碼生成目標文件 ± 4: assembler, {3}, object 鏈接:鏈接需要的動態庫和靜態庫,生成可執行文件 ± 5: linker, {4}, image(鏡像文件) 綁定:通過不同的架構,生成對應的可執行文件 6: bind-arch, “x86_64”, {5}, image 新建一個工程,cd 到 main.m 路徑,然后執行 clang -ccc-print-phases main.m,結果如下:
yangdw@Kody LLVM
% clang
- ccc
- print
- phases main
. m
+ - 0 : input
, "main.m" , objective
- c
+ - 1 : preprocessor
, { 0 } , objective
- c
- cpp
- output
+ - 2 : compiler
, { 1 } , ir
+ - 3 : backend
, { 2 } , assembler
+ - 4 : assembler
, { 3 } , object
+ - 5 : linker
, { 4 } , image
6 : bind
- arch
, "x86_64" , { 5 } , image
二、預處理編譯階段
int test ( int a
, int b
) { return a
+ b
+ 3 ; } int main ( int argc
, const char * argv
[ ] ) { @autoreleasepool
{ int a
= test ( 1 , 2 ) ; printf ( "%d" , a
) ; } return 0 ; }
然后執行 clang -E main.m,可以看到,結果如下:
#
9 "main.m" 2 int test ( int a
, int b
) { return a
+ b
+ 3 ; } int main ( int argc
, const char * argv
[ ] ) { @autoreleasepool
{ int a
= test ( 1 , 2 ) ; printf ( "%d" , a
) ; } return 0 ; }
不難看出,這個階段主要是處理了包括宏的替換和頭文件的導入;
三、編譯階段
① 詞法分析
預處理完成后就會進行詞法分析,這里會把代碼切成一個個Token,比如大小括號、等于號還有字符串等。 通過如下命令查看詞法分析的結果:
clang
- fmodules
- fsyntax
- only
- Xclang
- dump
- tokens main
. m
yangdw@Kody LLVM
% clang
- fmodules
- fsyntax
- only
- Xclang
- dump
- tokens main
. mannot_module_include '#import
< Foundation
/ Foundation
. h
> int test ( int a
, int b
) { return a
+ b
+ 3 ; } int main ( int argc
, con' Loc
= < main
. m
: 8 : 1 > int 'int' [ StartOfLine
] Loc
= < main
. m
: 10 : 1 > identifier
'test' [ LeadingSpace
] Loc
= < main
. m
: 10 : 5 > l_paren
'(' Loc
= < main
. m
: 10 : 9 > int 'int' Loc
= < main
. m
: 10 : 10 > identifier
'a' [ LeadingSpace
] Loc
= < main
. m
: 10 : 14 > comma
',' Loc
= < main
. m
: 10 : 15 > int 'int' Loc
= < main
. m
: 10 : 16 > identifier
'b' [ LeadingSpace
] Loc
= < main
. m
: 10 : 20 > r_paren
')' Loc
= < main
. m
: 10 : 21 > l_brace
'{' Loc
= < main
. m
: 10 : 22 > return 'return' [ StartOfLine
] [ LeadingSpace
] Loc
= < main
. m
: 11 : 5 > identifier
'a' [ LeadingSpace
] Loc
= < main
. m
: 11 : 12 > plus
'+' [ LeadingSpace
] Loc
= < main
. m
: 11 : 14 > identifier
'b' [ LeadingSpace
] Loc
= < main
. m
: 11 : 16 > plus
'+' [ LeadingSpace
] Loc
= < main
. m
: 11 : 18 > numeric_constant
'3' [ LeadingSpace
] Loc
= < main
. m
: 11 : 20 >
clang
- isysroot (自己SDK路徑)
- fmodules
- fsyntax
- only
- Xclang
- dump
- tokens main
. mclang
- isysroot
/ Applications
/ Xcode
. app
/ Contents
/ Developer
/ Platforms
/ iPhoneSimulator
. platform
/ Developer
/ SDKs
/ iPhoneSimulator14
. 1. sdk
/ - fmodules
- fsyntax
- only
- Xclang
- dump
- tokens main
. m
② 語法分析
詞法分析完成后就是語法分析,它的任務是驗證語法是否正確,在詞法分析的基礎上將單詞序列組合成各類此法短語,如程序、語句、表達式等,然后將所有節點組成抽象語法樹(Abstract Syntax TreeAST),語法分析程序判斷程序在結構上是否正確。 可以通過下面命令查看語法分析的結果:
clang
- fmodules
- fsyntax
- only
- Xclang
- ast
- dump main
. m
clang
- isysroot (自己SDK路徑)
- fmodules
- fsyntax
- only
- Xclang
- ast
- dump main
. mclang
- isysroot
/ Applications
/ Xcode
. app
/ Contents
/ Developer
/ Platforms
/ iPhoneSimulator
. platform
/ Developer
/ SDKs
/ iPhoneSimulator14
. 1. sdk
/ - fmodules
- fsyntax
- only
- Xclang
- ast
- dump main
. m
③ 生成中間代碼IR
生成中間代碼IR,代碼生成器(Code Generation)會將語法樹自頂向下遍歷逐步翻譯成LLVM IR; 可以通過下面命令可以生成.ll的文本文件,查看IR代碼。
clang
- S
- fobjc
- arc
- emit
- llvm main
. m
OC 代碼在這一步會進行 runtime 橋接:property合成、ARC處理等; IR 的基本語法: @ 全局標識% 局部標識alloca 開辟空間align 內存對齊i32 32bit 4個字節store 寫入內存load 讀取數據call 調用函數ret 返回 生成的中間代碼.ll文件如下:
IR 文件在OC中是可以進行優化的,一般設置是在target - Build Setting - Optimization Level(優化器等級)中設置。LLVM的優化級別分別是-O0 -O1 -O2 -O3 -Os(第一個是大寫英文字母O),下面是帶優化的生成中間代碼IR的命令:
clang
- Os
- S
- fobjc
- arc
- emit
- llvm main
. m
- o main
. ll
Xcode7 以后開啟 bitcode,蘋果會做進一步優化,生成.bc的中間代碼,通過優化后的IR代碼生成 .bc 代碼:
clang
- emit
- llvm
- c main
. ll
- o main
. bc
四、生成匯編代碼(后端)
clang
- S
- fobjc
- arc main
. bc
- o main
. s clang
- S
- fobjc
- arc main
. ll
- o main
. s
clang
- Os
- S
- fobjc
- arc main
. m
- o main
. s
yangdw@Kody LLVM
% clang
- emit
- llvm
- c main
. ll
- o main
. bcyangdw@Kody LLVM
% clang
- S
- fobjc
- arc main
. bc
- o main
. s yangdw@Kody LLVM
% clang
- Os
- S
- fobjc
- arc main
. m
- o main
. syangdw@Kody LLVM
% file main
. smain
. s
: assembler source text
, ASCII text
五、生成目標文件(編譯器)
目標文件的生成,是匯編器以匯編代碼作為插入,將匯編代碼轉換為機器代碼,最后輸出目標文件(object file)
clang
- fmodules
- c main
. s
- o main
. o
可以通過 nm 命令,查看下 main.o 中的符號:
$xcrun nm
- nm main
. o
以下是 main.o 中的符號,其文件格式為目標文件:
yangdw@Kody LLVM
% clang
- fmodules
- c main
. s
- o main
. oyangdw@Kody LLVM
% $xcrun nm
- nm main
. o ( undefined
) external
_objc_autoreleasePoolPop ( undefined
) external
_objc_autoreleasePoolPush ( undefined
) external _printf
0000000000000000 ( __TEXT
, __text
) external _test
000000000000000 a ( __TEXT
, __text
) external _mainyangdw@Kody LLVM
% file main
. o main
. o
: Mach
- O
64 - bit object x86_64
分析說明: _printf 函數是一個是undefined 、external 的 undefined 表示在當前文件暫時找不到符號_printf external 表示這個符號是外部可以訪問的
六、鏈接
鏈接主要是鏈接需要的動態庫和靜態庫,生成可執行文件,其中 連接器把編譯生成的 .o 文件和 .dyld .a 文件鏈接,生成一個mach-o文件:
clang main
. o
- o main
$xcrun nm
- nm main
其中的undefined表示會在運行時進行動態綁定:
clang main
. o
- o main$xcrun nm
- nm
main ( undefined
) external _printf(from libSystem)
( undefined
) external dyld_stub_binder(from libSystem)
0000000100000000 ( __TEXT
, __text
) [ referenced dynamically
] external __execute_header
0000000100003 f20 ( __TEXT
, __text
) external _test
0000000100003 f40 ( __TEXT
, __text
) external _main
0000000100008008 ( __DATA
, __data
) non_external _dyld_private
查看 main 是什么格式,此時是 mach-o可執行文件:
yangdw@Kody LLVM
% file mainmain
: Mach
- O
64 - bit executable x86_64
七、綁定
綁定主要是通過不同的架構,生成對應的mach-o格式可執行文件
八、LLVM 的編譯流程如下
Clang插件開發
一、LLVM 下載
由于國內網絡限制,需要借助鏡像下載 LLVM 的源碼:LLVM 的鏡像鏈接 下載 LLVM 項目:
git clone https
: 或者git clone https
:
在 LLVM 的 tools 目錄下下載 Clang:
cd llvm
/ toolsgit clone https
:
在 LLVM 的 projects 目錄下下載 compiler-rt、libcxx、libcxxabi:
cd
. . / projectsgit clone https
: itgit clone https
: git clone https
:
在 Clang 的 tools 下安裝 extra 工具:
cd
. . / tools
/ clang
/ toolsgit clone https
:
二、LLVM 編譯
由于最新的 LLVM 只支持 cmake 來編譯,所以需要安裝 cmake。
① 安裝 cmake
查看brew是否安裝cmake,如果已經安裝,則跳過下面步驟:
brew list
brew install cmake
② 通過 Xcode 編譯 LLVM
mkdir build_xcodecd build_xcodecmake
- G Xcode
. . / llvm
如果編譯過程中遇到如下的錯誤:刪除構建目錄下的 CMakeCache.txt 即可。
CMake Error : Error : generator
: Xcode Does not match the generator used previously
: Ninja Either remove the
CMakeCache . txt file and
CMakeFiles directory or choose a different binary directory
.
使用 Xcode 編譯 Clang,選擇自動創建 Schemes:
編譯(CMD + B),選擇 ALL_BUILD Secheme 進行編譯(時間較長,預計一個小時以上)
如果編譯過程中遇到錯誤:error: The i386 architecture is deprecated. You should update your ARCHS build setting to remove the i386 architecture 。 只需要將對應中的 Build Settings 選項 Architectures 中的值切換為 Standard Architectures(64-bit Intel) 即可。 或者:選擇手動創建Schemes,然后編譯編譯 Clang + ClangTooling 即可。
③ 通過 ninja 編譯 LLVM
使用 ninja 進行編譯則還需要安裝 ninja,使用以下命令安裝ninja:
brew install ninja
在 LLVM 源碼根目錄下新建 build_ninja 目錄,最終會在 build_ninja 目錄下生成build.ninja; 在 LLVM 源碼根目錄下新建 llvm_release 目錄,最終編譯文件會在 llvm_release 文件夾路徑下:
cd llvm_buildcmake
- G Ninja
. . / llvm
- DCMAKE_INSTALL_PREFIX
= 安裝路徑(本機為
/ Users
/ xxx
/ xxx
/ LLVM
/ llvm_release)
ninjaninja install
三、創建插件
在 /llvm/tools/clang/tools 目錄下新建插件 YDWPlugin:
在 /llvm/tools/clang/tools 目錄下的 CMakeLists.txt 文件,新增add_clang_subdirectory(YDWPlugin),此處的 YDWPlugin 即為上一步創建的插件名稱;
在 YDWPlugin 目錄下新建兩個文件,分別是 YDWPlugi.cpp 和 CMakeLists.txt,并在CMakeLists.txt 中加上以下代碼:
touch YDWPlugin
. cpptouch CMakeLists
. txt
add_llvm_library ( YDWPlugin MODULE BUILDTREE_ONLY YDWPlugin
. cpp
)
利用 cmake 重新生成 Xcode 項目,在 build_xcode 目錄下執行以下命令:
cmake
- G Xcode
. . / llvm
最后可以在 LLVM 的 Xcode 項目中可以看到 Loadable modules 目錄下由自定義的YDWPlugin 目錄,然后就可以在里面編寫相應的插件代碼。
四、編寫插件代碼
在 YDWPlugin 目錄下的 YDWPlugin.cpp 文件中,加入以下代碼:
# include <iostream>
# include "clang/AST/AST.h"
# include "clang/AST/DeclObjC.h"
# include "clang/AST/ASTConsumer.h"
# include "clang/ASTMatchers/ASTMatchers.h"
# include "clang/Frontend/CompilerInstance.h"
# include "clang/ASTMatchers/ASTMatchFinder.h"
# include "clang/Frontend/FrontendPluginRegistry.h" using namespace clang
;
using namespace std
;
using namespace llvm
;
using namespace clang
:: ast_matchers
;
namespace YDWPlugin
{
class YDWMatchCallback : public MatchFinder:: MatchCallback { private : CompilerInstance
& CI
; bool isUserSourceCode ( const string filename
) { if ( filename
. empty ( ) ) return false ; if ( filename
. find ( "/Applications/Xcode.app/" ) == 0 ) return false ; return true ; } bool isShouldUseCopy ( const string typeStr
) { if ( typeStr
. find ( "NSString" ) != string
:: npos
|| typeStr
. find ( "NSArray" ) != string
:: npos
|| typeStr
. find ( "NSDictionary" ) != string
:: npos
) { return true ; } return false ; } public : YDWMatchCallback ( CompilerInstance
& CI
) : CI ( CI
) { } void run ( const MatchFinder
:: MatchResult
& Result
) { const ObjCPropertyDecl
* propertyDecl
= Result
. Nodes
. getNodeAs
< ObjCPropertyDecl
> ( "objcPropertyDecl" ) ; if ( propertyDecl
&& isUserSourceCode ( CI
. getSourceManager ( ) . getFilename ( propertyDecl
-> getSourceRange ( ) . getBegin ( ) ) . str ( ) ) ) { ObjCPropertyDecl
:: PropertyAttributeKind attrKind
= propertyDecl
-> getPropertyAttributes ( ) ; string typeStr
= propertyDecl
-> getType ( ) . getAsString ( ) ;
if ( propertyDecl
-> getTypeSourceInfo ( ) && isShouldUseCopy ( typeStr
) && ! ( attrKind
& ObjCPropertyDecl
:: OBJC_PR_copy
) ) { DiagnosticsEngine
& diag
= CI
. getDiagnostics ( ) ; diag
. Report ( propertyDecl
-> getBeginLoc ( ) , diag
. getCustomDiagID ( DiagnosticsEngine
:: Warning
, "%0 - 這個地方推薦使用copy!!" ) ) << typeStr
; } } }
} ;
class YDWASTConsumer : public ASTConsumer {
private : MatchFinder matcher
; YDWMatchCallback callback
; public : YDWASTConsumer ( CompilerInstance
& CI
) : callback ( CI
) { matcher
. addMatcher ( objcPropertyDecl ( ) . bind ( "objcPropertyDecl" ) , & callback
) ; } bool HandleTopLevelDecl ( DeclGroupRef D
) {
return true ; } void HandleTranslationUnit ( ASTContext
& context
) {
matcher
. matchAST ( context
) ; }
} ;
class YDWASTAction : public PluginASTAction { public : bool ParseArgs ( const CompilerInstance
& ci
, const std
:: vector
< std
:: string
> & args
) { return true ; } unique_ptr
< ASTConsumer
> CreateASTConsumer ( CompilerInstance
& CI
, StringRef iFile
) { return unique_ptr
< YDWASTConsumer
> ( new YDWASTConsumer ( CI
) ) ; } } ; }
static FrontendPluginRegistry
:: Add
< YDWPlugin
:: YDWASTAction
> YDW ( "YDWPlugin" , "This is YDWPlugin" ) ;
原理分析: ① 注冊插件,并自定義AST語法樹Action類 繼承自 PluginASTAction,自定義 ASTAction,需要重載兩個方法 ParseArgs 和 CreateASTConsumer,其中的重點方法是 CreateASTConsumer,方法中有個參數 CI 即編譯實例對象,主要用于判斷文件是否是屬于用戶 和拋出警告 ; 通過 FrontendPluginRegistry 注冊插件,需要關聯插件名與自定義的 ASTAction 類; ② 掃描配置完畢 繼承自 ASTConsumer 類,實現自定義的子類 YDWASTConsumer,有兩個參數 MatchFinder 對象 matcher 以及 YDWMatchCallback 自定義的回調對象 callback; 實現構造函數,主要是創建 MatchFinder 對象,以及將 CI 傳遞給回調對象; 實現兩個回調方法:HandleTopLevelDecl:解析完一個頂級的聲明,就回調一次; HandleTranslationUnit:整個文件都解析完成的回調,將文件解析完畢后的上下文context(即AST語法樹) 給 matcher; ③ 掃描完畢的回調函數 繼承自 MatchFinder::MatchCallback,自定義回調類 YDWMatchCallback; 定義 CompilerInstance 私有屬性,用于接收 ASTConsumer 類傳遞過來的 CI 信息; 重寫 run 方法: 通過 result,根據節點標記,獲取相應節點,此時的標記需要與 YDWASTConsumer 構造方法中一致; 判斷節點有值,并且是用戶文件即 isUserSourceCode 私有方法; 獲取節點的描述信息; 獲取節點的類型,并轉成字符串; 判斷應該使用 copy,但是沒有使用 copy; 通過 CI 獲取診斷引擎; 通過診斷引擎報告錯誤;
五、測試插件
自己編譯的clang文件路徑
- isysroot
/ Applications
/ Xcode
. app
/ Contents
/ Developer
/ Platforms
/ iPhoneSimulator
. platform
/ Developer
/ SDKs
/ iPhoneSimulator14
. 1. sdk
/ - Xclang
- load
- Xclang 插件
( . dyld
) 路徑
- Xclang
- add
- plugin
- Xclang 插件名
- c 源碼路徑
/ Users
/ XXX
/ Desktop
/ build_xcode
/ Debug
/ bin
/ clang
- isysroot
/ Applications
/ Xcode
. app
/ Contents
/ Developer
/ Platforms
/ iPhoneSimulator
. platform
/ Developer
/ SDKs
/ iPhoneSimulator14
. 1. sdk
/ - Xclang
- load
- Xclang
/ Users
/ XXXX
/ Desktop
/ build_xcode
/ Debug
/ lib
/ YDWPlugin
. dylib
- Xclang
- add
- plugin
- Xclang YDWPlugin
- c
/ Users
/ XXXX
/ Desktop
/ XXX
/ XXXX
/ 測試demo
/ testClang
/ testClang
/ ViewController
. m
/ Users
/ XXX
/ Desktop
/ build_xcode
/ Debug
/ bin
/ clang
- isysroot
/ Applications
/ Xcode
. app
/ Contents
/ Developer
/ Platforms
/ iPhoneSimulator
. platform
/ Developer
/ SDKs
/ iPhoneSimulator14
. 1. sdk
/ - Xclang
- load
- Xclang
/ Users
/ XXXX
/ Desktop
/ build_xcode
/ Debug
/ lib
/ YDWPlugin
. dylib
- Xclang
- add
- plugin
- Xclang YDWPlugin
- c
/ Users
/ XXXX
/ Desktop
/ XXX
/ XXXX
/ 測試demo
/ testClang
/ testClang
/ ViewController
. m
. . . Controller
. m
: 12 : Warning
: -- -- -- - NSString
* 沒用 copy 修飾
-- -- -- -- @
property ( nonatomic
, strong
) NSString
* name
; -- -- -- - NSArray
* 沒用 copy 修飾
-- -- -- -- @
property ( nonatomic
, strong
) NSArray
* list
; . . .
六、Xcode 集成插件
加載插件,打開測試項目,在 target -> Build Settings -> Other C Flags 添加以下內容:
- Xclang
- load
- Xclang ( . dylib
) 動態庫路徑
- Xclang
- add
- plugin
- Xclang YDWPlugin
設置編譯器,由于 Clang 插件需要使用對應的版本去加載,如果版本不一致會導致編譯失敗,如下所示:
在 Build Settings 欄目中新增兩項用戶定義的設置,分別是 CC 和 CXX; CC 對應的是自己編譯的 Clang 的絕對路徑; CXX 對應的是自己編譯的 Clang++ 的絕對路徑;
接下來在 Build Settings 中搜索 index,將 Enable Index-Wihle-Building Functionality 的 Default 改為 NO;
總結
以上是生活随笔 為你收集整理的iOS之LLVM编译流程和Clang插件开发集成 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。