iOS高级强化--008:静态库

什么是库?

库(Library):就是⼀段编译好的⼆进制代码,加上头⽂件就可以供别⼈使⽤。

常⽤库⽂件格式:.a.dylib.framework.xcframework.tdb

什么时候会⽤到库?
  • 某些代码需要给别⼈使⽤,但是我们不希望别⼈看到源码,就需要以库的形式进⾏封装,只暴露出头⽂件
  • 对于某些不会进⾏⼤改动的代码,我们想减少编译的时间,就可以把它打包成库。因为库是已经编译好的⼆进制,编译的时候只需要Link⼀下,不会浪费编译时间
什么是链接?

库(Library)在使⽤的时候需要链接(Link

链接的⽅式有两种:

  • 静态
  • 动态
什么是静态库?

静态库即静态链接库:可以简单的看成⼀组⽬标⽂件的集合。即很多⽬标⽂件经过压缩打包后形成的⽂件。Windows下的.libLinuxMac下的 .aMac独有的.framework

缺点:浪费内存和磁盘空间,模块更新困难

链接静态库
生成目标文件

目录中包含一个test.m文件和AFNetworking三方库

打开test.m文件,写入以下代码:

#import <Foundation/Foundation.h>
#import <AFNetworking.h>

int main(){
   AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
   NSLog(@"testApp----%@", manager);
   return 0;
}

AFNetworking 为静态库,打开AFNetworking目录,里面包含了头文件.a文件

打开终端,进入指定目录,使用file libAFNetworking.a命令,查看.a的文件格式

libAFNetworking.a: current ar archive
  • 从打印结果来看,.a文件是一个文档格式

使用ar -t libAFNetworking.a命令,查看.a文件

  • .a中包含了AFNetworking编译出来的所有目标文件,验证了静态库其实就是.o文件的合集

使用man clang查看clang命令

  • clangCC++Objective-C的编译器。它也是一个工具的合集,包含了预处理、解析、优化、代码生成、汇编化、链接

使用clang命令,将.m文件编译成.o文件

clang -x objective-c \
-target x86_64-apple-ios14-simulator \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.3.sdk \
-I ./AFNetworking \
-c test.m -o test.o
  • -x:指定编译文件语言类型
  • -target:指定生成架构
  • -fobjc-arc:使用ARC
  • -isysroot:使用SDK的路径
  • -I:指定头文件路径Header Search Paths
  • -c:生成目标文件
  • -o:输出文件

此时目录中生成了.o目标文件

  • 目标文件中包含重定位符号表,它保存了文件中使用的所有符号,链接时会根据重定位符号表生成具体的符号信息
  • 所以在生成目标文件时,只需静态库的头文件即可。重定位符号表只需要记录哪个地方的符号需要重定位,然后在链接过程中会自动将符号重定位
生成可执行文件

使用clang命令,将.o文件链接成可执行文件

clang -target x86_64-apple-ios14-simulator \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.3.sdk \
-L ./AFNetworking \
-lAFNetworking \
test.o -o test
  • -L:指定库文件路径(.a\.dylib库文件)Library Search Paths
  • -l:指定链接的库文件名称(.a\.dylib库文件)Other Linker Flags -lAFNetworking

此时目录中生成了test可执行文件

  • 链接成可执行文件,会将重定位符号表中的符号进行重定位,此时需要知道符号的真实地址,而真实地址保存在静态库的.o文件中,需要指定库文件路径和将要链接的库文件名称
  • 库文件名称的查找规则:先找lib+<library_name>的动态库,找不到,再去找lib+<library_name>的静态库,还找不到,就报错

静态库链接成功的三要素:

  • -I:指定头文件路径Header Search Paths
  • -L:指定库文件路径(.a\.dylib库文件)Library Search Paths
  • -l:指定链接的库文件名称(.a\.dylib库文件)Other Linker Flags -lAFNetworking
链接静态库的原理

静态库是.o文件的合集,下面我们证明这一点:

生成可执行文件

项目中包含test.m文件和一个StaticLibrary子目录,StaticLibrary目录下包含TestExample.h文件和TestExample.m文件

打开TestExample.h文件,写入以下代码:

#import <Foundation/Foundation.h>

@interface TestExample : NSObject

- (void)lg_test:(_Nullable id)e;

@end

打开TestExample.m文件,写入以下代码:

#import "TestExample.h"

@implementation TestExample

- (void)lg_test:(_Nullable id)e {
   NSLog(@"TestExample----");
}

@end

使用clang命令,将TestExample.m文件编译成.o文件

clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-c TestExample.m -o TestExample.o

此时目录中生成了TestExample.o目标文件

如果只有一个.o文件,那它自身就相当于这个合集。将TestExample.o文件重命名为libTestExample.dylib(改为.a格式也可以)

使用file libTestExample.dylib命令,查看libTestExample.dylib的文件格式

libTestExample.dylib: Mach-O 64-bit object x86_64
  • 此时libTestExample.dylib依然是目标文件

打开test.m文件,写入以下代码:

#import <Foundation/Foundation.h>
#import "TestExample.h"

int main(){
   NSLog(@"testApp----");
   TestExample *manager = [TestExample new];
   [manager lg_test: nil];
   return 0;
}

使用clang命令,将test.m文件编译成.o文件

clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-I ./StaticLibrary \
-c test.m -o test.o
  • test.m文件使用了TestExample中的代码,所以要使用-I参数,指定头文件路径

此时目录中生成了test.o目标文件

使用clang命令,将test.o文件链接成可执行文件

clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-L ./StaticLibrary \
-lTestExample \
test.o -o test

此时目录中生成了test可执行文件

运行可执行文件

使用lldb命令,在终端中进入lldb环境

使用file test命令,将test可执行文件包装成一个target

Current executable set to '/Users/zang/Zang/Spark/test' (x86_64).

使用r命令,开始运行

Process 96467 launched: '/Users/zang/Zang/Spark/test' (x86_64)
2021-02-26 18:10:47.713761+0800 test[96467:6978980] testApp----
2021-02-26 18:10:47.713991+0800 test[96467:6978980] TestExample----
Process 96467 exited with status = 0 (0x00000000)
  • 执行成功,打印的内容正是test.mTestExample.m输出的内容

使用q,退出lldb环境

使用objdump --macho --private-header libTestExample.dylib命令,查看libTestExample.dylib文件的Mach Header信息

Mach header
     magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64  X86_64        ALL  0x00      OBJECT     4       1160 SUBSECTIONS_VIA_SYMBOLS
  • 通过filetype可以看出,libTestExample.dylib依然是一个目标文件

由此证明:静态库就是.o文件的合集

静态库的合并

静态库合并的两种方式:

  • 使用ar命令
  • 使用Xcode提供的libtool命令
ar命令

使用man ar,查看ar命令

  • 可以查看静态库
  • 也能将多个.o文件合并成静态库
  • 可以将静态库中包含的.o文件全部解压出来

目录下,分别是libAFNetworking.alibSDWebImage.a两个静态库

使用ar x libAFNetworking.a命令,解压libAFNetworking.a静态库

使用ar x libSDWebImage.a命令,解压libSDWebImage.a静态库

使用ar r lib_AF_SD.a *.o命令,将目录下的所有.o文件,合并成一个.a文件

使用objdump --macho --rebase lib_AF_SD.a命令,查看lib_AF_SD.a的重定位符号表

  • 此时lib_AF_SD.a文件中,包含了libAFNetworking.alibSDWebImage.a的所有.o文件,相当于将两个静态库进行合并
libtool

使用man libtool,查看libtool命令

  • 可以创建库文件
  • 可以添加或更新一系列的静态库文件

使用libtool命令,合并libAFNetworking.alibSDWebImage.a两个静态库

libtool -static \
-o \
libCat.a \
libAFNetworking.a \
libSDWebImage.a

目录下,成功合并出libCat.a静态库

使用objdump --macho --rebase libCat.a命令,查看libCat.a的重定位符号表

  • 此时libCat.a文件中,包含了libAFNetworking.alibSDWebImage.a的所有.o文件,相当于将两个静态库进行合并
mudule

muduleclang提供的解析.h头文件的一种解析格式

mudule可以把.h头文件预先编译成二进制,存储到系统缓存目录中。好处:当编译.m文件时,避免反复编译.h文件

例如:当一个.h文件在很多.m中被import,当这些.m文件被编译时,不需要一遍又一遍的重复编译.h文件,它会直接使用mudule预先编译好的二进制

Auto-Link

Auto-LinkLC_LINKER_OPTION链接器的特性。启用该特性后,在import <模块>时不需要再往链接器去配置链接参数

例如:import <Framework>,代码中使用这个Framework格式的库文件,在生成目标文件时,会自动在目标文件的Mach-O中,插入一个load command,格式是LC_LINKER_OPTION,存储链接器参数-framework <Framework>

Framework

Mac OS/iOS平台还可以使⽤Framework

Framework实际上是⼀种打包⽅式,将库的⼆进制⽂件,头⽂件和有关的资源⽂件打包到⼀起,⽅便管理和分发

Framework和系统的UIKit.Framework还是有很⼤区别。系统的Framework不需要拷⻉到⽬标程序中,而自定义的Framework哪怕是动态的,最后也要拷⻉到App中(AppExtensionBundle是共享的),因此苹果⼜把这种Framework称为Embedded Framework

Framework可以是静态库,也可以是动态库

  • 无论是静态库还是动态库,Framework都包含了头文件、库的原始文件、签名和资源文件
  • Framework是静态库还是动态库,取决于库的原始文件
Embedded Framework

开发中使⽤的动态库会被放⼊到ipa下的framework⽬录中,基于沙盒运⾏

不同的App使⽤相同的动态库,并不会只在系统中存在⼀份。⽽是会在多个App中各⾃打包、签名、加载一份

App Framework存放位置

手动创建Framework

创建Frameworks目录,目录中包含TestExample.h文件和TestExample.m文件

打开TestExample.h文件,写入以下代码:

#import <Foundation/Foundation.h>

@interface TestExample : NSObject

- (void)lg_test:(_Nullable id)e;

@end

打开TestExample.m文件,写入以下代码:

#import "TestExample.h"

@implementation TestExample

- (void)lg_test:(_Nullable id)e {
   NSLog(@"TestExample----");
}

@end

使用clang命令,将TestExample.m文件编译成.o文件

clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-c TestExample.m -o TestExample.o

此时目录中生成了TestExample.o目标文件

使用ar -rc libTestExample.a TestExample.o命令,生成libTestExample.a文件

Frameworks目录下,手动创建TestExample.framework静态库

  • Frameworks目录下,创建TestExample.framework目录
  • TestExample.framework目录下,创建Headers目录
  • TestExample.h头文件移动到TestExample.framework/Headers目录下
  • libTestExample.a库文件移动到TestExample.framework目录下,和Headers目录平级
  • 重命名libTestExample.a库文件,去掉lib开头,去掉.a后缀名

创建test.m文件,和Frameworks目录平级

打开test.m文件,写入以下代码:

#import <Foundation/Foundation.h>
#import "TestExample.h"

int main(){
   NSLog(@"testApp----");
   TestExample *manager = [TestExample new];
   [manager lg_test: nil];
   return 0;
}

使用clang命令,将test.m文件编译成.o文件

clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-I ./Frameworks/TestExample.frameworks/Headers \
-c test.m -o test.o

此时目录中生成了test.o目标文件

使用clang命令,将test.o文件链接成可执行文件

clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-F ./Frameworks \
-framework TestExample \
test.o -o test
  • -F:指定Framework所在目录Framework Search Paths
  • -framework:指定链接Framework的名称Other Linker Flags -framework AFNetworking

使用lldb命令,进入lldb终端。使用file test命令,将test可执行文件包装成一个target

Current executable set to '/Users/zang/Zang/Spark/Framework/test' (x86_64).

使用r命令,开始运行

Process 6365 launched: '/Users/zang/Zang/Spark/Framework/test' (x86_64)
2021-03-02 15:44:56.562585+0800 test[6365:7259836] testApp----
2021-03-02 15:44:56.562824+0800 test[6365:7259836] TestExample----
Process 6365 exited with status = 0 (0x00000000)
  • 执行成功,打印的内容正是test.mTestExample.m输出的内容

Framework链接成功的三要素:

  • -I:指定头文件路径Header Search Paths
  • -F:指定Framework所在目录Framework Search Paths
  • -framework:指定链接Framework的名称Other Linker Flags -framework AFNetworking

Shell脚本的使用

Shell是一门解释型的编程语言(脚本语言),它的解释器就Shell这个程序。作为解释型语言,Shell语言具有明显的“胶水语言”的特性,并且这种特性由于其直接运行在Shell中而被放大;通过Shell编程可以极大地缓解“重复的人机交互命令”给使用者带来的疲劳,实现办公自动化。

使用Shell脚本,将.o文件链接成可执行文件,实现自动化

创建build.sh文件,和test.m文件平级

按以下步骤写入代码:

【步骤一】:使用clang命令,将test.m文件编译成.o文件

echo "-------------编译test.m to test.o------------------"
clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-I ./StaticLibrary \
-c test.m -o test.o
  • echo命令,用于字符串输出,一般起到提示作用

【步骤二】:进入StaticLibrary目录

echo "-------------进入到StaticLibrary目录------------------"
pushd ./StaticLibrary
  • 使用cd命令,也可以进入一个目录,但这里不推荐使用,因为cd会直接修改目录栈里最上层的元素,修改后无法返回
  • 推荐使用pushd命令,pushd是往目录栈中重新push一个目录,修改后可以使用popd命令返回

【步骤三】:使用clang命令,将TestExample.m文件编译成.o文件

echo "-------------编译TestExample.m to TestExample.o------------------"
clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-c TestExample.m -o TestExample.o

【步骤四】:使用ar命令,生成libTestExample.a文件

echo "-------------TestExample.o to libTestExample.a------------------"
ar -rc libTestExample.a TestExample.o

【步骤五】:退出StaticLibrary目录

echo "-------------退出StaticLibrary目录------------------"
popd

【步骤六】:使用clang命令,将test.o文件链接成可执行文件

echo "-------------将test.o链接成可执行文件------------------"
clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-L ./StaticLibrary \
-lTestExample \
test.o -o test
  • 此时build.sh的脚本代码全部完成

打开终端,使用chmod命令,为build.sh文件增加可执行权限

chmod +x ./build.sh

使用ls -all命令,查看文件权限

drwxr-xr-x@  5 zang  staff   160  3  2 16:56 StaticLibrary
-rwxr-xr-x@  1 zang  staff  1126  3  2 16:50 build.sh
-rw-r--r--@  1 zang  staff   196  1 20 12:07 test.m

使用./build.sh命令,执行Shell脚本

-------------编译test.m to test.o------------------
-------------进入到StaticLibrary目录------------------
~/Zang/Spark/Shell/StaticLibrary ~/Zang/Spark/Shell
-------------编译TestExample.m to TestExample.o------------------
-------------TestExample.o to libTestExample.a------------------
-------------退出StaticLibrary目录------------------
~/Zang/Spark/Shell
-------------将test.o链接成可执行文件------------------

执行成功,目录下自动生成test可执行文件

优化Shell脚本,将脚本内多次出现的参数提取成变量

LANGUAGE=objective-c
TAREGT=x86_64-apple-macos11.1
SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk

FILE_NAME=test
STATICLIBRARY=TestExample
HEAD_PATH=./StaticLibrary
LIBRARY_PATH=./StaticLibrary

echo "-------------编译test.m to test.o------------------"
clang -x $LANGUAGE  \
-target $TAREGT     \
-fobjc-arc          \
-isysroot $SYSROOT  \
-I${HEAD_PATH}   \
-c ${FILE_NAME}.m -o ${FILE_NAME}.o

echo "-------------进入到StaticLibrary目录------------------"
pushd ${HEAD_PATH}

echo "-------------编译TestExample.m to TestExample.o------------------"
clang -x $LANGUAGE  \
-target $TAREGT     \
-fobjc-arc          \
-isysroot $SYSROOT  \
-c ${STATICLIBRARY}.m -o ${STATICLIBRARY}.o

echo "-------------TestExample.o to libTestExample.a------------------"
ar -rc lib${STATICLIBRARY}.a ${STATICLIBRARY}.o

echo "-------------退出StaticLibrary目录------------------"
popd

echo "-------------将test.o链接成可执行文件------------------"
clang -target $TAREGT   \
-fobjc-arc              \
-isysroot $SYSROOT      \
-L${LIBRARY_PATH}       \
-l${STATICLIBRARY}      \
${FILE_NAME}.o -o $FILE_NAME
  • 使用变量的方式,可以通过$${}两种方式
  • $:当没有其他内容的拼接,可以直接使用$XXX
  • ${}:变量前后有其他内容的拼接,例如:${STATICLIBRARY}.m,需要使用${XXX}
-noall_load

链接库文件的过程中,默认设置为-noall_load,符合剥离条件的代码全部剥离

打开test.m文件,只导入TestExample.h头文件,不使用TestExample.m的任何代码

#import <Foundation/Foundation.h>
#import "TestExample.h"

int main(){
   NSLog(@"testApp----");
//    TestExample *manager = [TestExample new];
//    [manager lg_test: nil];
   return 0;
}

使用./build.sh命令,链接成可执行文件

使用objdump --macho -d test命令,查看__TEXT代码段信息

(__TEXT,__text) section
_main:
100003f60:  55  pushq   %rbp
100003f61:  48 89 e5    movq    %rsp, %rbp
100003f64:  48 83 ec 10 subq    $16, %rsp
100003f68:  48 8d 05 99 00 00 00    leaq    153(%rip), %rax ## Objc cfstring ref: @"testApp----"
100003f6f:  c7 45 fc 00 00 00 00    movl    $0, -4(%rbp)
100003f76:  48 89 c7    movq    %rax, %rdi
100003f79:  b0 00   movb    $0, %al
100003f7b:  e8 08 00 00 00  callq   0x100003f88 ## symbol stub for: _NSLog
100003f80:  31 c0   xorl    %eax, %eax
100003f82:  48 83 c4 10 addq    $16, %rsp
100003f86:  5d  popq    %rbp
100003f87:  c3  retq
  • 只有一个main函数
  • 说明clang在链接过程中,默认就是-noall_load

打开test.m文件,使用TestExample.mlg_test方法

#import <Foundation/Foundation.h>
#import "TestExample.h"

int main(){
   NSLog(@"testApp----");
   TestExample *manager = [TestExample new];
   [manager lg_test: nil];
   return 0;
}

使用./build.sh命令,链接成可执行文件

使用objdump --macho -d test命令,查看__TEXT代码段信息

(__TEXT,__text) section
-[TestExample lg_test:]:
100003e70:  55  pushq   %rbp
100003e71:  48 89 e5    movq    %rsp, %rbp
100003e74:  48 83 ec 20 subq    $32, %rsp
100003e78:  48 89 7d f8 movq    %rdi, -8(%rbp)
100003e7c:  48 89 75 f0 movq    %rsi, -16(%rbp)
100003e80:  48 c7 45 e8 00 00 00 00 movq    $0, -24(%rbp)
100003e88:  48 8d 7d e8 leaq    -24(%rbp), %rdi
100003e8c:  48 89 d6    movq    %rdx, %rsi
100003e8f:  e8 a4 00 00 00  callq   0x100003f38 ## symbol stub for: _objc_storeStrong
100003e94:  48 8d 05 75 01 00 00    leaq    373(%rip), %rax ## Objc cfstring ref: @"TestExample----"
100003e9b:  48 89 c7    movq    %rax, %rdi
100003e9e:  b0 00   movb    $0, %al
100003ea0:  e8 87 00 00 00  callq   0x100003f2c ## symbol stub for: _NSLog
100003ea5:  31 c9   xorl    %ecx, %ecx
100003ea7:  89 ce   movl    %ecx, %esi
100003ea9:  48 8d 7d e8 leaq    -24(%rbp), %rdi
100003ead:  e8 86 00 00 00  callq   0x100003f38 ## symbol stub for: _objc_storeStrong
100003eb2:  48 83 c4 20 addq    $32, %rsp
100003eb6:  5d  popq    %rbp
100003eb7:  c3  retq
100003eb8:  90  nop
100003eb9:  90  nop
100003eba:  90  nop
100003ebb:  90  nop
100003ebc:  90  nop
100003ebd:  90  nop
100003ebe:  90  nop
100003ebf:  90  nop
_main:
100003ec0:  55  pushq   %rbp
100003ec1:  48 89 e5    movq    %rsp, %rbp
100003ec4:  48 83 ec 10 subq    $16, %rsp
100003ec8:  48 8d 05 61 01 00 00    leaq    353(%rip), %rax ## Objc cfstring ref: @"testApp----"
100003ecf:  c7 45 fc 00 00 00 00    movl    $0, -4(%rbp)
100003ed6:  48 89 c7    movq    %rax, %rdi
100003ed9:  b0 00   movb    $0, %al
100003edb:  e8 4c 00 00 00  callq   0x100003f2c ## symbol stub for: _NSLog
100003ee0:  48 8b 0d e9 41 00 00    movq    16873(%rip), %rcx ## Objc class ref: TestExample
100003ee7:  48 89 cf    movq    %rcx, %rdi
100003eea:  e8 43 00 00 00  callq   0x100003f32 ## symbol stub for: _objc_opt_new
100003eef:  31 d2   xorl    %edx, %edx
100003ef1:  48 89 45 f0 movq    %rax, -16(%rbp)
100003ef5:  48 8b 45 f0 movq    -16(%rbp), %rax
100003ef9:  48 8b 35 c8 41 00 00    movq    16840(%rip), %rsi ## Objc selector ref: lg_test:
100003f00:  48 89 c7    movq    %rax, %rdi
100003f03:  ff 15 f7 00 00 00   callq   *247(%rip) ## Objc message: +[TestExample lg_test:]
100003f09:  45 31 c0    xorl    %r8d, %r8d
100003f0c:  44 89 c6    movl    %r8d, %esi
100003f0f:  c7 45 fc 00 00 00 00    movl    $0, -4(%rbp)
100003f16:  48 8d 45 f0 leaq    -16(%rbp), %rax
100003f1a:  48 89 c7    movq    %rax, %rdi
100003f1d:  e8 16 00 00 00  callq   0x100003f38 ## symbol stub for: _objc_storeStrong
100003f22:  8b 45 fc    movl    -4(%rbp), %eax
100003f25:  48 83 c4 10 addq    $16, %rsp
100003f29:  5d  popq    %rbp
100003f2a:  c3  retq
  • 除了之前的main函数,还多了lg_test方法
  • 说明在链接过程中,将多个.o文件中的代码和符号放到一起,最终链接出一个可执行文件

使用-noall_load的问题

OC中,分类是在运行时动态创建的,但-noall_load参数在链接时已经生效。在链接时发现分类的代码没有被使用,就会将其剥离

搭建LGStaticFramework静态库

打开LGOneObject+Category.h文件,写入以下代码:

#import <LGOneObject.h>

@interface LGOneObject (Category)

- (void)lg_test_category;

@end

打开LGOneObject+Category.m文件,写入以下代码:

#import "LGOneObject+Category.h"

@implementation LGOneObject (Category)

- (void)lg_test_category {
   NSLog(@"lg_test_category");
}

@end

打开LGOneObject.h文件,写入以下代码:

#import <Foundation/Foundation.h>

@interface LGOneObject : NSObject

- (void)lg_test;

@end

打开LGOneObject.m文件,写入以下代码:

#import "LGOneObject.h"
#import <LGOneObject+Category.h>

@implementation LGOneObject

- (void)lg_test {
   [self lg_test_category];
}

@end

打开LGStaticFramework.h文件,写入以下代码:

#import <Foundation/Foundation.h>
#import <LGStaticFramework/LGOneObject.h>

导入LGApp项目,通过LGApp链接LGStaticFramework静态库

使用workspace搭建多项目合集

  • 可重⽤性。多个模块可以在多个项⽬中使⽤。节约开发和维护时间
  • 节省测试时间。单独模块意味着每个模块中都可以添加测试功能
  • 更好的理解模块化思想

选择File->Save As Workspace...

在项目根目录下创建TestDeadStrip.xcworkspace

关闭Xcode,来到项目根目录,使用TestDeadStrip.xcworkspace打开项目

点击左下角+,选择Add Files to “TestDeadStrip”...

  • 注意:上面一定不要选择任何文件,先叉掉所有文件,再点+

找到一个已有项目,选择LGApp.xcodeproj,点击Add

LGApp项目,加入到TestDeadStrip.xcworkspace

将静态库添加到LGApp项目

选择LGStaticFramework.framework

添加成功后,当编译LGApp项目时,静态库会一起编译

将静态库的Embed选择为Do Not Embed

  • Do Not Embed:不会将Framework拷贝到IPA包里。用于静态库
    项目使用的Framework是静态库,链接时静态库的代码和符号会跟App进行合并,故此不需要将Framework拷贝到IPA包里
  • Embed & Sign:将Framework拷贝到IPA包里,并进行签名操作。用于动态库
  • Embed Without Signing:如果Framework已有正确签名,可以使用此项

LGApp项目中,打开ViewController.m文件,写入以下代码:

#import "ViewController.h"
#import <LGStaticFramework/LGOneObject.h>

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   LGOneObject *obj = [LGOneObject new];
   [obj lg_test];
}

