美文网首页iOS 大神们
iOS高级强化--008:静态库

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

作者: 帅驼驼 | 来源:发表于2021-03-03 11:56 被阅读0次
    什么是库?

    库(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

    相关文章

      网友评论

        本文标题:iOS高级强化--008:静态库

        本文链接:https://www.haomeiwen.com/subject/ghdmfltx.html