以前的文章《我是如何讓微博綠洲的啓動速度提高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使用了相同的三方庫:
還有什麼問題歡迎你們提出來~
若是以爲本文對你有所幫助,給我點個贊吧~