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

总结:

当一个动态库运行时,不能够保证它永远都在指定的运行位置,就要使用弱引用

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容