likes
comments
collection
share

iOS 静态库动态库看这里

作者站长头像
站长
· 阅读数 32

问题

先列举一下本文要讲述的问题~解答后会通过演示证明

  • 1、什么是动态库?
  • 2、什么是动态库?
  • 3、动态库和静态库的区别是什么?
  • 4、动态库、静态库、framework是什么关系?
  • 5、动态库和静态库链接到主程序以后放在什么位置?
  • 6、什么是dead strip
  • 7、-all_load、-noall_load、-ObjC、-force_load参数的区别?
  • 8、什么是tbd文件?
  • 9、动态库和静态库的选择?
  • 10、为什么项目里动态库不能超过六个?
  • 11、怎么剥离动态库里不需要的架构?

1、什么是静态库?

静态库是静态链接库;是多个目标文件经过压缩打包后形成的文件包。以下都是静态库的类型

  • Windows 的 .lib
  • Linux 的 .a
  • MacOS 独有的 .framework

2、什么是动态库?

  • 动态库是动态链接库,是实现共享函数库的一种方式。
  • 动态库在编译的时候不会被拷贝到目标程序中,目标程序只会存储下动态库的引用。
  • 真正用到动态库内的函数时才会去查找 - 绑定 - 使用函数。
  • 动态库的格式有:.framework.dylib.tbd……

3、动态库和静态库的区别

  • 静态库
    • 在编译时加载
    • 优点:代码装载和执行速度比动态库快。
    • 缺点:浪费内存和磁盘空间,模块更新困难。
  • 动态库
    • 在运行时加载
    • 优点:体积比静态库小很多,更加节省内存。
    • 缺点:代码装载和执行速度比静态库慢。
  • 备注:
    • 体积小于最小单位16k的静态库编译出来的动态库体积会等于16k
    • 换成动态库会导致⼀些速度变低,但是会通过延迟绑定(Lazy Binding)技术优化。
    • 延迟绑定:首次使用的时候查找并记录方法的内存地址,后续调用就可以省略查找流程。

4、动态库、静态库、framework是什么关系?

  • 库是已经编译完成的二进制文件。
  • 代码需要提供给外部使用又不想代码被更改,就可以把代码封装成库,只暴露头文件以供调用。
  • 希望提高编译速度,可以把部分代码封装成库,编译时只需要链接。
  • 库都是需要链接的,链接库的方式有静态和动态,所以就产生了静态库和动态库。

framework其实是一种文件的打包方式,把头文件、二进制文件、资源文件封装在一起,方便管理和分发。所以动态库和静态库的文件格式都会有.framework

iOS 静态库动态库看这里

  • Dynamic Framework动态库,系统提供的 framework 都是动态库。比如 UIKit.framework,具有所有动态库的特性。

  • Static Framework静态库,开发者可以制作。可以理解为静态库 = 头文件 + 资源文件 + 二进制代码,具有静态库的属性。

  • Embedded Framework也是动态库的一种,用户可以制作。系统的Framework不需要拷贝到目标程序中,Embedded Framework最后需要拷贝到APP中。他具有部分动态特性,可以在 Extension可执行文件目标APP 之间共享。

  • XCFramework是苹果官⽅推荐的、⽀持的文件格式。支持 xcode11 以上,可以提供多个不同平台的分发二进制文件,xcode会自动判断你需要编译的ipa包是什么架构,使用的时候就不用通过脚本剥离不需要的架构体系。

5、动态库和静态库链接到主程序以后放在什么位置?

iOS 静态库动态库看这里

6、什么是dead strip

dead strip 可以在编译时把没有用到的代码屏蔽在外,以节约包体积。 iOS 静态库动态库看这里

7、-all_load-noall_load-ObjC-force_load 参数的区别?

这几个参数只对链接静态库生效

  • -all_load:加载全部代码
  • -noall_load:默认参数,屏蔽未用代码
  • -ObjC:加载全部OC相关代码,包括分类
  • -force_load: 可以加载指定静态库的全部代码