@end

运行项目后,程序直接崩溃

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[LGOneObject lg_test_category]: unrecognized selector sent to instance 0x6000002b4240'
  • 崩溃的原因就是在于-noall_load,因为分类是在运行时动态创建的,但-noall_load参数导致在链接时,已经将静态库的分类代码剥离

解决此问题,需要借助链接器的参数配置

打开LGApp项目,创建xcconfig,写入以下代码:

LGSTATIC_FRAMEWORK_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/LGStaticFramework.framework/LGStaticFramework
OTHER_LDFLAGS=-Xlinker -force_load ${LGSTATIC_FRAMEWORK_PATH}
  • -noall_load:默认值,符合剥离条件的代码全部剥离
  • -all_load:不要剥离任何代码
  • -ObjCOC代码不要剥离
  • -force_load:指定某个静态库不要剥离代码,参数后面需要拼接静态库路径

xcconfig配置到Tatget

再次运行项目,分类方法被成功调用,打印lg_test_category

-noall_load和Dead Code Stripping区别

-noall_loadXcodeDead Code Stripping配置项,完全不是一个东西

-noall_load

  • 链接库文件时,控制死代码剥离的参数之一,符合剥离条件的代码全部剥离
  • ld中有-noall_load-all_load-ObjC-force_load四种参数的配置

