iOS封装功能生成 .framework

作者: 和珏猫 | 来源:发表于2016-08-01 17:23 被阅读21221次

    前言

          如果你想将你开发的控件与别人分享,一种方法是直接提供源代码文件。然而,这种方法并不是很优雅。它会暴露所有的实现细节,而这些实现你可能并不想开源出来。此外,开发者也可能并不想看到你的所有代码,因为他们可能仅仅希望将你的这份漂亮代码的一部分植入自己的应用中。

          另一种方法是将你的代码编译成静态库(library),让其他开发者添加到自己的项目中。然而,这需要你一并公布所有的公开的头文件,实在是非常不方便。

    你需要一种简单的方法来编译你的代码,这种方法应该使得你的代码易分享,并且在多个工程中易复用。你需要的是一种方法来打包你的静态库,将所有的头文件放到一个单元中,这样你就可以立刻将其加入到你的项目中并使用。

          OS X完美地支持这一点,因为Xcode就提供了一个项目模板,包含着默认构建目标(target)和可以容纳类似于图片、声音、字体等资源的文件。你可以为iOS创建Framework,不过这是一个比较复杂的手工活,如果你跟着教程走,你将学到怎么样跨过路障,顺利地完成Framework的创建。

    比较

    可以参考这篇文章.a和.framework.a和.framework的区别

    我们可以看出.a的封装和.framework的封装差不多,也有模拟器和真机合并的过程,通过上边的图片我们可以看出.a 和.framework的区别,就是.a+.h+soureFile=.framework。可以看出我们直接封装.framework其实是最好的。那么我们就来看看framework怎么封装的。

    另外关于.a的封装大家可以参考iOS如何生成.a文件

    目标

          本文将基于Xcode7创建一个简单的工程,通过两种方法来教大家如何制作一个自己的framework,目的就是简单易学的制作framework。这种方法可以使得你的代码易分享,在多个工程中复用,并且可以隐藏实现细节控制公开的头文件

    步骤

    1、打开Xcode,新建工程。

    不要选择“Application”,选择“Framework & Library”。选择第一个,然后Next。

    2、创建功能类。

    这里我创建一个继承自NSObject的SayHello类

    3、实现功能。

    在新创建的类里面声明方法并实现。这里我写一个sayHello的方法,以便后面测试使用。

    4、更改参数

    在TARGETS下选中工程,在Build Settings下更改几个参数。

    5、增加armv7s

      在Architectures下增加armv7s,并选中。将Build Active Architecture Only 设置为NO。

    6、设置Headers

    将你要公开的头文件拖至Public下,要隐藏的放在Private或者Project下,当然,隐藏的头文件就无法再被引用。

    然后需要在Test.h(必须是公开的,否则无法引用)中将你所有要公开的.h引入。

    打包Framework

    第一种方法

    1.选中模拟器,编译程序

    2.选中测试机,编译程序

    3.在finder中找到framework文件

    选中图中所标示的framework,然后右键show in finder。

    找到下图中所示的Test文件,一个是Debug-iphoneos(真机)下的,一个是Debug-iphonesimulator(模拟器)下的。

    4.通过终端命令将两个framework合为一个模拟器和真机都可使用的framework。

    打开控制台输入 lipo -create iphoneos下frameworkTest的路径 simulator下frameworkTest的路径 -output 新的路径,这样就完成了模拟器和真机版本的合并,新路径下的frameworkTest就是你合并后的文件,将这个文件名字改成和你未合并之前的Test一样的名字,放到framework文件夹下,替换掉原来的frameworkTest文件。

    上边说的乱糟糟的,看不清楚,这里给大家解释一下,看下边的图:打开终端手动输入画红线的lipo -create命令,然后绿线是iphoneos下frameworkTest的路径(找到iphoneos下frameworkTest的文件,拖拽进来),会自动有空格,紫线是simulator下frameworkTest的路径(同样找到simulator下frameworkTest的文件,拖拽进来),也会自动有空格,然后输入-output,然后敲空格,在引入一个新的路径(拖拽进一个新的路径),最后敲回车。这样就完成合并了。

    上面这段命令就是把真机和模拟器的frameworkTest合并成一个MyNewFrameworktest文件并存放在桌面上的New文件夹下。

    这里我们合并的时候会遇到一个error,这是啥原因还真不知道,但是会在和我们-output的文件夹路径并列的地方生成一个.lipo文件,这个.lipo文件我们下边会说到。

    注意:合并完成后会出现一个如下图的.lipo格式的文件。

    这TM是啥,不是应该出现一个类似下图的吗?不应该后缀什么也没有吗?怎么后缀会是.lipo,这是什么文件啊?!

    我们的操作是按照人家说的把合成后的文件名字改成MyFrameworkTest替换原来的。而且,把后缀.lipo去掉!

    在按照上述说的,替换了原来的。

    然后就可以进行下一步了。

    5.将修改后的framework拷贝出来保存,这就是我们最终制作的framework。

    第二种方法

    1、选中TARGETS下的工程,点击上方的Editor,选择Add Target创建一个Aggregate.

    2、选择Other下的Aggregate,点击Next创建。

    3、嵌入脚本。选中刚刚创建的Aggregate,然后选中右侧的Build Phases,点击左下方加号,选择New Run Script Phase

    将这段脚本复制进去:

    # Sets the target folders and the finalframework product.# 如果工程名称和Framework的Target名称不一样的话,要自定义FMKNAME# 例如: FMK_NAME = "MyFramework"FMK_NAME=${PROJECT_NAME}# Install dir will be the final output tothe framework.# The following line create it in the rootfolder of the current project.  INSTALL_DIR=${SRCROOT}/Products/$      {FMK_NAME}.framework# Working dir will be deleted after theframework creation.  WRK_DIR=build  DEVICE_DIR=${WRK_DIR}/Release-iphoneos/$                        {FMK_NAME}.framework  SIMULATOR_DIR=${WRK_DIR}/Release-  iphonesimulator/${FMK_NAME}.framework# -configuration ${CONFIGURATION}# Clean and Building both architectures.xcodebuild -configuration"Release"-target"${FMK_NAME}"-sdk iphoneos clean build  xcodebuild -configuration"Release"-target"${FMK_NAME}"-sdk iphonesimulator clean          build# Cleaning the oldest.if[-d"${INSTALL_DIR}"]thenrm -rf"${INSTALL_DIR}"fimkdir -p"${INSTALL_DIR}"cp -R"${DEVICE_DIR}/""${INSTALL_DIR}/"# Uses the Lipo Tool to merge both binaryfiles (i386 + armv6/armv7) into one      Universal final product.  lipo -create"${DEVICE_DIR}/${FMK_NAME}""${SIMULATOR_DIR}/${FMK_NAME}"-output"$                      {INSTALL_DIR}/${FMK_NAME}"rm -r"${WRK_DIR}"open"${INSTALL_DIR}"

    这里有一个误区,就是复制上边的这段脚本的时候,会在我们期望的效果里面多了几个回车,这几个回车是致命的,如果不删除回车,会报出如下的错误:

    最后的格式如下图,尽量一个回车也不能错:

    通过第一种方法中“把真机和模拟器的frameworkTest合并成一个”的过程和上边的脚本语言比较,我们可以发现其实两者异路同归,两个方法里面同时用到了“lipo -create  xxx”和“-output xxx”,不同的地方是第一种方法需要我们自己真机和模拟器分别变异一遍,而且需要我们把framework的路径拖进去,相比而言第二种方法比较简单。

    4、编译。如图所示,command+B编译。这里Generic iOS Device的意思是“iOS通用设备”,大概就是说模拟器和真机都能用。

    5、编译成功后会自动跳出一个finder,保存这个.framework,这就是我们需要的framework。

    至此,两种打包framework的方法介绍完成!

    最后就是用我们的Framework了,倒入另一个Xcode中,我们打开这个framework看看,发现只有Headers,里面有两个.h,其中一个是我们之前添加的FrameworkDemo.h文件,另一个就是我们的SayHello.h 。

    然后引入头文件:

    由于我们测试的方法是实例方法,那么我们实例化一个实例对象,然后就可以让这个实例对象调取相应的方法了:

    至此,完成Framework的制作和使用。

    总结

    最后需要注意的是:

    1、.h文件的外漏一定要保证是自己的想要外漏的。不想外漏的就别外漏了。

    2、开始打包的时候,一定要在选中模拟器和选中真机上边分别编译一次, 我觉得之前在家里没有真机的时候编译的好像不对。

    3、在终端上边合并的时候可能是error并生成一个.lipo文件,不要怕,大胆修改成同名的不挂后缀的同名文件。

    4、调用的时候分清楚是类方法还是实例方法,方便调用。

    5、在制作framework或者lib的时候,如果使用了category,则使用改FMWK的程序运行时会crash,此时需要在该工程中 other linker flags添加两个参数 -ObjC -all_load。(这点没有亲测)

    6、带有图片资源的需要把图片打包成Bundle文件,和framework一起拷贝到相应的项目中。

    7、公开的类中如果引用的private的类,打包以后对外会报错,找不到那个private的类,可以把那个private的.h放到(也没亲测)

    8、namespace 冲突。静态库用了某第三方库,项目也用了同样的第三方库,在编译的时候就会有 duplicate symbol 错误,因为有两份同样的第三方库。解决办法就是把用到的第三方库加上自定义前缀,包括类名、delegate 协议、常量名,尤其需要注意 Category 的方法名要修改。

    9、封装静态库的时候应尽量避免引入重量级第三方库,多自己进行封装

    10、一个静态库要有自己独有的前缀,所有类名、常量等都要加同样的前缀。

    11、真机+模拟器支持。(和第2条意思一样)Xcode 默认只会用当前环境(真机或模拟器)生成静态库,这样的 SDK 不方便其他项目开发时调试。解决办法就是通过脚本生成一份通用库,build_universal_library.sh,via SO.

    12、文档。静态库的方便是使用者直接拿你提供的方法来用,无需关注具体实现;不方便在于看不到实现,出现问题无法排查,因此需要把 SDK 的版本、更新历史、使用、FAQ 等写成文档,方便使用,也显得 SDK 比较正式规范。

    13、图片等资源文件用 bundle 方式打包。一个简单制作 bundle 的方法:新建文件夹,重命名为 YourSDK.bundle,然后 Show Package Contents 打开,加入图片。使用图片的时候需要指明 bundle: [UIImage imageNamed:@"YourSDK.bundle/img.png"]。也可以用 Target 方式制作 bundle,比如 iOS Library With Resourceshttp://www.galloway.me.uk/tutorials/ios-library-with-resources/.

    14、如果 SDK 有用到 Category,注意项目设置 Other Linker Flags 添加 -ObjC。(后边介绍了-ObjC的作用)

    补充

    编译过程:

    从C代码到可执行文件经历的步骤是:源代码 > 预处理器 > 编译器 > 汇编器 > 机器码 > 链接器 > 可执行文件

    在最后一步需要把.o文件和C语言运行库链接起来,这时候需要用到ld命令。源文件经过一系列处理以后,会生成对应的.obj文件,然后一个项目必然会有许多.obj文件,并且这些文件之间会有各种各样的联系,例如函数调用。链接器做的事就是把这些目标文件和所用的一些库链接在一起形成一个完整的可执行文件。Other linker flags设置的值实际上就是ld命令执行时后面所加的参数

    下面逐个介绍3个常用参数:

    -ObjC:加了这个参数后,链接器就会把静态库中所有的Objective-C类和分类都加载到最后的可执行文件中

    -all_load:会让链接器把所有找到的目标文件都加载到可执行文件中,但是千万不要随便使用这个参数!假如你使用了不止一个静态库文件,然后又使用了这个参数,那么你很有可能会遇到ld: duplicate symbol错误,因为不同的库文件里面可能会有相同的目标文件,所以建议在遇到-ObjC失效的情况下使用-force_load参数。

    -force_load:所做的事情跟-all_load其实是一样的,但是-force_load需要指定要进行全部加载的库文件的路径,这样的话,你就只是完全加载了一个库文件,不影响其余库文件的按需加载

    后期会试着把贝塞尔画饼的demo封装成framework,另外可能会增加Bundle文件的生成方法。

    参考自1、iOS-制作Framework(最新)

              2、iOS--创建你自己的Framework

    最后,哪里不对的地方可以给我留言,我会及时改进的,谢谢大家。

    相关文章

      网友评论

      • d0caed4b9235:很好的陈述,学到了,给作者点赞!
      • 一束强光:楼主,有个疑问,Aggregate编译出来的是debug还是Release版本的 ?
      • gelinxiao:作者的第二种方法中的自动编译有问题啊
      • 姜小栀在北:谢谢写的这么详细,帮助还是很大的。合并真机和模拟器的第二个方法实现了,但是第一个方法,怎么做都还是有问题。下面是用终端合并的时候出现的错误提示,我先贴出来,看看有没有知道的大侠。
        RdeiMac:~ Rong$ lipo -create/Users/Rong/Library/Developer/Xcode/DerivedData/FrameworkTest-efuxkjfmmmcsuqcibiluobrivzqr/Build/Products/Debug-iphoneos/FrameworkTest.framework/FrameworkTest /Users/Rong/Library/Developer/Xcode/DerivedData/FrameworkTest-efuxkjfmmmcsuqcibiluobrivzqr/Build/Products/Debug-iphonesimulator/FrameworkTest.framework/FrameworkTest -output /Users/Rong/Desktop/New/FrameworkTest
        fatal error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/lipo: unknown flag: -create/Users/Rong/Library/Developer/Xcode/DerivedData/FrameworkTest-efuxkjfmmmcsuqcibiluobrivzqr/Build/Products/Debug-iphoneos/FrameworkTest.framework/FrameworkTest
        eb9f7bec7caf:-create 后面少个空格?
      • 周英俊a:请问下 我合并出.lipo 文件了 修改后缀后 也没问题 但是里面没有文件是哪里出错了?
      • PerryMorning:我正在进行SDK的开发,遇到一个问题是,SDK中的图片资源无法显示,想问一下是什么原因导致的,有哪些需要特殊设置的地方吗
      • PerryMorning:我正在进行SDK的开发,遇到一个问题是,SDK中的图片资源无法显示,想问一下是什么原因导致的,有哪些需要特殊设置的地方吗
      • wokenshin:好文章,多谢大神分享!
        关于 文章末尾 总结部分 有一些疑问,还请大神赐教
        8: SDK中使用了 第三方库的解决办法,感觉很麻烦。是不是可以在外部工程中直接导入第三方库,而SDK中通过pods做第三方库的关联[但是这样做的话 会不会在生成静态库的时候 也把第三方库加载进去了呢?] ,我之前写的.a的文件,是将AFN直接拖到SDK里面的,但是并没有暴露任何头文件。在外部引入SDK的时候,外部的项目也引入了AFN 并没有报错,并且不引入 反而会提示缺少对应的头文件。综上,是否可以让外界自己导入依赖库呢?
        11:真机+模拟器支持 这里的脚本不是很懂

        谢谢:smiley:
        wokenshin:@蜜糖阿橙哥c 我是直接讲SDK内部需要用的三方库拖进去的,和外接不会产生冲突
        蜜糖阿橙哥c:同问。 兄逮 你的解决了么
      • MonkeyHan:多谢分享 正需要 写的很详细
      • 小宝二代:不错,Mark,用第一种方法试验成功
      • wellcoding:用这个framework的时候为什么始终提醒could not build module
      • 不辣先生:楼主你好,为啥我打包的framwork在导入demo中的时候是这样的#import "test_tou.framework/Headers/test_tou.h" 为啥不能去掉中间这个Headers?
      • 琦均煞Sylar:#这个是声明生成的framework的名字,有些和工程名字一样,看你创建时候怎么写
        #FMK_NAME是个变量
        FMK_NAME="XXXXXX"
        if [ "${ACTION}" = "build" ]
        then
        INSTALL_DIR=${SRCROOT}/Products/${FMK_NAME}.framework

        DEVICE_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphoneos/${FMK_NAME}.framework

        SIMULATOR_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphonesimulator/${FMK_NAME}.framework


        if [ -d "${INSTALL_DIR}" ]
        then
        rm -rf "${INSTALL_DIR}"
        fi

        mkdir -p "${INSTALL_DIR}"

        cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"
        #ditto "${DEVICE_DIR}/Headers" "${INSTALL_DIR}/Headers"

        lipo -create "${DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${INSTALL_DIR}/${FMK_NAME}"
        #这个是合并完成后打开对应的文件夹,你就可以直接看到文件了
        open "${SRCROOT}/Products"
        fi
        ------------------------------------------
        这位朋友的脚本说实话看不清,我把我用的贴上。
        为楼主的分享点赞:+1:
        gelinxiao:兄台的脚本语言不含自动编译framework,只是把Debug的包和Release包合并。需要手动编译一下就行了
        姜小栀在北:@就是废话多 不知道你解决了没有,一开始我直接用的楼上兄台整理好的脚本,也遇到了和你类似的问题,编译可以通过,但是也有爆红的,爆红内容如下:
        cp: /Users/Rong/Library/Developer/Xcode/DerivedData/FrameworkTest-efuxkjfmmmcsuqcibiluobrivzqr/Build/Products/Debug-iphoneos/XXXX.framework/: No such file or directory
        fatal error: lipo: can't open input file: /Users/Rong/Library/Developer/Xcode/DerivedData/FrameworkTest-efuxkjfmmmcsuqcibiluobrivzqr/Build/Products/Debug-iphoneos/XXXX.framework/XXXX (No such file or directory)
        Command /bin/sh emitted errors but did not return a nonzero exit code to indicate failure

        然后我将楼上兄台的那句FMK_NAME="XXXXXX"改成了FMK_NAME=${PROJECT_NAME}就没问题了。
        wellcoding:cp: /Users/mac/Library/Developer/Xcode/DerivedData/framework-gtfgmcnlqmpokbfivmdfpxbiiipf/Build/Products/Debug-iphoneos/framework.framework/: No such file or directory
        fatal error: lipo: can't open input file: /Users/mac/Library/Developer/Xcode/DerivedData/framework-gtfgmcnlqmpokbfivmdfpxbiiipf/Build/Products/Debug-iphoneos/framework.framework/framework (No such file or directory)
        Command /bin/sh emitted errors but did not return a nonzero exit code to indicate failure
        请问我这样编译失败了,好像是iPhone文件缺失问题吧,这种怎么解决
      • e093b22b36cc:第一种方法 导入头文件 #import <MyFrameworkTest/SayHello.h> 报 'Missing submodule 'MyFrameworkTest.SayHello''的警告,这是什么原因啊
      • 我的时代我开创:为什么我使用自己的sdk,加载sdk后会报找不到sdk的错误啊
      • 要开心:Good Job!不过有点疑问,我用的第一种方法封装的,你说的控制台合并并替换原文件以后,phoneos和iphonesimulator这两个文件夹下的framework都是一样的了是不?导入其它项目中时,这两个framework用哪个都行是吗?
      • liyaoyao:我用这行执行这行命名 xcodebuild -target ${TARGET_NAME} -configuration Release -sdk iphoneos CONFIGURATION_BUILD_DIR=${SIMULATOR_DIR} 然后脚本就一直在循环运行,请问你遇到过没?
      • 69de80985ad6:大神,其他的我都会了,就是生成framework有没有什么方法把framework生成到指定目录下(因为每次都是自动生成到工程目录下的product文件下)比如说桌面(因为我接手上一个人时,他向我演示过一遍,直接生成到桌面了)
        和珏猫:@lang小逸 客气了。主要是封装,至于封装到哪里,只要是你能找到的地方,我觉得就可以。
        69de80985ad6:@和珏猫 可能这个功能没什么实际意义,所以百度上大家都没提到这点,没事,我去问问他好了,谢谢你的回复。
        和珏猫:@lang小逸 百度一下看看能不能修改指定路径?既然你看到了能在桌面生成,那么一定有办法做到的:stuck_out_tongue:
      • Haha_kd:我是用第一种方法制作sdk,并且能成功sayhello 但是我用lipo -info指令查看了lipo的版本,只有arm64和x86_64 也就是说,armv7 armv7s都没有.arm64是我的真机iPhone6s
      • 高高_叔叔:第二方方法我编译通过了但是没有跳出保存页面啊。
      • magicMushroom:楼主,为啥bulid setting 中要把link standard libraries属性设置为NO啊?为啥不连接标准库啊,求解释?
      • 宁哈哈哈:生成的framework导入另一个工程中,使用时就会报错
        和珏猫:@宁静hxn 报什么错
      • 宁哈哈哈:题主,我按照你的步骤来的,报这个错误,头好大,请赐教。
        Undefined symbols for architecture x86_64:
        和珏猫:@宁静hxn 发现了这个静态库只支持armv7 armv7s i386 . 而在我的工程中Valid Architectures和Architectures中均包含了arm64的指令集,这就是说明我需要编译的app最终要支持arm64的,而程序中用到的静态库并没有arm64,所以才导致了出错,因此,需要我们去重新下载一个支持arm64的静态库文件,那么就可以正常编译通过了.试试这个
      • 往南渡:带有xib文件的怎么弄?
        和珏猫:@小白手 客气了,主要是我不太习惯拉线布局控件,另外我们多人开发,拉线可能不适合。所以也就没有研究这方面,没能帮助你,太不好意思了。
        往南渡:谢谢啊,自己试着弄了两次带xib的,不过都没有成功
        和珏猫:@小白手 这个真没研究过,xib和storyboard不喜欢
      • 左饵ear:两种方法都试过了,但是第一种去掉后缀之后的文件导入到例子🌰中看不到头文件,还应该加什么吗?
        左饵ear:就一个文件,导入之后也只有文件。对照前面的配置是没错的,如果错了那么也不会编译成功,是真机编译的。
        和珏猫:@左饵ear 不应该啊,应该可以看到啊,是不是你漏了哪一步?
      • 清清R:请教一下,你这个封装是已经实现全部功能的Framework包,可以直接编译导出使用.
        但如果在开发过程中还要在SDK中修改并添加新功能并直接运行测试检查,要怎么将SDK与当前项目放在同一个工程中编写呢? 具体怎么操作,请教一下,谢谢!
        清清R:@赵丽颖技术组 已经掌握这个技巧了,谢谢
        SwiftUI:如果你的项目是工作空间,你可以添加project来创建动态库静态库。如果你的项目是project,你可以添加target来创建动态库静态库。
      • 8e957eb79c3e:请问下,第二种方式的header是在哪添加的,我这直接生成是没有的
        8e957eb79c3e:@和珏猫 就是在Build Phases里面,但是Build Phases是自带的target下面的,第二种方式不是生成一个新的target吗
        8e957eb79c3e:@和珏猫 是在framework那个target下 是吗,我那个是添加了的
        和珏猫:@米斯特张专属秋秋 header添加是在两个方法之前啊
      • 我的时代我开创:.lipo 在哪 为什么我合并报错了 但是没找到这个文件 不在我要他生成的那个文件夹里啊
        我的时代我开创:@和珏猫 谢谢 我解决了,合并的是二进制文件,我合并错误了
        我的时代我开创:我放在了桌面下一级的文件夹里但是和那个文件并列的桌面上没有.lipo文件啊
        和珏猫:@我的时代我开创 会在和我们-output的文件夹路径并列的地方生成一个.lipo文件

      本文标题:iOS封装功能生成 .framework

      本文链接:https://www.haomeiwen.com/subject/vsoxsttx.html