8、什么是tbd文件?

  • tbd全称是txt-based stub libraries,本质上是一个YMAL描述文本文件。
  • 用于记录动态库信息,包括 导出符号、动态库框架信息、动态库依赖信息
  • 真机情况下动态库都在手机内
  • xcode开发时相关的库存在MacOS,不用存储Xcode内。使用tbd格式的伪framework可以大大减少xcode的大小。

9、动态库和静态库的选择?

  • 相同代码打包成动态库比静态库体积更小
    • 静态库是.o文件的合集,每个.o都包含全局符号,多个.o会重复包含全局符号,库体积更大。
    • 动态库是编译链接产物,所有符号都放在一起,全局符号只存1次,库体积更小。
  • 使用静态库的工程生成ipa包体积更小
    • 动态库是编译链接的最终产物,无法优化,需要拷贝到frameworks文件夹中,会增加ipa包体积。
    • 工程编译默认将静态库代码合并到APP主程序符号表.framework格式静态库不含资源文件的时候可以选择Do not embed,这样静态库文件不会嵌入包,可以缩小安装包体积
    • 静态库还可以通过设置-noall_load-ObjC-force_load屏蔽不需要的代码。

10、为什么项目里动态库不能超过六个?

因为动态库是APP运行时动态加载的,数量多了会影响启动速度。

11、怎么剥离动态库里不需要的架构?

  • 1、编译库文件
  • 2、lipo指令剥离不需要的架构
  • 3、重新拖到项目

演示

接下来手把手通过Xcode和模拟脚本模拟打包过程并验证上述的问题。

1、创建项目

首先在同一目录创建一个主工程APP,一个静态库,一个动态库 iOS 静态库动态库看这里 iOS 静态库动态库看这里

在主工程APP内创建Workspace iOS 静态库动态库看这里 iOS 静态库动态库看这里

关闭主APP工程,打开workspace添加两个库工程 iOS 静态库动态库看这里 iOS 静态库动态库看这里 iOS 静态库动态库看这里 iOS 静态库动态库看这里

添加完成后的工程文件目录如下 iOS 静态库动态库看这里

2、库工程的设置

关闭workspace,打开库工程(两个设置都一样)

Project -> BuildSettings -> Deployment Postprocessing 设置为YES

这个设置相当于 Deployment 一整列的开关,所以一定要打开 iOS 静态库动态库看这里

并不是所有的符号都是必须的,比如 Debug Map,所以 Xcode 提供了Strip Linked Product去除不需要的符号信息,可以通过设置 Strip Style 参数控制效果。

稍后会演示这个参数如何使用。

去除符号信息后只能使用 dSYM 进行符号化,所以需要将 Debug Information Format 修改为 DWARF with dSYM file

ps:没有 DWARF 调试信息Xcode 靠什么来生成dSYM

还是通过 DWARFXcode 的编译步骤:

  • 生成带有 DWARF 调试信息的可执行文件
  • 提取可执行文件中的信息打包成 dSYM
  • 使用 strip 命令去除符号化信息

iOS 静态库动态库看这里

Project -> BuildSettings -> Strip Style 设置为 Debugging Symbols(默认)

选择不同的 Strip StyleAPP 构建最后一步的 Strip操作 会带上对应参数。

选择 debugging symbols 在函数调用栈中,可以看到类名和方法名。 iOS 静态库动态库看这里

添加可以暴露的头文件 iOS 静态库动态库看这里

3、生成XCConfig配置文件

iOS 静态库动态库看这里

先创建一个配置文件debugConfig.cxconfig,里面指定库以及主程序路径 iOS 静态库动态库看这里

指定使用配置文件 iOS 静态库动态库看这里

4、初步运行工程

编译成功后,可以看到产物里面会出现动态库.framework及静态库.a文件 iOS 静态库动态库看这里 iOS 静态库动态库看这里

5、配置运行脚本