Dead Code Stripping

  • 在链接过程中,链接器提供的一种代码优化方式
  • ld中指定-dead_strip参数,决定此功能是否启用。启用后,没有被入口点或导出符号使用的函数和数据将被删除

打开test.m文件,写入以下代码:

#import <Foundation/Foundation.h>
#import "TestExample.h"

void global_function() {
}

int main(){
//    global_function();
   NSLog(@"testApp----");
//    TestExample *manager = [TestExample new];
//    [manager lg_test: nil];
   return 0;
}
  • TestExample是静态库,在test.m文件中只导入了.h头文件,没有使用里面的代码
  • global_function是一个全局函数,但并没有被调用

打开build.sh脚本,最后一步链接成可执行文件,增加-dead_strip参数

echo "-------------将test.o链接成可执行文件------------------"
clang -target $TAREGT   \
-fobjc-arc              \
-isysroot $SYSROOT      \
-Xlinker -dead_strip    \
-L${LIBRARY_PATH}       \
-l${STATICLIBRARY}      \
${FILE_NAME}.o -o $FILE_NAME
  • -dead_strip:启用代码优化,没有被入口点或导出符号使用的函数和数据将被删除

使用./build.sh命令,链接成可执行文件

使用objdump --macho --syms test命令,查看test符号表

