XCFramework

前言

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工程如下

image.png

// 編譯模擬器產物
$ 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下。

image.png
dSYMs文件:當應用發生崩潰時,可以根據dSYMs文件和崩潰文件,把調用棧恢復出來,所以SDK工程師需要提供dSYMs文件
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架構,相同架構的二進制文件不能打包成胖二進制文件
image.png

解決辦法:把模擬器中相同架構刪除

// 刪除模擬器中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

合并完成如下圖所示
image.png

二. XCFramework

XCFramework的出現就是為了解決上面合并報錯,和傳統的framework相比:

  1. 可以用單個.xcframework文件提供多個平臺的分發二進制文件;
  2. 與Fat Header相比,可以按照平臺劃分,可以包含相同架構的不同平 臺的文件;
  3. 在使用時,不需要再通過腳本去剝離不需要的架構體系。
    查看SYTimer二進制文件架構如下
    image.png
    當應用上架的時候,x86_64模擬器架構還需要手動剝離,XCFramework的出現就解決了上面剝離的繁瑣操作
    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'

一個文件就可以包含多個架構,并且架構是按照傳遞順序來生成
image.png

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'

合并之后查看如下圖所示
image.png

接下來使用xcframework,如下圖所示
image.png
編譯完成,查看LGApp中ipa包下的frameworks文件夾,發現在使用的時候會動態的只加載當前需要的架構。

三. 弱引用

3.1創建項目如下

image.png

#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 弱引用
image.png

四. 靜態庫沖突

4.1創建項目如下,其中兩個AFNetworking文件只是名稱不同

image.png

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"

此時編譯工程會報符號沖突
image.png

修改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

編譯鏈接不會報錯,原因是 編譯動態庫的時候,會把依賴的靜態庫代碼整個鏈接到動態庫中
現在有一個問題,主工程可以使用靜態庫中的代碼嗎?

靜態庫對動態庫來說是導出符號,同樣的對主工程來說也是導出符號,所以是可以使用的,需要進行如下配置
image.png
接下來是第二個問題,動態庫會把靜態庫代碼全部鏈接作為導出符號,如果不想把靜態庫導出提供給外部使用,該怎么解決?
解決辦法:把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
image.png
image.png

解決辦法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"

image.png
編譯成功,運行失敗,動態庫找不到 image not found
解決辦法:類似于動態庫鏈接動態庫 的兩種方案
關于第一種解決方案操作
首先把cocoapods中腳本復制到以下目錄
image.png
接下來在工程中配置Run Script腳本
image.png
5.5工程中導入靜態庫與動態庫
如果在工程中即導入靜態庫又導入動態庫,該怎么解決?

// 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

總結:

當一個動態庫運行時,不能夠保證它永遠都在指定的運行位置,就要使用弱引用

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容