接下来,给xcode添加一个脚本,使得编译的时候能把符号表输出到终端。

在主程序的根目录下创建一个名为xcode_run_cmd.sh的脚本文件 iOS 静态库动态库看这里

代码直接贴出来方便大家cv操作。

#!/bin/sh


RunCommand() {
  #判断全局字符串VERBOSE_SCRIPT_LOGGING是否为空。-n string判断字符串是否非空
  #[[是 bash 程序语言的关键字。用于判断
  if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
    #作为一个字符串输出所有参数。使用时加引号"$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数
      if [[ -n "$TTY" ]]; then
          echo "♦ $@" 1>$TTY
      else
          echo "♦ $*"
      fi
      echo "------------------------------------------------------------------------------" 1>$TTY
  fi
  #与$*相同。但是使用时加引号,并在引号中返回每个参数。"$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数
  if [[ -n "$TTY" ]]; then
        eval "$@" &>$TTY
  else
       "/bin/bash $@"
  fi
  #显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
  return $?
}

EchoError() {
    #在shell脚本中,默认情况下,总是有三个文件处于打开状态,标准输入(键盘输入)、标准输出(输出到屏幕)、标准错误(也是输出到屏幕),它们分别对应的文件描述符是0,1,2
    # >  默认为标准输出重定向,与 1> 相同
    # 2>&1  意思是把 标准错误输出 重定向到 标准输出.
    # &>file  意思是把标准输出 和 标准错误输出 都重定向到文件file中
    # 1>&2 将标准输出重定向到标准错误输出。实际上就是打印所有参数已标准错误格式
    if [[ -n "$TTY" ]]; then
        echo "$@" 1>&2>$TTY
    else
        echo "$@" 1>&2
    fi
    
}

RunCMDToTTY() {
    if [[ ! -e "$TTY" ]]; then
        EchoError "=========================================="
        EchoError "ERROR: Not Config tty to output."
        exit -1
    fi
    # CMD = 运行到命令
    # CMD_FLAG = 运行到命令参数
    # TTY = 终端
    if [[ -n "$CMD" ]]; then
        RunCommand $CMD
    else
        EchoError "=========================================="
        EchoError "ERROR:Failed to run CMD. THE CMD must not null"
    fi
}


RunCMDToTTY

主程序里配置脚本路径 Target -> Build Phases -> Run TTY Script 添加

/bin/bash -c "${SRCROOT}/xcode_run_cmd.sh"

iOS 静态库动态库看这里

6、创建运行脚本的配置文件

打开终端,输入tty,看到一个路径,记下来且不要关闭终端 iOS 静态库动态库看这里

创建一个名为runConfig.xcconfigXCConfig文件,并且添加如下代码 iOS 静态库动态库看这里

记得上面第一次创建的配置文件吗,因为XCode只支持一份配置文件,所以不需要改动配置路径

直接在第一份配置里引用runConfig.xcconfig即可

#include "runConfig.xcconfig"

运行工程,接下来查看终端,已经输出的我们的指令~ iOS 静态库动态库看这里

7、输出符号信息

runConfig.xcconfig里面添加查看符号表的指令

括号内名称就是debugConfig.xcconfig里配置的

// nm 查看符号表
// p:不分类 a:查看所有machO符号,理解成all
// ${地址}
CMD = nm -pa ${MYSTATICLIB_PATH}

iOS 静态库动态库看这里

在静态库内添加一个测试方法 iOS 静态库动态库看这里

再次运行代码可以在终端里看到,测试方法的符号 iOS 静态库动态库看这里

为了测试的方便和清晰,动态库和静态库都各添加上两个类

  • 一个公开类PublibObj
  • 一个私有类PrivateObj 都添加上Test方法 iOS 静态库动态库看这里

此时输出动态库的符号表,可以看到不同类的方法是放在一起的 iOS 静态库动态库看这里

再输出静态库的符号表,看到符号表是按照.o文件分类输出的