SYMBOL TABLE:
0000000100008008 l     O __DATA,__data __dyld_private
0000000100000000 g     F __TEXT,__text __mh_execute_header
0000000100003f60 g     F __TEXT,__text _main
0000000000000000         *UND* _NSLog
0000000000000000         *UND* ___CFConstantStringClassReference
0000000000000000         *UND* dyld_stub_binder
  • 设置-dead_strip参数后,global_function的符号被剥离了。虽然它是全局符号,也是导出符号,但它没有被入口点或其他导出符号使用,所以被剥离
  • 由于链接静态库时,默认为-noall_load,符合剥离条件的代码全部剥离,所以静态库相关代码也被剥离

打开build.sh脚本,增加-all_load参数

echo "-------------将test.o链接成可执行文件------------------"
clang -target $TAREGT   \
-fobjc-arc              \
-isysroot $SYSROOT      \
-Xlinker -dead_strip    \
-Xlinker -all_load      \
-L${LIBRARY_PATH}       \
-l${STATICLIBRARY}      \
${FILE_NAME}.o -o $FILE_NAME
  • -all_load:针对静态库,不要剥离任何代码

使用./build.sh命令,链接成可执行文件

使用objdump --macho --syms test命令,查看test符号表

SYMBOL TABLE:
0000000100003460 l     F __TEXT,__text -[TestExample lg_test:]
00000001000034e0 l     F __TEXT,__text ___cpu_indicator_init
0000000100003d60 l     F __TEXT,__text ___cpu_indicator_init.cold.1
0000000100003d80 l     F __TEXT,__text ___cpu_indicator_init.cold.2
0000000100008018 l     O __DATA,__objc_const __OBJC_METACLASS_RO_$_TestExample
0000000100008060 l     O __DATA,__objc_const __OBJC_$_INSTANCE_METHODS_TestExample
0000000100008080 l     O __DATA,__objc_const __OBJC_CLASS_RO_$_TestExample
0000000100008118 l     O __DATA,__data __dyld_private
0000000100008120 l     O __DATA,__common ___cpu_model
0000000100008130 l     O __DATA,__common ___cpu_features2
00000001000080f0 g     O __DATA,__objc_data _OBJC_CLASS_$_TestExample
00000001000080c8 g     O __DATA,__objc_data _OBJC_METACLASS_$_TestExample
0000000100000000 g     F __TEXT,__text __mh_execute_header
00000001000034b0 g     F __TEXT,__text _main
0000000000000000         *UND* _NSLog
0000000000000000         *UND* _OBJC_CLASS_$_NSObject
0000000000000000         *UND* _OBJC_METACLASS_$_NSObject
0000000000000000         *UND* ___CFConstantStringClassReference
0000000000000000         *UND* ___assert_rtn
0000000000000000         *UND* __objc_empty_cache
0000000000000000         *UND* _objc_storeStrong
0000000000000000         *UND* dyld_stub_binder
  • 设置-all_load参数后,静态库的符号全部被保留下来
  • global_function符号依然被剥离了,因为控制它的是-dead_strip参数,而-all_load只针对静态库有效

