以前的文章《我是如何让微博绿洲的启动速度提高30%的》收到了不少朋友的反馈。git
其中,动态库转静态库的收益相比于二进制重排收益更大,但在实际操做中你们也遇到了一些问题。objective-c
本着装完B就跑,本身装的B,跪着也要装完的原则,在这里我详细来说一讲这些问题。shell
咱们先来看看动态库。这里我作了2个库Pod1和Pod2:swift
Podfile文件中配置了use_frameworks!
,而后进行pod install
,这样生成的就是动态库。安全
要怎么肯定这个是动态库呢?bash
首先,这个库的Mach-O Type是动态库。架构
执行⌘+B构建以后,咱们仍是来到Products文件中的app:app
在生成的Demo.app文件包上面点右键,选择显示包内容:svn
打开Framewoks文件夹,咱们能够看到里面有咱们建立的两个动态Pod1.framework和Pod2.framework。文件夹里面有代码签名、资源、Info.plist、Pod1(Mach-O)、bundle。post
也就是说,若是咱们使用的是动态库,在Framewoks文件夹就会看到它的身影,同时主工程的Mach-O文件中是没有相关的代码的。
下面咱们修改Build Settings中的Mach-O Type,将其设置为静态库Static Library。
同时按照上一篇文章说的,删除Pods-Demo-frameworks.sh中install_framework相关的部分:
先执行Clean Build Folder(或⇧+⌘+K),而后再⌘+B进行构建。完成以后,咱们仍是来打开Demo.app文件包:
此次咱们发现,Framewoks文件夹是空的!咱们再看看主工程的Mach-O文件:
咱们看到咱们在两个库中建立的类Pod1Object
和Pod2Object
来到了主工程的Mach-O文件中!
如今应该明白了:
以前咱们看到静态库会和主工程的Mach-O合并在一块儿,这会引发什么问题呢?
回顾下 -ObjC 、 -all_load 、-force_load这三个flag的区别:
咱们在Pod1库中复制一份Pod2Object.{h,m},同时在Build Settings中的Other Linker Flags中添加 -all_load。
先执行Clean Build Folder(或⇧+⌘+K),而后再⌘+B进行构建,这时就会出现duplicate symbols报错:
解决办法:
任意一个或者都不使用静态库。虽然这么说,其实这也是不安全的。若是能更名字就改一下吧。
咱们在Pod1Object
和Pod2Object
中添加如下方法:
- (nullable NSBundle *)getBundle {
return [NSBundle bundleForClass:[self class]];
}
复制代码
再在主工程的ViewController
中添加:
- (void)viewDidLoad {
[super viewDidLoad];
NSBundle *main = [NSBundle mainBundle];
NSBundle *pod1 = [[Pod1Object new] getBundle];
NSBundle *pod2 = [[Pod2Object new] getBundle];
NSLog(@"%@", main);
NSLog(@"%@", pod1);
NSLog(@"%@", pod2);
}
复制代码
咱们先看一下动态库的状况:
咱们看到Main Bundle是咱们的App,而咱们的Pod1 Bundle和Pod2 Bundle分别是其对应的framework,相似于它们有本身的沙盒。
咱们再来看看静态库:
能够看到3个Bundle都变成了咱们的Main Bundle!
这是由于静态库被合并到了主工程Mach-O文件中:
[NSBundle bundleForClass:[self class]];
复制代码
[self class]
如今在主工程的Mach-O中,那么上面找到的天然是主工程的Bundle,即Main Bundle。
这个问题解决起来比符号冲突简单一些,但解决这个问题前,我要先讲一下CocoaPods。
咱们在执行了pod install
以后,CocoaPods会在主工程的Build Phase添加一个 [CP] Embed Pods Frameworks脚本:
这个脚本会在Build以后执行。咱们以前静态化后,把三方库install_framework相关的代码注释(或者删除)了,来解决Archive以后在Organizer中尝试Validate App时会报错的问题:
其实,这个操做过于简单粗暴,会致使资源文件的丢失。
以前三方库中资源文件较少,没有发现这个问题,感谢你们的提醒。
咱们看仔细看一下install_framework究竟是干吗的。
# Copies and strips a vendored framework
install_framework()
{
# 设置source变量,三方库构建以后的路径
if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
local source="${BUILT_PRODUCTS_DIR}/$1"
elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
elif [ -r "$1" ]; then
local source="$1"
fi
# 设置destination变量,三方库须要移动到的路径
local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
# 判断source是否为连接文件,须要指向原来的文件
if [ -L "${source}" ]; then
echo "Symlinked..."
source="$(readlink "${source}")"
fi
# rsync --delete无差别同步,能够简单理解为网盘同步,或者复制
# 想详细了解rsync,能够在命令行中输入man rsync
# 这里至关于把source的文件(文件夹)同步到destination
# 即把*.framework复制到Frameworks文件夹下
# Use filter instead of exclude so missing patterns don't throw errors.
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
# 下面是找到二进制文件,即framework的Mach-O
local basename
basename="$(basename -s .framework "$1")"
binary="${destination}/${basename}.framework/${basename}"
if ! [ -r "$binary" ]; then
binary="${destination}/${basename}"
elif [ -L "${binary}" ]; then
echo "Destination binary is symlinked..."
dirname="$(dirname "${binary}")"
binary="${dirname}/$(readlink "${binary}")"
fi
# 去掉无效的架构
# Strip invalid architectures so "fat" simulator / device frameworks work on device
if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
strip_invalid_archs "$binary"
fi
# 进行代码签名
# Resign the code if required by the build settings to avoid unstable apps
code_sign_if_enabled "${destination}/$(basename "$1")"
# Swift的运行时库,Xcode 7以后就用不到了,能够无论
# Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
local swift_runtime_libs
swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u)
for lib in $swift_runtime_libs; do
echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
code_sign_if_enabled "${destination}/${lib}"
done
fi
}
复制代码
把这部分注释了,至关于说不会把构建好的 *.framework包复制到App的Frameworks文件夹下,天然 *.framework中的资源文件也就丢失了。
如今问题已经明了了:
解决办法:
既然如今拿到的Bundle是Main Bundle,咱们构建以后利用脚本把资源拷贝到App文件夹下不就行了。
install_framework_bundle()
{
# 设置source变量,三方库构建以后的路径
if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
local source="${BUILT_PRODUCTS_DIR}/$1"
elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
elif [ -r "$1" ]; then
local source="$1"
fi
# 设置destination变量,三方库须要移动到的路径
local destination="${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
# 遍历framework下的文件,找到bundle和图片,有其余资源本身改一下
for filename in `ls ${source} | grep ".*\.bundle\|.*\.jpg\|.*\.jpeg\|.*\.png"`
do
full_path=${source}/${filename}
# 把资源同步到Main Bundle中
rsync -abrv --suffix .conflict "${full_path}" "${destination}"
done
}
复制代码
如今咱们的操做就是把被静态化的三方库从install_framework方法改成install_framework_bundle:
if [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework_bundle "${BUILT_PRODUCTS_DIR}/Pod1/Pod1.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/Pod2/Pod2.framework"
fi
if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework_bundle "${BUILT_PRODUCTS_DIR}/Pod1/Pod1.framework"
install_framework_bundle "${BUILT_PRODUCTS_DIR}/Pod2/Pod2.framework"
fi
复制代码
咱们来对比一下:
如今资源都能正确访问了。
// Pod1Object
@implementation Pod1Object
- (nullable NSBundle *)getBundle
{
return [NSBundle bundleForClass:[self class]];
}
- (nullable NSBundle *)getResourceBundle {
NSBundle *bundle = [self getBundle];
return [NSBundle bundleWithPath:[bundle pathForResource:@"image1" ofType:@"bundle"]];
}
@end
// Pod2Object
@implementation Pod2Object
- (nullable NSBundle *)getBundle
{
return [NSBundle bundleForClass:[self class]];
}
- (nullable NSBundle *)getResourceBundle {
NSBundle *bundle = [self getBundle];
return [NSBundle bundleWithPath:[bundle pathForResource:@"image" ofType:@"bundle"]];
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSBundle *pod1 = [[Pod1Object new] getResourceBundle];
NSBundle *pod2 = [[Pod2Object new] getResourceBundle];
UIImage *image1 = [[UIImage alloc] initWithContentsOfFile:[pod1 pathForResource:@"icon121" ofType:@"png"]];
UIImageView *imageView1 = [[UIImageView alloc] initWithFrame:CGRectMake(0, 100, 100, 100)];
imageView1.contentMode = UIViewContentModeCenter;
[self.view addSubview:imageView1];
imageView1.image = image1;
UIImage *image2 = [[UIImage alloc] initWithContentsOfFile:[pod2 pathForResource:@"icon120" ofType:@"png"]];
UIImageView *imageView2 = [[UIImageView alloc] initWithFrame:CGRectMake(0, 200, 100, 100)];
imageView2.contentMode = UIViewContentModeCenter;
[self.view addSubview:imageView2];
imageView2.image = image2;
}
@end
复制代码
注意:
install_framework_bundle中,我没有处理重名问题。
-b --suffix .conflict会把重名文件添加后缀 .conflict,这个后缀是可配的。
处理完你能够用find扫一遍App文件夹,看一下有没有重名的资源被 .conflict标记出来。
check_conflict()
{
local destination="${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
conflict_list=`find ${destination} -regex '.*\.conflict'`
conflict_list=(${conflict_list/ /})
count=${#conflict_list[*]}
if [ $count -gt 0 ]; then
echo "Found conflicts:"
for var in ${conflict_list[@]}
do
echo $var
done
exit 1
fi
}
复制代码
若是资源重名,可能就没方法静态化了。
- 若是三方库代码写得很差,可能发生崩溃。
- 若是没有发生崩溃,代码行为可能受到影响。
虽然这是一个老生常谈的问题了,这里既然在讨论静态库和动态库就简单说一下。
库类型 | 优势 | 缺点 |
---|---|---|
静态库 | 1. 目标程序没有外部依赖,直接就能够运行。 2. 效率教动态库高。 |
1. 会使用目标程序的体积增大。 |
动态库 | 1. 不须要拷贝到目标程序中,不会影响目标程序的体积。 同一份库能够被多个程序使用。 2. 运行时才载入,可让咱们随时对库进行替换,而不须要从新编译代码。 |
1. 动态载入会带来一部分性能损失。 2. 动态库会使得程序依赖于外部环境。若是环境缺乏动态库或者库的版本不正确,就会致使程序没法运行。 |
iOS平台上规定不容许存在动态库,同时在iOS8以前由于App都是运行在沙盒当中,不一样的程序之间不能共享代码:
综上,因此上动态库也就没有存在的必要了。
iOS8以后,iOS有了App Extesion特性。因为iOS主App和Extension须要共享代码,因而苹果后来提出了Embedded Framework。这种动态库容许App和App Extension共享代码,可是这份动态库的做用范围被限定在一个App进程内,且须要拷贝到目标程序中。
简单点能够理解为被阉割的动态库:由于系统的动态库是不须要拷贝到目标程序中,且能够被多个进程使用;而咱们的动态库(Embedded Framework)没有这么大的能力。
建议:
若是程序使用了App Extesion,且主工程和Extension使用了相同的三方库:
还有什么问题欢迎你们提出来~
若是以为本文对你有所帮助,给我点个赞吧~