前言
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工程如下
![](https://img.haomeiwen.com/i1212147/df6438e9c7b70ed4.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下。
![](https://img.haomeiwen.com/i1212147/3e981a1d5675c7e4.png)
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架构,相同架构的二进制文件不能打包成胖二进制文件
![](https://img.haomeiwen.com/i1212147/858044870ff19844.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
合并完成如下图所示
![](https://img.haomeiwen.com/i1212147/389a54071db21cbb.png)
二. XCFramework
XCFramework的出现就是为了解决上面合并报错,和传统的framework相比:
- 可以用单个.xcframework文件提供多个平台的分发二进制文件;
- 与Fat Header相比,可以按照平台划分,可以包含相同架构的不同平 台的文件;
- 在使用时,不需要再通过脚本去剥离不需要的架构体系。
查看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'
一个文件就可以包含多个架构,并且架构是按照传递顺序来生成
![](https://img.haomeiwen.com/i1212147/68adadf2d94b1e6f.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'
合并之后查看如下图所示
![](https://img.haomeiwen.com/i1212147/4d3d957129cff427.png)
![](https://img.haomeiwen.com/i1212147/a370d027fdac6a6f.png)
三. 弱引用
3.1创建项目如下
![](https://img.haomeiwen.com/i1212147/36bf296940a742a7.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 弱引用
![](https://img.haomeiwen.com/i1212147/446c8b387d1ebb9a.png)
四. 静态库冲突
4.1创建项目如下,其中两个AFNetworking文件只是名称不同
![](https://img.haomeiwen.com/i1212147/cbc400fb59baa5fe.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"
此时编译工程会报符号冲突
![](https://img.haomeiwen.com/i1212147/f1aad71371d189e5.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
编译链接不会报错,原因是 编译动态库的时候,会把依赖的静态库代码整个链接到动态库中
现在有一个问题,主工程可以使用静态库中的代码吗?
![](https://img.haomeiwen.com/i1212147/bb50f026a811f87f.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
![](https://img.haomeiwen.com/i1212147/66502cd21b90362b.png)
![](https://img.haomeiwen.com/i1212147/21a0d1f5a821a0e3.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"
![](https://img.haomeiwen.com/i1212147/5faf7ab3b47950de.png)
解决办法:类似于动态库链接动态库 的两种方案
关于第一种解决方案操作
首先把cocoapods中脚本复制到以下目录
![](https://img.haomeiwen.com/i1212147/5660d72d3dde3b1a.png)
![](https://img.haomeiwen.com/i1212147/8c41c794710028b6.png)
如果在工程中即导入静态库又导入动态库,该怎么解决?
// 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
总结:
当一个动态库运行时,不能够保证它永远都在指定的运行位置,就要使用弱引用
网友评论