由此证明:-noall_loadXcodeDead Code Stripping配置项,完全不是一个东西

查看指定符号的使用链

打开test.m文件,写入以下代码:

#import <Foundation/Foundation.h>

void global_function() {
}

int main(){
   global_function();
   NSLog(@"testApp----");
   return 0;
}

打开build.sh脚本,最后一步链接成可执行文件,增加-why_live -Xlinker 【符号名称】参数

echo "-------------将test.o链接成可执行文件------------------"
clang -target $TAREGT   \
-fobjc-arc              \
-isysroot $SYSROOT      \
-Xlinker -dead_strip    \
-Xlinker -all_load      \
-Xlinker -why_live -Xlinker _global_function \
-L${LIBRARY_PATH}       \
-l${STATICLIBRARY}      \
${FILE_NAME}.o -o $FILE_NAME

使用./build.sh命令,链接成可执行文件

-------------将test.o链接成可执行文件------------------
_global_function from test.o
 _main from test.o
   _main from test.o
  • 终端打印出_global_function符号的使用链,它被test.o中的main函数使用
Link-Time Optimization

多个.o文件链接成可执行文件,和.o链接静态库有一些差异:

  • .o链接成可执行文件:先将多个.o文件合并成一个大的.o文件,再去链接成可执行文件。先组合再链接,故此Dead Code Stripping的优化无法生效
  • .o链接静态库:相当于.o直接使用静态库。先Dead Code Stripping再使用

此时可以使用链接器提供的另一个参数,LTOLink-Time Optimization)链接时间优化

  • 使用Monolothic选项,在多个.o文件链接时进行优化,此优化在Dead Code Stripping之后触发
总结:

静态库链接成功的三要素:

  • 指定头文件路径Header Search Paths
  • 指定库文件路径(.a\.dylib库文件)Library Search Paths
  • 指定链接的库文件名称(.a\.dylib库文件)Other Linker Flags -lAFNetworking

Framework链接成功的三要素:

  • 指定头文件路径Header Search Paths
  • 指定Framework所在目录Framework Search Paths
  • 指定链接Framework的名称Other Linker Flags -framework AFNetworking
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,841评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,415评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,904评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,051评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,055评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,255评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,729评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,377评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,517评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,420评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,467评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,144评论 3 317
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,735评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,812评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,029评论 1 256
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,528评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,126评论 2 341

推荐阅读更多精彩内容