美文网首页开发配置swift是未来开发技巧分类
Swift构建通用版本Framework以及Framework的

Swift构建通用版本Framework以及Framework的

作者: XcqRomance | 来源:发表于2016-12-26 18:49 被阅读3917次

    前段时间公司app中有个BookRoom模块,也就是绘本阅读的模块需要重新做,并且封装成framework的形式进行(fcs项目已经很大,很冗余,每次Xcode编译项目都需要大量的时间,至少有5分钟),所以使用swift构建的framework遇到的问题坑我基本上都遇到过。于是想把整个过程记录下来,肯定有人和我一样遇到类似的问题,以便下次遇到可以快速解决;

    一、新建Framework项目

    1. 新建项目选择Cocoa Touch Framework项目,取名为BookRoomKit,选择Swift语言


      新建framework
      bookroomkit
    2. 然后新建一个BookRoomManager单例类用来传值设置,并且需要申明为public,这样我们才能够在其他项目上使用我们提供的framwork的代码;

    3. 编译framework;我这里以真机发布版本framework的为例。
      首先使用Product->Scheme->Edit Scheme 打开如下界面,设置为Release环境,

      设置编译环境为release
      然后按照下图选择,第1步代表真机运行,第2步运行,第3步代表编译成功的BookRoomKit.framework;
      编译framework
      选中BookRoomKit.framework,右击Show inFinder,会显示编译好的framework在finder中的位置,如下图:
      show in finder
    4. 接下来我们新建一个壳工程,也就是测试工程,用来使用BookRoomKit.framework;并且命名为BookRoomDemo,如下图:

      BookRoomDemo App
      BookRoomDemo
      将BookRoomKit.framework拖入项目中,勾选下面三项,然后点击Finish
      勾选的三项
      编写代码前,记得将壳工程切换为release环境,然后真机运行,因为编译的framework必须与壳工程运行的环境一致才能够运行成功。
      编写测试代码
      运行之后你会发现,程序运行不起来,奔溃的错误信息是:
    dyld: Library not loaded: @rpath/BookRoomKit.framework/BookRoomKit
      Referenced from: /var/containers/Bundle/Application/C3208286-60C0-456D-B512-C26FB1E6A254/BookRoomDemo.app/BookRoomDemo
      Reason: image not found
    
    奔溃信息日志
    这里就涉及一个问题,那就是我之前的一步操作是直接从Finder中将framework拖拽进壳工程项目的,这样操作默认会将framework放置在Linked Frameworks and Libraries下面,如下图;

    第一:Embedded Binaries和Linked Frameworks and Libraries是有区别,具体区别看这篇文章What is the difference between Embedded Binaries and Linked Frameworks;第三:从ios8开始,苹果官方支持我们构建的dynamic framework,我们构建的BookRoomKit.framework就是dynamic frameworks,这种类型的framework它需要签名code-signed以及嵌入到我们的app,否则我们真机运行时就会奔溃Embedded Binaries with iOS Framework,还有一个问题就是,我们构建的BooRoomKit.framework往往需要使用第三方的网络请求框架,JSON解析框架等等,这些都因为代码签名的问题都得在壳工程中添加
    说了这么多,解决方案就是:选中Linked Frameworks and Libraries下面的BookRoomKit.framework点击删除,然后将左侧导航栏的framewotk拖拽到Embedded Binaries下面,这时Linked Frameworks and Libraries默认也会有,如下图:
    正确的方法
    再次运行,你会发现运行成功,而且打印了hello bookroom
    hello bookroom

    二、Framework合并

    1. 使用lipo -info查看framework支持的cpu架构,分别对应真机版本和模拟器版本的framework信息
      lipo -info
    2. 使用lipo -create指令将模拟器和真机的framework合并成通用版本
      真机与模拟器的framework
      具体操作如下:首先,切换到/BookRoomKit/Build/Products;然后执行lipo -create -output [name] [path1] [path2]这条命令;执行完成后在Products目录下生成一个BookRoomKit文件。
      lipo -create1
      lipo -create2
      然后将上图红圈的文件复制到Release-iphoneos中去覆盖原来的版本,最后将Release-iphonesimulator中的框架文件里的/Modules/BookRoomKit.swiftmodule里的文件复制到Release-iphoneos对应的文件夹下。这样我们就得到了一个通用的的框架。
      通用的framework
      最后用这个framework替换掉测试工程的framewotk就可以在真机和模拟器运行了

    三、测试framework专用

    为了便于测试工程使用framework,我这里教大家把两个项目放到一个Xcode里面打开,也不用Xcode打开两个项目,这样便于测试使用,也不用来回在framework工程和壳工程之间切换。操作如下:关闭BookRoomKit项目,打开BookRoomDemo项目,将之前的操作取消回退,删除集成进来的Book Room.framework。然后将BookRoomKit.xcodeproj拖拽进BookRoomDemo项目中,编译BookRoomKit,如下图:

    使用一个项目打开
    然后将编译好的framework拖拽进BookRoomDemo中的Embedded Binaries,如下图
    3F6F9E91-A8CB-4ACE-B0B4-D21ABD9C5BA8.png
    通过上面的操作你就可以方便的测试framework编写的代码,不用每次都编译构建framework,当你运行BookRoomDemo的时候你就能够使用framework暴露出来的方法调用。

    四、Framework中使用image,xib,storyboard,font等资源文件

    1. image, xib,storyboard都是需要传递相应的bundle。而我看到网上一些旧的教程都很繁琐,还要建立什么resources.bundle之类的。经过我的实践,我这种方法更为简单。
    let bundle = Bundle(identifier: "xcqromance.BookRoomKit") // framework的bundle ID
    

    storyboard的加载,xib类似

     let sb = UIStoryboard(name: "BookHome", bundle: bundle)
     let vc = sb.instantiateViewController(withIdentifier: "BookViewController") as! BookViewController
     viewController.navigationController?.pushViewController(vc, animated: true)
    

    image的加载

    let image = UIImage(named: "bookroom_down_bg_blue", in: bundle, compatibleWith: nil)
    let imageView = UIImageView(image: image)
    
    1. font的加载则是比较麻烦,得先注册,才能使用!所以我写了个OC的NSObject分类
    + (UIFont *) loadMyCustomFont:(NSString *)name size: (CGFloat)size type: (NSString *)type {
      NSString *fontPath = [[NSBundle bundleWithIdentifier:@"xcqromance.BookRoomKit"] pathForResource:name ofType:type];
      NSData *inData = [NSData dataWithContentsOfFile:fontPath];
      CFErrorRef error;
      CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)inData);
      CGFontRef font = CGFontCreateWithDataProvider(provider);
      if (! CTFontManagerRegisterGraphicsFont(font, &error)) {
        CFStringRef errorDescription = CFErrorCopyDescription(error);
        NSLog(@"Failed to load font: %@,%@", errorDescription,name);
        CFRelease(errorDescription);
      }
      CFRelease(font);
      CFRelease(provider);
      NSString *fontName = (__bridge NSString *)CGFontCopyPostScriptName(font);
      UIFont* uifont = [UIFont fontWithName:fontName size:size];
      return uifont;
    }
    

    然后在单例模初始化的时候进行注册。使用这个字体的方法和平时的一样,将fontName传递下就可以了

    let label = UILabel()
    label.text = "hello word"
    label.sizeToFit()
    label.center = view.center
    label.font = UIFont(name: "Kreon-Bold", size: 17)
    

    Github上面下载BookRoomDemo

    ---------------------2017.04.10更新---------------------
    每次使用终端lipo -create创建通用版本很繁琐、低效,于是想到了将这个过程脚本化;

    使用脚本一键构建通用版本的framework(真机、模拟器通吃的版本)

    步骤如下:

    1. build active architecture only设置为No
      build active architecture only -> No
    2. 新建一个target,用来构建通用版本framework


      new target

      选择Cross-platform->other->Aggregate->Next


      Aggregate
      命名为univeralBuilder,新建一个New Run Script Phase
      D124D6A4-B28B-4632-95F2-568278624B06.png

      在shell里面添加以下内容,注意将第九行的FRAMEWORK_NAME改为自己framework的名字。

    # Merge Script
    
    # 1
    # Set bash script to exit immediately if any commands fail.
    set -e
    
    # 2
    # Setup some constants for use later on.
    FRAMEWORK_NAME="Your framework name" 
    
    # 3
    # If remnants from a previous build exist, delete them.
    if [ -d "${SRCROOT}/build" ]; then
    rm -rf "${SRCROOT}/build"
    fi
    
    # 4
    # Build the framework for device and for simulator (using
    # all needed architectures).
    xcodebuild -target "${FRAMEWORK_NAME}" -configuration Release -arch arm64 -arch armv7 -arch armv7s only_active_arch=no defines_module=yes -sdk "iphoneos"
    xcodebuild -target "${FRAMEWORK_NAME}" -configuration Release -arch x86_64 -arch i386 only_active_arch=no defines_module=yes -sdk "iphonesimulator"
    
    # 5
    # Remove .framework file if exists on Desktop from previous run.
    if [ -d "${HOME}/Desktop/${FRAMEWORK_NAME}.framework" ]; then
    rm -rf "${HOME}/Desktop/${FRAMEWORK_NAME}.framework"
    fi
    
    # 6
    # Copy the device version of framework to Desktop.
    cp -r "${SRCROOT}/build/Release-iphoneos/${FRAMEWORK_NAME}.framework" "${HOME}/Desktop/${FRAMEWORK_NAME}.framework"
    
    # 7
    # Replace the framework executable within the framework with
    # a new version created by merging the device and simulator
    # frameworks' executables with lipo.
    lipo -create -output "${HOME}/Desktop/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}" "${SRCROOT}/build/Release-iphoneos/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}" "${SRCROOT}/build/Release-iphonesimulator/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}"
    
    # 8
    # Copy the Swift module mappings for the simulator into the
    # framework.  The device mappings already exist from step 6.
    cp -r "${SRCROOT}/build/Release-iphonesimulator/${FRAMEWORK_NAME}.framework/Modules/${FRAMEWORK_NAME}.swiftmodule/" "${HOME}/Desktop/${FRAMEWORK_NAME}.framework/Modules/${FRAMEWORK_NAME}.swiftmodule"
    
    # 9
    # Delete the most recent build.
    if [ -d "${SRCROOT}/build" ]; then
    rm -rf "${SRCROOT}/build"
    fi
    

    3.最后一步:选择Aggregate target和Simulator然后build,你会看到桌面由构建好通用版本的framework。
    参考资料:http://arsenkin.com/ios-universal-framework.html

    2017.11.07添加

    五、Framework依赖第三方库

    有人在下面提问,如果我新建的framework还依赖第三方库怎么解决?而这也是一个很正常的需求。比如我的BookRoomKit.framework有一个绘本zip包解压的功能,此时我有两种解决方案,第一将ZipArchive的代码拖入到我的framework中,还有一种是ZipArchive.framework引入到我的framework中。明显我们会选择第二种方式,一旦有更新直接替换新的的framework就行。而第二种方式又有两种方法引入:第一:在壳工程中使用pods、carhtage第三方库管理工具来添加;第二:直接将依赖的framework拖入壳工程。其实这两种方式的本质都是一样的,都有一个很关键的点就是Framework 的Build Settings中设置好依赖的第三方库的Framework Search Paths,同时在Build Phases的Link Binary With Binaries添加依赖的第三方库,下面我就以第二种方法直接将依赖的第三方库拖入壳工程进行配置为例。

    1. 将下载好的ZipArchive.framework拖入壳工程,记得勾选Copy items if needed,此时你在壳工程就已经能够使用这个库了,这是因为你拖入第三库ZipArchive时Xcode已经给你配置好Framework Search Paths,你可以在壳工程的builds settings搜索到。如下图:

      但是你此时在壳工程中使用ZipArchive.framework,运行起来是会crash的,奔溃信息和前文一张名为奔溃信息日志的图片是一样的,
    dyld: Library not loaded: @rpath/ZipArchive.framework/ZipArchive
      Referenced from: /var/containers/Bundle/Application/74F5D1D3-D6B5-4C68-9629-9CAF6C16F133/BookRoomDemo.app/BookRoomDemo
      Reason: image not found
    

    所以解决方案也就是一样的,在EmbeddedBinaies中添加ZipArchive.framework

    1. 设置BookRoomKit.xcodeproj的framework Search Paths
      路径务必填写正确,我写的相对路径:$(PROJECT_DIR)/../BookRoomDemo/BookRoomDemo
    2. BookRoomKit.xcodeproj的Link Binary With Binaries添加ZipArchive.framework,选在Add Other,添加进来即可。
      接下来你就可以在BookroomKit使用ZipArchive了,具体代码你可以在BookRoomDemo下载。

    如果你觉得本文对你有帮助,就请你点亮底部的❤️吧,你的鼓励是我前进的动力!

    相关文章

      网友评论

      • 慧煎蛋:let bundle = Bundle(identifier: "xcqromance.BookRoomKit") // framework的bundle ID

        我自己要 注意写完整 framework的bundle ID
      • JoeXP:实测mach-0为Dynamic时,加载图片的方法有效,为Static这个方法就无效了,是否还是得用Bundle的形式
        XcqRomance:@JoeXP 编译的framework使用静态库和动态库都是可以的提供给项目使用,并且上线到appStore。我们公司的app一个阅读模块使用的就是动态库。只要不涉及苹果私有api,都会通过。
        JoeXP:@XcqRomance 好的!谢谢啊,困扰我好久!那么现在是否还是只能提供静态framework给上架的app使用呢。
        XcqRomance:@JoeXP 如果是静态库,图片或者其他资源就必须使用bundle的形式加载了
      • 只敲代码不偷桃:你好,如果framework里依赖了第三方库该怎么解决呢(这点和OC好像还不太一样)?
        XcqRomance:@只敲代码不偷桃 将framework依赖的第三方库拖入壳工程工程中,或者使用pod,Carthage 管理道理都一样。就是得在framework 的build settings中设置好依赖的第三方库的framework search path,同时在build phases的link with binary添加依赖的第三方库进来就行!
      • 冰三尺:我找了这么多文章, 所有的文章都千篇一律, 只有你的文章说到了, 要把模拟器合并之后的文件copy到真机下. 我在想, 其他人写的文章, 不过不把模拟器下的文件copy一份到真机下, 真的能够在模拟器和真机下都能运行, 不知道他们是怎么做的?谢谢楼主的文章, 解决了我的问题.
        lotawei:一样的复制在真机模拟器下framework替换都可以
        XcqRomance:@里脊糖醋 我的愿望是自己踩过的坑,不让其他人再踩!:smiley:
      • 长路漫漫_xq:oc中集成swift动态库,打包的时候库会很大。这个有解决的办法吗?
        XcqRomance:@顾先全 这个只有等苹果爸爸将swift语言支持ABI稳定了才能做到!我们能做的只有等,希望swift 5能实现吧!:pray:
      • 神奇水滴:我的 Framework 裡面有再使用 pods 依賴其他的framework
        使用 aggregate 這個 target 創見時會提示找不到依賴的 framework
        這代表我也要讓 aggregate 這個 target 加入 pods 的依賴嗎
        但是又不知該如何把 pods 加入 aggregate 這個 target 裏
        XcqRomance:是的,你的这个思路是对的!报错的原因就是因为pod引入的第三方库并不是通用库,所以你得在framework的Build Settings中设置framework search path时设置的pod依赖为通用版本路径。所以我给你解决方案的思路就是添加一个脚本,对所以依赖的pods库进行通用版本处理。
      • lnoctis:将 app 发布到 App Store 的时候会报出这些错误 90080,90087,90209,90125 求怎么解决
        XcqRomance:错误信息提供详细些吧,把报的错误全部发过来看下!
        lnoctis:@Xcq_Romance archive的时候没问题 在上传到 appstore 的时候报出来的
        XcqRomance:@lnoctis 这是哪个环节的问题?我发布到AppStore 可从来没有出现这种错误
      • 王仁洁:我自定义了一个UITextfiled类,打包成.framework,在自己的项目里的storyboard中没法使用,但也不警告不报错,请问有解决方案吗?
        XcqRomance:@王仁洁 不报错,不警告说明编译通过了,你看下是不是没有用public修饰符修饰自定义的UItextfield类
      • LD_左岸:Build Setting 中 defines module 设为Yes了
        product module name 也是工程的名字
        还报工程名-swift.h文件找不到?
        项目是framework
        XcqRomance:@左岸__ 你是在壳工程里引用编译好的framework报的这个错误吧!

      本文标题:Swift构建通用版本Framework以及Framework的

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