这也可以印证静态库就是一个个.o文件的合集 iOS 静态库动态库看这里

最后输出主工程的符号表 iOS 静态库动态库看这里

8、使用动态库和静态库

主工程内创建一个文件夹并添加两个公开类的引用,调用两个测试方法 iOS 静态库动态库看这里

再次查看终端,搜索静态库的方法,能查到其符号 iOS 静态库动态库看这里

再去查找动态库方法时,却发现找不到他的符号 iOS 静态库动态库看这里

其实上述现象正好印证了动态库和静态库的加载时期并不一样,脚本是在编译时运行的

  • 静态库的方法也是在编译期加载,所以这里能获取到静态库的符号。

  • 动态库的方法则是在运行时加载,所以脚本运行时获取不到动态库的符号。

9、查看静态库的代码

通过objdump指令可以查看具体的代码情况

// objdump 查看
// -macho 查看macho格式的信息
// -d 打印代码块的内容
// ${地址}
CMD = objdump -macho -d ${APP_PATH}

iOS 静态库动态库看这里

可以看到动态库的代码只有可怜的两行,这里其实是ViewDidLoad在调用 iOS 静态库动态库看这里

但是动态库的代码除了被调用以外,连实现都在这里输出了 iOS 静态库动态库看这里

而且能楚的看到,静态库的代码和主程序的其他代码放在一块了~

这是因为静态库在编译时会复制一份代码到全局符号表~

10、静态库的链接方式

前面已经讲述了下列几个参数只对链接静态库生效

  • -all_load:加载全部代码
  • -noall_load:默认参数,屏蔽未用代码
  • -ObjC:加载全部OC相关代码,包括分类
  • -force_load: 可以加载指定静态库的全部代码

下面来稍稍验证一下~

ViewContrller的代码屏蔽 iOS 静态库动态库看这里

运行后在终端是无法找到任何与静态库相关的内容,因为主工程是默认开启dead strip iOS 静态库动态库看这里

Target -> Building Setting -> Other Linker Flag 设置为 -all_load 再次运行 iOS 静态库动态库看这里

重新搜索到staticPublicTest的实现,这就证明了 -all_load可以加载全部代码 iOS 静态库动态库看这里

接下来把ViewContrller的代码屏蔽放开 iOS 静态库动态库看这里

Target -> Building Setting -> Other Linker Flag 设置为 -noall_load 再次运行 iOS 静态库动态库看这里

此时报错无法运行。当然了,所有符号都不加载程序肯定没法跑 iOS 静态库动态库看这里

-ObjC这个不好演示,就稍微讲一讲吧~

他的作用是将静态库中任何Objective-C代码都链接到APP中。

任何也就包括了Category的方法,这就导致使用-ObjC可能会链接很多静态库中未被使用的Objective-C代码,极大的增加APP的代码体积。

至于-force_load和前面-all_load的用法基本一致,只需要在参数后面添加静态库的地址即可,这样就加载指定静态库的全部代码~

10、构建 XCFramework 并使用

在动态库工程的根目录创建一个脚本pack_xcframe.sh,复制下列代码

FREAMEWORK_NAME='MyDynamicLib'修改为你的库名就能用了

#!/bin/sh -e

# Framework/工程 的名字
FREAMEWORK_NAME='MyDynamicLib'
# 所有产物的目标路径
OUTPUT_DIR="./Build/${FREAMEWORK_NAME}"

# Device Archive 生成的 .xcarchive 存放路径。在工程的根目录下生成 Build 文件夹。
ARCHIVE_PATH_IOS_DEVICE="./${OUTPUT_DIR}/${FREAMEWORK_NAME}_device.xcarchive"
# Simulator Archive 生成的 .xcarchive 存放路径。
ARCHIVE_PATH_IOS_SIMULATOR="./${OUTPUT_DIR}/${FREAMEWORK_NAME}_simulator.xcarchive"

