iOS里的动态库和静态库

iOS里的动态库和静态库

2023年7月3日发(作者:)

iOS⾥的动态库和静态库介绍什么是库?库(Library)说⽩了就是⼀段编译好的⼆进制代码,加上头⽂件就可以供别⼈使⽤。什么时候我们会⽤到库呢?⼀种情况是,某些代码需要给别⼈使⽤,但是我们不希望别⼈看到源码,就需要以库的形式进⾏封装,只暴露出头⽂件。另外⼀种情况是,对于某些不会进⾏⼤的改动的代码,我们想减少编译的时间,就可以把它打包成库,因为库是已经编译好的⼆进制了,编译的时候只需要Link⼀下,不会浪费编译时间。iOS 中的链接(Linking) 是为了链接你的应⽤中会⽤到的库。在实际开发过程中,⼀个库⼀般都包括了可执⾏代码,公共的头⽂件和资源,这些库可以被链接器连接到你的应⽤。上⾯提到库在使⽤的时候需要Link,Link 的⽅式有两种,静态和动态,于是便产⽣了静态库和动态库。动态库形式:.dylib和.framework静态库形式:.a和.framework动态库和静态库的区别静态库:链接时,静态库会被完整地复制到可执⾏⽂件中,被多次使⽤就有多份冗余拷贝(图1所⽰)1.静态库在编译时加载,链接时会完整的复制到可执⾏⽂件中。2.静态库的可执⾏⽂件通常会⽐较⼤,因为所需的数据都会被整合到⽬标代码中,因此编译后的执⾏⽂件不需要外部库的⽀持,直接就能使⽤。3. 有多个app使⽤就会被复制多份,不能共享且占⽤更多冗余内存。4. 所有的函数都在库中,因此当修改函数时需要重新编译。注释:.a 和 .framework 的区别.a是单纯的⼆进制⽂件,.framework是⼆进制⽂件+资源⽂件。其中.a不能直接使⽤,需要 .h⽂件配合,⽽.framework则可以直接使⽤。.framework = .a + .h + sorrceFile(资源⽂件)推荐⽣成使⽤.framework。系统动态库:链接时不复制,程序运⾏时由系统动态加载到内存,供程序调⽤,系统只加载⼀次,多个程序共⽤,节省内存(图2所⽰)image上图中的绿框表⽰app的可执⾏⽂件。动态库的作⽤应⽤插件化:每⼀个功能点都是⼀个动态库,在⽤户想使⽤某个功能的时候让其从⽹络下载,然后⼿动加载动态库,实现功能的的插件化虽然技术上来说这种动态更新是可⾏的,但是对于AppStore上上架的app是不可以的。iOS8之后虽然可以上传含有动态库的app,但是苹果不仅需要你动态库和app的签名⼀致,⽽且苹果会在你上架的时候再经过⼀次AppStore的签名。所以你想在线更新动态库,⾸先你得有苹果APPStore私钥,⽽这个基本不可能。除⾮你的应⽤不需要通过AppStore上架,⽐如企业内部的应⽤,通过企业证书发布,那么就可以实现应⽤插件化在线更新动态库了。共享可执⾏⽂件:在其它⼤部分平台上,动态库都可以⽤于不同应⽤间共享,这就⼤⼤节省了内存。从⽬前来看,iOS仍然不允许进程间共享动态库,即iOS上的动态库只能是私有的,因为我们仍然不能将动态库⽂件放置在除了⾃⾝沙盒以外的其它任何地⽅。不过iOS8上开放了App Extension功能,可以为⼀个应⽤创建插件,这样主app和插件之间共享动态库还是可⾏的。(还需了解下App Extension)Xcode6之后⽀持创建动态库⼯程Xcode6之后苹果在iOS上开放了动态库。创建:File->New->Projectimage我们上⾯说过Framework即可以是动态库,也可以是静态库。那么我们上图中默认创建的是动态库,那么如何创建动态库呢?⽐如我创建的framework叫testLib,然后在build setting中设置动态库或静态库。如下图,创建framework的时候默认是Dynamic Library,我们可以修改为Static Library。image如果我们创建的framework是动态库,那么我们直接在⼯程⾥使⽤的时候会报错:Reason: Image Not Found。需要在⼯程的General⾥的Embedded Binaries添加这个动态库才能使⽤。因为我们创建的这个动态库其实也不能给其他程序使⽤的,⽽你的App Extension和APP之间是需要使⽤这个动态库的。这个动态库可以App

Extension和APP之间共⽤⼀份(App 和 Extension 的 Bundle 是共享的),因此苹果⼜把这种 Framework 称为

