前言
XCFramework:是蘋果官方推薦的,支持的,可以更方便的表示一個多個平臺結構的分發二進制的格式。
需要Xcode 11以上支持,
是為更好的支持Mac Catalyst和ARM芯片的macOS。 專?在2019年提出的framework的另一種先進格式。
平時開發涉及到的一些架構:
iOS/iPad:arm64
iOS/iPad Simulator:x86_64 arm64
Mac Catalyst: x86_64 arm64 跨平臺
Mac: x86_64 arm64
XCFramework的好處:
多架構合并,模擬器,真機可以通用
上架AppStore,不需要將xcframework中的真機架構分離,.framework還需要用腳本分離
一. archive打包
1.1 archive打包命令原理
準備SYTimer工程如下
// 編譯模擬器產物
$ xcodebuild archive -project 'SYTimer.xcodeproj' \
-scheme 'SYTimer' \
-configuration Release \
-destination 'generic/platform=iOS Simulator' \
-archivePath '../archives/SYTimer.framework-iphonesimulator.xcarchive' \
SKIP_INSTALL=NO
// 編譯真機產物
$ xcodebuild archive -project 'SYTimer.xcodeproj' \
-scheme 'SYTimer' \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath '../archives/SYTimer.framework-iphoneos.xcarchive' \
SKIP_INSTALL=NO
SKIP_INSTALL只有設置成NO,才會把我們的編譯產物SYTimer.framework放到Products下。
BCSymbolMaps文件: 打開了bitcode會生成,用于bitcode調試
1.2 合并上面生成的兩個framework
$ lipo -output SYTimer -create ../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer ../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer
報錯:兩個framework都包含arm64架構,相同架構的二進制文件不能打包成胖二進制文件解決辦法:把模擬器中相同架構刪除
// 刪除模擬器中arm64架構,輸出到SYTimer-x86_64
$ lipo -output SYTimer-x86_64 -extract x86_64 ../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer
// 合并真機架構與剛才生成的SYTimer-x86_64架構文件到 SYTimer
$ lipo -output SYTimer -create ../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer SYTimer-x86_64
合并完成如下圖所示二. XCFramework
XCFramework的出現就是為了解決上面合并報錯,和傳統的framework相比:
- 可以用單個.xcframework文件提供多個平臺的分發二進制文件;
- 與Fat Header相比,可以按照平臺劃分,可以包含相同架構的不同平 臺的文件;
- 在使用時,不需要再通過腳本去剝離不需要的架構體系。
查看SYTimer二進制文件架構如下image.png
2.1 XCFramework初探
既然XCFramework有那么多優勢,接下來就讓我們一起探討
// 進入xcframework文件夾,使用xcframework合并上面兩個framework
xcodebuild -create-xcframework \
-framework '../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
-framework '../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
-output 'SYTimer.xcframework'
一個文件就可以包含多個架構,并且架構是按照傳遞順序來生成2.2 合并framework完成發現并沒有symbols調試符號,接下來繼續探討
注意?? 傳遞調試符號必須是絕對路徑
xcodebuild -create-xcframework \
-framework '../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
-debug-symbols '/Users/wangning/Documents/資料/1:25/第五節、動態庫 與靜態庫實戰/上課代碼/多架構合并/archives/SYTimer.framework-iphoneos.xcarchive/BCSymbolMaps/1FE90BC9-7791-32D3-B864-ACFAC9DD7069.bcsymbolmap' \
-debug-symbols '/Users/wangning/Documents/資料/1:25/第五節、動態庫與靜態庫實戰/上課代碼/多架構合并/archives/SYTimer.framework-iphoneos.xcarchive/BCSymbolMaps/366D557B-B19A-3DB8-9A3F-E932BCE58BBB.bcsymbolmap' \
-debug-symbols '/Users/wangning/Documents/資料/1:25/第五節、動態庫與靜態庫實戰/上課代碼/多架構合并/archives/SYTimer.framework-iphoneos.xcarchive/dSYMs/SYTimer.framework.dSYM' \
-framework '../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
-debug-symbols '/Users/wangning/Documents/資料/1:25/第五節、動態庫與靜態庫實戰/上課代碼/多架構合并/archives/SYTimer.framework-iphonesimulator.xcarchive/dSYMs/SYTimer.framework.dSYM' \
-output 'SYTimer.xcframework'
合并之后查看如下圖所示三. 弱引用
3.1創建項目如下
#import "ViewController.h"
#import <SYTimer.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
SYTimer *timer = [SYTimer new];
NSLog(@"%@", timer);
}
@end
//xcconfig文件配置如下
FRAMEWORK_SEARCH_PATHS = $(inherited) ${SRCROOT}
HEADER_SEARCH_PATHS = $(inherited) '${SRCROOT}/SYTimer.framework/Headers'
LD_RUNPATH_SEARCH_PATHS = $(inherited)
OTHER_LDFLAGS = $(inherited) -framework "SYTimer"
編譯成功,運行時候報錯,找不到動態庫SYTimer
dyld: Library not loaded: @rpath/SYTimer.framework/SYTimer
Referenced from: /Users/wangning/Library/Developer/CoreSimulator/Devices/C53887CF-B3AC-4677-B6FD-DD090CC6D346/data/Containers/Bundle/Application/651CC63E-F724-4A4E-BE71-51BF0470B945/LGApp.app/LGApp
Reason: image not found
3.2 修改xcconfig文件如下
FRAMEWORK_SEARCH_PATHS = $(inherited) ${SRCROOT}
HEADER_SEARCH_PATHS = $(inherited) '${SRCROOT}/SYTimer.framework/Headers'
LD_RUNPATH_SEARCH_PATHS = $(inherited)
// weak:允許該庫在運行時消失。
OTHER_LDFLAGS = $(inherited) -Xlinker -weak_framework -Xlinker "SYTimer"
運行成功,打印null
查看ipa包LGApp文件
$ otool -l /Users/wangning/Library/Developer/Xcode/DerivedData/LGApp-adhpusbeokaxbtazgqzkquzynndv/Build/Products/Debug-iphonesimulator/LGApp.app/LGApp
可以看出來 cmd LC_LOAD_WEAK_DYLIB 弱引用四. 靜態庫沖突
4.1創建項目如下,其中兩個AFNetworking文件只是名稱不同
4.2配置xcconfig文件如下
//-I
HEADER_SEARCH_PATHS = $(inherited) "${SRCROOT}/AFNetworking" "${SRCROOT}/AFNetworking2"
//-L
LIBRARY_SEARCH_PATHS = $(inherited) "${SRCROOT}/AFNetworking" "${SRCROOT}/AFNetworking2"
OTHER_LDFLAGS = $(inherited) -all_load -l"AFNetworking" -l"AFNetworking2"
此時編譯工程會報符號沖突修改xcconfig文件如下,避免oc文件全部加載導致符號沖突
//-I
HEADER_SEARCH_PATHS = $(inherited) "${SRCROOT}/AFNetworking" "${SRCROOT}/AFNetworking2"
//-L
LIBRARY_SEARCH_PATHS = $(inherited) "${SRCROOT}/AFNetworking" "${SRCROOT}/AFNetworking2"
//-l
// 沖突
// all_load // -ObjC
// 兩個靜態庫 -》 庫 默認強制鏈接哪一個庫
OTHER_LDFLAGS = $(inherited) -l"AFNetworking" -l"AFNetworking2" -Xlinker -force_load -Xlinker "${SRCROOT}/AFNetworking/libAFNetworking.a"
五. 動態庫與靜態庫相互鏈接
5.1動態庫鏈接動態庫
首先創建動態庫LGNetworkManager,內容如下
// LGAFNetworkingManager.h文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGAFNetworkingManager : NSObject
+ (instancetype)manager;
@end
NS_ASSUME_NONNULL_END
//LGAFNetworkingManager.m文件
#import "LGAFNetworkingManager.h"
#import <AFNetworking/AFNetworking.h>
@implementation LGAFNetworkingManager
+ (instancetype)manager {
NSLog(@"%@", [AFNetworkReachabilityManager manager]);
return [LGAFNetworkingManager new];
}
@end
// 使用use_frameworks 說明引用的動態庫
platform :ios, '14.1'
target :'LGNetworkManager' do
use_frameworks!
pod 'AFNetworking'
end
工程引用動態庫LGNetworkManager,LGNetworkManager引用AFNetworking
編譯成功,運行報錯 image not found,LGNetworkManager使用的動態庫AFNetworking找不到
解決辦法1:
// 參考cocoapods中腳本,動態的把AFNetworking復制到frameworks目錄下
if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework"
fi
解決辦法2:
// 修改podfile文件,把AFNetworking復制到工程frameworks目錄下
platform :ios, '14.1'
target :'LGNetworkManager' do
use_frameworks!
pod 'AFNetworking'
end
//LGNetworkManagerTests 工程名,這里使用的單元測試工程
target :'LGNetworkManagerTests' do
use_frameworks!
pod 'AFNetworking'
end
現在有一個問題,如果動態庫LGNetworkManager要引用LGNetworkManagerTests中內容,該怎么解決?
首先在LGNetworkManagerTests中創建類LGAppObject,里面添加一個測試方法test_app,然后在動態庫LGNetworkManager中引用LGAppObject,如下所示
#import "LGAFNetworkingManager.h"
#import <AFNetworking/AFNetworking.h>
#import "LGAppObject.h"
@implementation LGAFNetworkingManager
+ (instancetype)manager {
NSLog(@"%@", [AFNetworkReachabilityManager manager]);
LGAppObject *obj = [LGAppObject new];
[obj test_app];
return [LGAFNetworkingManager new];
}
@end
編譯之后報錯,找不到OBJC_CLASS$_LGAppObject符號
解決辦法
// Pods-LGNetworkManager.debug.xcconfig 文件配置如下
// 指定_OBJC_CLASS_$_LGAppObject是動態查找符號
OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -Xlinker -U -Xlinker _OBJC_CLASS_$_LGAppObject
5.2動態庫鏈接靜態庫
還是上面例子,此時修改podfile文件即可
// 注釋use_frameworks! 即可引用靜態庫
platform :ios, '14.1'
target :'LGNetworkManager' do
// use_frameworks!
pod 'AFNetworking'
end
編譯鏈接不會報錯,原因是 編譯動態庫的時候,會把依賴的靜態庫代碼整個鏈接到動態庫中
現在有一個問題,主工程可以使用靜態庫中的代碼嗎?
解決辦法:把AFNetworking隱藏調即可
// xc文件修改前
OTHER_LDFLAGS = $(inherited) -ObjC -l"AFNetworking"
// xc文件修改后
OTHER_LDFLAGS = $(inherited) -ObjC -Xlinker -hidden-l"AFNetworking"
5.3靜態庫鏈接靜態庫
編譯失敗分析:app鏈接組件靜態庫,組件靜態庫鏈接靜態庫,編譯時候會報錯,找不到靜態庫的符號OBJC_CLASS$_AFNetworking?
解決辦法1:
//配置路徑如下圖所示
${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/AFNetworking
解決辦法2:
// 修改podfile文件,把靜態庫直接導入主程序
platform :ios, '14.1'
target :'LGNetworkManagerTests' do
// use_frameworks!
pod 'AFNetworking'
end
5.4靜態庫鏈接動態庫
首先我們來分析一下 app鏈接靜態庫,會把靜態庫代碼全部鏈接到app中,靜態庫鏈接動態庫,需要把動態庫鏈接到app里面,app才能使用動態庫
編譯失敗:找不到動態庫符號
解決辦法:
// 配置路徑如下圖所示
"${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/AFNetworking"
解決辦法:類似于動態庫鏈接動態庫 的兩種方案
關于第一種解決方案操作
首先把cocoapods中腳本復制到以下目錄
如果在工程中即導入靜態庫又導入動態庫,該怎么解決?
// podfile文件配置如下
platform :ios, '14.1'
target :'LGNetworkManager' do
use_frameworks!
# 靜態庫、動態庫
# 指定需要被編譯成static_framework的庫
$static_framework = ['AFNetworking']
pre_install do |installer|
installer.pod_targets.each do |pod|
if $static_framework.include?(pod.name)
def pod.build_type;
Pod::Target::BuildType.static_framework
end
end
end
end
pod 'SDWebImage'
end
另外還有一點:如果有多個xcworkspace文件 cocoapods中podfile文件還可以指定為哪一個xcworkspace文件導入三方庫
// 修改podfile文件,把靜態庫直接導入主程序
platform :ios, '14.1'
// 指定workspace
workspace '../MultiProject.xcworkspace'
target :'LGFramework' do
use_frameworks!
pod 'AFNetworking'
end
// 指定app
target :'LGApp' do
// 指定app路徑
project '../LGApp/LGApp.xcodeproj'
use_frameworks!
pod 'AFNetworking'
end
總結:
當一個動態庫運行時,不能夠保證它永遠都在指定的運行位置,就要使用弱引用