# 制作完 framework 后,是否在 Finder 中打开
REVEAL_XCFRAMEWORK_IN_FINDER=true

# 生成单个平台的 .xcarchive. 接收4个参数, scheme, destination, archivePath,指令集.
function archiveOnePlatform {
   echo "▸ Starts archiving the scheme: ${1} for destination: ${2};\n▸ Archive path: ${3}"

   xcodebuild archive \
       -scheme "${1}" \
       -destination "${2}" \
       -archivePath "${3}" \
       VALID_ARCHS="${4}" \
       SKIP_INSTALL=NO\
       BUILD_LIBRARY_FOR_DISTRIBUTION=YES
}

function archiveAllPlatforms {
   # Platform                Destination
   # iOS                    generic/platform=iOS
   # iOS Simulator            generic/platform=iOS Simulator
   # iPadOS                generic/platform=iPadOS
   # iPadOS Simulator        generic/platform=iPadOS Simulator
   # macOS                    generic/platform=macOS
   # tvOS                    generic/platform=tvOS
   # watchOS                generic/platform=watchOS
   # watchOS Simulator        generic/platform=watchOS Simulator
   # carPlayOS                generic/platform=carPlayOS
   # carPlayOS Simulator    generic/platform=carPlayOS Simulator

   SCHEME=${1}

   archiveOnePlatform $SCHEME "generic/platform=iOS Simulator" ${ARCHIVE_PATH_IOS_SIMULATOR} "x86_64"
   archiveOnePlatform $SCHEME "generic/platform=iOS" ${ARCHIVE_PATH_IOS_DEVICE} "armv7 arm64"
}

function makeXCFramework {
   mkdir -p "${OUTPUT_DIR}"
   FRAMEWORK_RELATIVE_PATH="Products/Library/Frameworks"
   sudo xcodebuild -create-xcframework \
       -framework "${ARCHIVE_PATH_IOS_DEVICE}/${FRAMEWORK_RELATIVE_PATH}/${FREAMEWORK_NAME}.framework" \
       -framework "${ARCHIVE_PATH_IOS_SIMULATOR}/${FRAMEWORK_RELATIVE_PATH}/${FREAMEWORK_NAME}.framework" \
       -output "${OUTPUT_DIR}/${FREAMEWORK_NAME}.xcframework"
}

echo "##################### 启动脚本 #####################"

echo "##################### 重置目标路径 ${OUTPUT_DIR} #####################"
sudo rm -rf $OUTPUT_DIR

echo "##################### 正在归档 ${FREAMEWORK_NAME} #####################"
archiveAllPlatforms $FREAMEWORK_NAME

echo "##################### 正在制作 framework: ${FREAMEWORK_NAME}.xcframework #####################"
makeXCFramework


if [ ${REVEAL_XCFRAMEWORK_IN_FINDER} = true ]; then
   sudo open "${OUTPUT_DIR}/"
fi

终端调用一下 iOS 静态库动态库看这里

创建成功~ iOS 静态库动态库看这里

怎么使用?

  • 1、跟普通的动态库一样~XCFramework复制到工程里面
  • 2、Target -> General -> Frameworks,Libraries,and Embeddded Content 添加一下 iOS 静态库动态库看这里
  • 3、然后import头文件调用就行

总结瞎掰

总结其实也没啥能总了...

这里纯瞎说一下

这篇东西3天熬夜断断续续搞出来的,查资料、xcode调试、截图、码字要老命了...

质量嘛,自我感觉还行,纯手打真手把手教程,小白跟着也能走完整个过程

但是也有可能被认为是水文,毕竟没有很深奥的东西

其实吧

像我记性不好的人,喜欢记录一下折腾过的东西和踩过的坑

以后用到的时候翻一下自己的主页就找到,也不用去搜,方便省时间

万一有相同问题的朋友能搜到看到,那帮到人就更开心~

技术社区嘛,技术的东西多包容~

转载自:https://juejin.cn/post/7049803824214573086
评论
请登录