Embedded Framework,⽽我把这个动态库称为伪动态库。具体创建静态库和Framework可以参考:Xcode7创建静态库和Framework。⾃⼰创建的动态库我们创建的动态库和系统的动态库有什么区别呢?我们创建的动态库是在我们⾃⼰应⽤的.app⽬录⾥⾯,只能⾃⼰的App Extension和APP使⽤。⽽系统的动态库是在系统⽬录⾥⾯,所有的程序都能使⽤。可执⾏⽂件和⾃⼰创建的动态库位置:⼀般我们得到的iOS程序包是.ipa⽂件。其实就是⼀个压缩包,解压缩.ipa。解压缩后⾥⾯会有⼀个payload⽂件夹,⽂件夹⾥有⼀个.app⽂件,右键显⽰包内容,然后找到⼀个⼀般体积最⼤跟.app同名的⽂件,那个⽂件就是可执⾏⽂件。⽽我们在模拟器上运⾏的时候⽤NSBundle *bundel = [[NSBundle mainBundle] bundlePath];就能得到.app的路径。可执⾏⽂件就在.app⾥⾯。⽽我们⾃⼰创建的动态库就在.app⽬录下的Framework⽂件夹⾥。下图就是测试⼯程的⽬录image我这⾥⽤了⼀个测试⼯程,即有系统的动态库(WebKit),⼜有⾃⼰的动态库(DFCUserInterface),我们可以看⼀下可执⾏⽂件中对动态库的链接地址。⽤MachOView查看可执⾏⽂件。其中@rpth这个路径表⽰的位置可以查看Xcode 中的链接路径问题,⽽现在表⽰的其实就是.app下的Framework⽂件夹。imageimage下图表⽰了静态库,⾃⼰创建的动态库和系统动态库:image签名系统在加载动态库时,会检查 framework 的签名,签名中必须包含 TeamIdentifier 并且 framework 和 host app 的 TeamIdentifier 必须⼀致。我们在Debug测试的时候是不会报错的,在打包时如果有动态库,那么就会检查TeamIdentifier。如果不⼀致,否则会报下⾯的错误:Error loading /path/to/framework: dlopen(/path/to/framework, 265): no suitable image found. Did find:/path/to/framework: mmap() error 1此外,如果⽤来打包的证书是 iOS 8 发布之前⽣成的,则打出的包验证的时候会没有 TeamIdentifier 这⼀项。这时在加载 framework 的时候会报下⾯的错误:[deny-mmap] mapped file has no team identifier and is not a platform binary:/private/var/mobile/Containers/Bundle/Application/5D8FB2F7-1083-4564-94B2-0CB7DC75C9D1可以通过 codesign 命令来验证。codesign -dv /path/to/或codesign -dv /path/to/ork如果证书太旧,输出的结果如下:Executable=/path/to//YourAppIdentifier=pFormat=bundle with Mach-O thin (armv7)CodeDirectory v=20100 size=221748 flags=0x0(none) hashes=11079+5 location=embeddedSignature size=4321Signed Time=2015年10⽉21⽇ 上午10:18: entries=42TeamIdentifier=not setSealed Resources version=2 rules=12 files=2451Internal requirements count=1 size=188注意其中的 TeamIdentifier=not set。我们在⽤cocoapods的use_framework!的时候⽣成的动态库也可以⽤codesign -dv /path/to/ork查看到TeamIdentifier=not set。关于动态库的签名TeamIdentifier等之前没接触过,可以再去查看⼀下资料。关于Frameworkframework为什么既是静态库⼜是动态库?系统的.framework是动态库,我们⾃⼰建⽴的.framework⼀般都是静态库。但是现在你⽤xcode创建Framework的时候默认是动态库,⼀般打包成SDK给别⼈⽤的话都使⽤的是静态库,可以修改Build Settings的Mach-O Type为Static Library。什么是frameworkFramework是Cocoa/Cocoa Touch程序中使⽤的⼀种资源打包⽅式,可以将代码⽂件、头⽂件、资源⽂件、说明⽂档等集中在⼀起,⽅便开发者使⽤。⼀般如果是静态Framework的话,资源打包进Framework是读取不了的。静态Framework和.a⽂件都是编译进可执⾏⽂件⾥⾯的。只有动态Framework能在.app下⾯的Framework⽂件夹下看到,并读取.framework⾥的资源⽂件。Cocoa/Cocoa Touch开发框架本⾝提供了⼤量的Framework,⽐如ork/ork/ork等。需要注意的是,这些framework⽆⼀例外都是动态库。平时我们⽤的第三⽅SDK的framework都是静态库,真正的动态库是上不了AppStore的(iOS8之后能上AppStore,因为有个App Extension,需要动态库⽀持)。创建静态Framework1.选择Frameworkimage2.选择为静态库image3.⽣成对应版本的静态库静态库的版本(4种)真机-Debug版本真机-Release版本模拟器-Debug版本模拟器-Release版本这⾥debug或release是否⽣成符号表,是否对代码优化等可以在如何加快编译速度查看。我们选择Release版本。编译模拟器和真机的所有CPU架构。imageimage然后选择模拟器或者Generic iOS Device运⾏编译就会⽣成对应版本的Framework了。imageimage4.合成包含真机和模拟器的Framework终端cd到Products,然后执⾏以下代码,就会在Products⽬录下⽣成新的包含两种的执⾏⽂件,然后复制到任何⼀个ork⾥替换掉旧的testLib就可以了。lipo -create Release-iphoneos/ork/testLib Release-iphonesimulator/ork/testLib -output testLib或者在⼯程的Build Phases⾥添加以下脚本,真机和模拟器都Build⼀遍之后就会在⼯程⽬录下⽣成Products⽂件夹,⾥⾯就是合并之后的Framework。if [ "${ACTION}" = "build" ]thenINSTALL_DIR=${SRCROOT}/Products/${PROJECT_NAME}.frameworkDEVICE_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.frameworkSIMULATOR_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.frameworkif [ -d "${INSTALL_DIR}" ]thenrm -rf "${INSTALL_DIR}"fimkdir -p "${INSTALL_DIR}"cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"#ditto "${DEVICE_DIR}/Headers" "${INSTALL_DIR}/Headers"lipo -create "${DEVICE_DIR}/${PROJECT_NAME}" "${SIMULATOR_DIR}/${PROJECT_NAME}" -output "${INSTALL_DIR}/${PROJECT_NAME}"#open "${DEVICE_DIR}"#open "${SRCROOT}/Products"fiFramework⽬录imageHeaders表⽰暴露的头⽂件,⼀般都会有⼀个和Framework同名的.h⽂件,你在创建Framework的时候⽂件夹⾥也会默认⽣成这样⼀个⽂件。有这个和Framework同名的.h⽂件@import导⼊库的时候编译器才能找到这个库(@import导⼊头⽂件可参考iOS⾥的导⼊头⽂件)。主要就是这个Framework的⼀些配置信息。Modules这个⽂件夹⾥有个map⽂件,我们看到这⾥⾯有这样⼀句umbrella header "testLib.h",umbrella有保护伞、庇护的意思。也就是说Headers中暴露的testLib.h⽂件被放在umbrella⾬伞下保护起来了,所以我们需要将其他的所有需要暴露的.h⽂件放到testLib.h⽂件中保护起来,不然会出现警告。@import的时候也只能找到umbrella⾬伞下保护起来的.h⽂件。image⼆进制⽂件这个就是你源码编译⽽成的⼆进制⽂件,主要的执⾏代码就在这个⾥⾯。.bundle⽂件如果我们在Build Phases -> Copy Bundle Resources⾥加⼊.bundle⽂件,那么创建出来的.Framework⾥就会有这个.bundle的资源⽂件夹。Framework的资源⽂件CocoaPods如何⽣成Framework的资源⽂件我们能看到⽤cocoapods创建Framework的时候,Framework⾥⾯有⼀个.bundle⽂件,跟Framework同级⽬录⾥也有⼀个.bundle⽂件。这两个⽂件其实是⼀样的。那这两个.bundle是怎么来的呢?我们能看到⽤use_frameworks!⽣成的pod⾥⾯,pods这个PROJECT下⾯会为每⼀个pod⽣成⼀个target,⽐如我有⼀个pod叫做testLib,那么就会有⼀个叫testLib的target,最后这个target⽣成的就是ork。那么如果这个pod有资源⽂件的话,就会有⼀个叫testLib-bundleName的target,最后这个target⽣成的就是。上⾯创建静态Framework例⼦⾥⽣成资源⽂件在testLib的target的Build Phases -> Copy Bundle Resources⾥加⼊这个这个.bundle,在Framework⾥⾯就会⽣成这样⼀个bundle。在testLib的target的Build Phases -> Target Dependencies⾥加⼊这个target:testLib-bundleName,就会在Framework的同级⽬录⾥⽣成这样⼀个bundle。静态Framework⾥不需要加⼊资源⽂件⼀般如果是静态Framework的话,资源打包进Framework是读取不了的。静态Framework和.a⽂件都是编译进可执⾏⽂件⾥⾯的。只有动态Framework能在.app的Framework⽂件夹下看到,并读取.framework⾥的资源⽂件。你可以⽤NSBundle *bundel = [[NSBundle mainBundle] bundlePath];得到.app⽬录,如果是动态库你能在Framework⽬录下看到这个动态库以及动态库⾥⾯资源⽂件。然后你只要⽤NSBundle *bundle = [NSBundle bundleForClass:<#ClassFromFramework#>];得到这个动态库的路径就能读取到⾥⾯的资源了。但是如果是静态库的话,因为编译进了可执⾏⽂件⾥⾯,你也就没办法读到这个静态库了,你能看到.app下的Framework⽬录为空。在framework或⼦⼯程中使⽤xib问题如果静态库中有category类,则在使⽤静态库的项⽬配置中【Other Linker Flags】需要添加参数【-ObjC]或者【-all_load】。如果使⽤framework的使⽤出现【Umbrella header for module 'XXXX' does not include header 'XXXXX.h'】,是因为错把xxxxx.h拖到了public中。如果出现【dyld: Library not loaded:XXXXXX】,是因为打包的framework版本太⾼。⽐如打包framework时,选择的是iOS 9.0,⽽实际的⼯程环境是iOS 8开始的。需要到iOS Deployment Target设置对应版本。如果创建的framework类中使⽤了.dylib或者.tbd,⾸先需要在实际项⽬中导⼊.dylib或者.tbd动态库,然后需要设置【Allow Non-modularIncludes ....】为YES,否则会报错"Include of non-modular header inside framework module"。有时候我们会发现在使⽤的时候加载不了动态Framework⾥的资源⽂件,其实是加载⽅式不对,⽐如⽤pod的时候使⽤的是use_frameworks!,那么资源是在Framework⾥⾯的,需要使⽤以下代码加载(具体可参考给pod添加资源⽂件):NSBundle *bundle = [NSBundle bundleForClass:<#ClassFromFramework#>];[UIImage imageWithContentsOfFile:[bundle pathForResource:@"imageName@2x"(@"/imageName@2x") ofType:@"png"]];Swift ⽀持跟着 iOS8 / Xcode 6 同时发布的还有 Swift。如果要在项⽬中使⽤外部的代码,可选的⽅式只有两种,⼀种是把代码拷贝到⼯程中,另⼀种是⽤动态 Framework。使⽤静态库是不⽀持的。造成这个问题的原因主要是 Swift 的运⾏库没有被包含在 iOS 系统中,⽽是会打包进 App 中(这也是造成 Swift App 体积⼤的原因),静态库会导致最终的⽬标程序中包含重复的运⾏库(这是苹果⾃家的解释)。同时拷贝 Runtime 这种做法也会导致在纯 ObjC 的项⽬中使⽤ Swift 库出现问题。苹果声称等到 Swift 的 Runtime 稳定之后会被加⼊到系统当中,到时候这个限制就会被去除了(参考这个问题的问题描述,也是来⾃苹果⾃家⽂档)。CocoaPods 的做法在纯 ObjC 的项⽬中,CocoaPods 使⽤编译静态库 .a ⽅法将代码集成到项⽬中。在 Pods 项⽬中的每个 target 都对应这⼀个 Pod 的静态库。当不想发布代码的时候,也可以使⽤ Framework 发布 Pod,CocoaPods 提供了

vendored_framework 选项来使⽤第三⽅ Framework。对于 Swift 项⽬,CocoaPods 提供了动态 Framework 的⽀持。通过

use_frameworks! 选项控制。对于 Swift 写的库来说,想通过CocoaPods 引⼊⼯程,必须加⼊

use_frameworks! 选项。关于 use_frameworks!在使⽤CocoaPods的时候在Podfile⾥加⼊use_frameworks! ,那么你在编译的时候就会默认帮你⽣成动态库,我们能看到每个源码Pod都会在Pods⼯程下⾯⽣成⼀个对应的动态库Framework的target,我们能在这个target的Build Settings -> Mach-O Type看到默认设置是Dynamic Library。也就是会⽣成⼀个动态Framework,我们能在Products下⾯看到每⼀个Pod对应⽣成的动态库。这些⽣成的动态库将链接到主项⽬给主⼯程使⽤,但是我们上⾯说过动态库需要在主⼯程target的General -> Embedded Binaries中添加才能使⽤,⽽我们并没有在Embedded Binaries中看到这些动态库。那这是怎么回事呢,其实是cocoapods已经执⾏了脚本把这些动态库嵌⼊到了.app的Framework⽬录下,相当于在Embedded Binaries加⼊了这些动态库。我们能在主⼯程target的Build Phase -> Embed Pods Frameworks⾥看到执⾏的脚本。所以Pod默认是⽣成动态库,然后嵌⼊到.app下⾯的Framework⽂件夹⾥。我们去Pods⼯程的target⾥把Build Settings -> Mach-O Type设置为Static Library。那么⽣成的就是静态库,但是cocoapods也会把它嵌⼊到.app的Framework⽬录下,⽽因为它是静态库,所以会报错:unrecognized selector sent to instanceunrecognized selector sent to instance 。

发布者:admin,转转请注明出处:http://www.yc00.com/news/1688383751a129880.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信