美文网首页iOS 开发iOS开发技巧分类
Swift制作Framework,提供给OC项目和Swift项目

Swift制作Framework,提供给OC项目和Swift项目

作者: JoeXP | 来源:发表于2018-02-03 13:34 被阅读573次

    Swift制作framework

    公司的需要需要制作sdk给其他团队用,其实就是framework
    简直炸裂!踩了一个又一个的坑!
    遍体鳞伤之后,决定一定要记录下来,方便以后自己和有需要的人查阅,能有一点点帮助也是好的

    进入正题

    官方提供的是.framework.a两种方式制作SDK的方式。
    分别对应创建工程时下方的Cocoa Touch FrameworkCocoa Touch Static Library
    .framework.a两者区别自行百度吧,简书上制作Framework相关的文章基本都有,就懒得copy了。
    .framework其实是分动态静态的,所以.framework即可满足我的要求,想要制作动态或者静态创建framework后可以修改,往后面看。

    ps: 
    在 iOS 8 之前,
    iOS 平台不支持开发者使用用户自己的动态 Framework,这种限制可能是出于安全的考虑。
    换一个角度讲,因为 iOS 应用都是运行在沙盒当中,不同的程序之间不能共享代码,
    同时动态下载代码又是被苹果明令禁止的,没办法发挥出动态库的优势,
    实际上动态库也就没有存在的必要了。
    
    但是,iOS 8/Xcode 6 推出之后,
    iOS添加了对动态库的支持,为什么 iOS 8 要添加动态库的支持?唯一的理由大概就是Extension 的出现。
    Extension 和 App 是两个分开的可执行文件,同时需要共享代码,这种情况下动态库的支持就是必不可少的了。
    但是这种动态 Framework 和系统的UIKit.Framework 还是有很大区别。
    系统的 Framework 不需要拷贝到目标程序中,我们自己做出来的 Framework 哪怕是动态的,
    最后也还是要拷贝到 App 中(App 和Extension 的 Bundle 是共享的),
    因此苹果又把这种 Framework 称为 Embedded Framework
    

    手洗干净,挽起袖子干 0_0

    第一步:创建Framework工程

    运行 XCode -> Cocoa Touch Framework -> 取个名, 语言选择 Swift -> 创建成功

    第二步:基本设置

    创建完不急着编写代码,先做一些设置:

    1. 修改最低的系统要求,建议当然低一些好

    2. mach -0 type ,即选择动态库or静态库(甚至Object File)
      想知道这几种type的区别可以移步
      参考浅谈 SDK 开发(一)五种 Mach-O 类型的凛冬之战
      这里我选择默认的Dynamic Library即动态库

    3. Architectures 该编译选项指定了工程将被编译成支持哪些指令集,支持指令集是通过编译生成对应的二进制数据包实现的,如果支持的指令集数目有多个,就会编译出包含多个指令集代码的数据包,造成最终编译的包很大。
      那么指令集是什么呢:

      **iPhone指令集**,苹果处理器支持两个不同的指令集:
      32位ARM指令集(armv6|armv7|armv7s)和
      64位ARM指令集(arm64),i386|x86_64 是Mac处理器的指令集,
      i386是针对intel通用微处理器32架构的。
      x86_64是针对x86架构的64位处理器。
      当使用iOS模拟器的时候会遇到i386|x86_64,iOS模拟器没有arm指令集。
      

      所以我们看看我们需要什么指令集

      1、debug环境下
      设备:arm64(测试机型有限: 6P、5、7 )
      模拟器:iPhone7-Plus:x86_64、iPhone4s:i386
      2、release环境下
      设备:armv7、arm64
      模拟器:i386、x86_64
      

      以上所生成的framework均不包含armv7s, 在 Building Setting 中设置一下 Architectures,在原有基础上添加一行 armv7s ,如下:



      在原有基础上增加 armv7s


    4. Build Active Architecture Only 意思是: 该编译项用于设置是否只编译当前使用的设备对应的arm指令集
      当该选项设置成YES时,你连上一个armv7指令集的设备,就算你的Valid Architectures和Architectures都设置成armv7/armv7s/arm64,还是依然只会生成一个armv7指令集的二进制包
      Release模式为发布模式,需要支持各种设备指令集,所以设置为NO


    5. Valid Architectures 设置的支持arm指令集。指令集的版本有:armv7/armv7s/arm64。
      假设Architectures设置的支持arm指令集版本只有:arm64时,这时Xcode只会生成一个arm64指令集的二进制包
      所以这里我们都不用改,都包含进来就好了

    6. Dead Code Stripping, 设置为 NO 关闭对代码中“dead”,“unreachable”代码过滤

    7. Link With Standard Libraries 设置为 NO 避免重复链接

    8. Build 环境 设置build环境为release环境下


    第三步:编写代码

    在此之前,我们先command+B 看看是否成功


    build success 并看到Products 下的 文件 XPKit.framework 由红变黑,说明制作成功,右键show in finder

    这就是我们的framework,只是里面啥都还没写- -。
    好了,咱们抓紧写几句,饥渴难耐了吧,但是还是先跟着我来吧,弄明白了再去写自己的代码,少踩好多坑
    创建一个Manager类继承自 NSObject

    写上这么个 func
    @objc public class XPManager: NSObject {
        
        @objc public func sayHello(){
            print("XPKit-->: hello")
        }
        
        public func sayWorld() {
            print("XPKit-->: world")
        }
        
        @objc func saySwift() {
            print("XPKit-->: Swift")
        }
    }
    

    完毕, command + B ,报错了!来看看为啥:

    ld: symbol dyld_stub_binder not found (normally in libSystem.dylib).  Needed to perform lazy binding to function __T0s23_ContiguousArrayStorageCMa for architecture i386
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    

    这里提示我们少了一个系统库libSystem.dylib,我也不知道为啥,那我们就给加上呗,来到PROJECT->Build Phases->Link Binary With Libraries->+ 加上libSystem.tbd


    这时候再回来编译,发现Build Success。目的达到了 0-0!

    第四步:测试写好的Framework

    1. 先来测试Swift项目调用swift的framework

    先到Products 下的 XPKit.framework 右键 show in finder找到 XPKit.framework

    紧接着 create 一个 swift 工程app single view app,并将👆的XPKit.framework拖到工程中,记得勾选 copy if needed

    import XPKit
    

    然后 command + 左键 进入,可以看到暴露出来能用的方法

    image.png
    这里我们能发现我们写了public 修饰的 都暴露出来供app调用
    import UIKit
    import XPKit
    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            let manager = XPManager()
            manager.sayHello()
            manager.sayWorld()
        }
    }
    

    command + R Run一下发现报错了

    dyld: Library not loaded: @rpath/XPKit.framework/XPKit
      Referenced from: /Users/midoo/Library/Developer/CoreSimulator/Devices/8F8ADF9B-D5DD-45A1-B925-03BCBB11D9FF/data/Containers/Bundle/Application/C7D5F85A-DDC8-4BC7-964A-FDC4B5154C5A/SwiftInvokDemo.app/SwiftInvokDemo
      Reason: image not found
    (lldb) 
    

    这里因为我们制作的frameworkdynamic library动态的,所以我们到Project->General->滑到最下方


    选中 linked frameworks and libraries 中的 XPKit.framework-号 删除,再在上方 Embedded Binaries+号XPKit.framework 加回来,发现上下都有了,如:

    这时候再command + R Run,就不报错了,并且成功打印:
    2. 再来测试Swift项目调用swift的framework

    同理 create 一个 OC 的 app,拖进我们的 XPKit.framework 并在 Project->General -> Embedded Binaries 下添加进去
    这个时候就要导入#import <XPKit/XPKit-Swift.h> 而不是 #import <XPKit/XPKit.h>
    #import <XPKit/XPKit-Swift.h>OC 项目导入 Swift Framework 时自动产生的文件, 给我们展示可以用哪些接口
    command + 左键 点到这个文件里去可以看到

    image.png
    这时候我们就会发现,XPManager 我们的类暴露出来了,但是方法只有一个sayHello()

    所以敲黑板,划重点

    我们制作 swift framework 的时候,一定要注意可用性,因为难免会遇到让OC调用的时候。
    所以要在暴露在外给人家用的话,一定要写上修饰词 `@objc` 与 `public` 缺一不可
    

    而我们的类,即 Class ,继承了 NSObject 那么即使不写 @objc , 也是OK的,但是属性func一定要写

    可以做个测试,在framework工程中写:

    public class XPTest: NSObject{
        public func sayWorld() {
            print("XPKit-->: world")
        }
        @objc public func sayHello(){
            print("XPKit-->: hello")
        }
    }
    

    重新 build , 注: (每次重新build才会更新framework)
    并删除 OC-App 下的 framework 。重新拖到项目中并添加到Embedded Binaries
    发现,XPKit-Swift.h下暴露的是这样的:

    @interface XPTest : NSObject
    - (void)sayHello;
    - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
    @end
    

    所以如果是 Class 继承了 NSObject 那么即使不写 @objc , 也是OK的,但是属性func一定要写
    这里就是简单介绍,还有些细节你们可以慢慢测试

    第五步:创建可以直接调试的工程

    如果你都跟着步骤写了以后,会发现一直 Build, 一直拖,一直添加,太繁琐。所以咱们可以这样创建一个 workspace 将两个项目都包含进去

    1. 新建项目或者我们把原先的添加了的framework先丢掉,左上角 File -> save as workspace

    2. 关闭这个.xcodeproj 文件重新打开这个 .xcworkspace

    3. 将我们的 XPKit 项目 拖到 workspace 中,与 Demo-App 并行


      这个时候就会发现,我们就有两个项目了可以分别 build
    4. Demo工程文件 -> General ->embedded binaries 中将 XPKit 下的 .framework 加进来

    5. 试一试,scheme 选择 Demobuild 一下是success
      我们再来到我们的 XPManager.h ,添加几行代码(当测试)

    public class XPHomeViewController: UIViewController{
        @objc public let size = CGSize(width: 40, height: 40)
        @objc public var point: CGPoint = CGPoint(x: 20, y: 20)
        public var content: String?
        @objc public var textColor: UIColor?
        
        public override func viewDidLoad() {
            super.viewDidLoad()
            self.view.backgroundColor = UIColor.red
        }
        @objc public func sayHello(){
            print("XPKit-->: hello")
        }
        
        public func sayWorld() {
            print("XPKit-->: world")
        }
    }
    

    当然还是先build一下我们的XPKit,否则Demo里的framework不更新呐,再到OC项目中import并点进去看看。
    注:我这里试了几次总是没有自动补全,那就自己手写 import地址吧

    #import <XPKit/XPKit-Swift.h>
    

    点进去发现

    @interface XPHomeViewController : UIViewController
    @property (nonatomic, readonly) CGSize size;
    @property (nonatomic) CGPoint point;
    @property (nonatomic, strong) UIColor * _Nullable textColor;
    - (void)viewDidLoad;
    - (void)sayHello;
    - (nonnull instancetype)initWithNibName:(NSString * _Nullable)nibNameOrNil bundle:(NSBundle * _Nullable)nibBundleOrNil OBJC_DESIGNATED_INITIALIZER;
    - (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)aDecoder OBJC_DESIGNATED_INITIALIZER;
    @end
    
    
    SWIFT_CLASS("_TtC5XPKit9XPManager")
    @interface XPManager : NSObject
    - (void)sayHello;
    - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
    @end
    
    
    SWIFT_CLASS("_TtC5XPKit6XPTest")
    @interface XPTest : NSObject
    - (void)sayHello;
    - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
    @end
    

    哇,完美,并且,可以直接在项目里用。那么边写边测,还能设断点,完美~跟平时写app没两样,其实还差最后一步 >_<.
    当你调用并且运行,发现又报错了

    dyld: Library not loaded: @rpath/libswiftCore.dylib
      Referenced from: /Users/midoo/Library/Developer/Xcode/DerivedData/OCInvokDemo-fvmdsbbzuqqnjnbswwgpcidptord/Build/Products/Debug-iphonesimulator/XPKit.framework/XPKit
      Reason: image not found
    

    并且这次我们已经加到 Embedded Binaries 中了,原因是,如果我们在OC项目中引用swift framework,还需要到 build setting 中设置如下:

    image.png
    到此为止才是真正完美 ~

    第五步:创建通用包

    之前说过iphone指令集, 真机与模拟器是不同的,所以编译出来的包也是不通用的,现在我们要做通用的包提供别人使用。

    • 切换到XPKit, 选择任意一个模拟器build一下,
    • 再选择Generic iOS Device ,build 一下
    • show in finder,我们只看release包的,不要关闭finder
    • 我们需要用到终端来创建通用包,打开我们的终端
    • cd 到一个文件目录下,为了存放我们制作的包
    cd /Users/midoo/Desktop/测试文件 
    
    • 输入lipo -create空格以后,分别到 release-iphoneosrelease-iphonesimulator 下的 XPKit.framework -> 选择XPKit二进制文件,拖动到 终端下

    • 紧接在后面写上 -output + 你的包名 ,回车
      注:我选择的文件夹下面有 XPKit 文件名,重复了 - -。创建失败...所以换了个文件夹
    • OK,这个时候还没完,只是创建了一个通用的二进制文件,还得把其他东西给他加上,回到release-iphoneosrelease-iphonesimulator,选择任意一个文件夹,整个复制出来
    • 并将制作好的二进制文件拖出来 替换掉


      image.png
    • 还没完!!!再到另一个文件夹,将指令集文件copy到这个,我们这个新的文件夹下



      保证这些都包含在内,那么这个framework包才算制作完成


    • 到此为止我们得到了我们想要的通用包,按照相同的方法,拖到工程中,引入到Embedded Binaries,可以调试了

    第六步:用Shell脚本创建通用包

    创建通用包用到的次数不多,上面的方法够用了,但是如果你还是觉得不方便、很繁琐。那你可以跟我这样做

    • 选择XPKit 工程点击左下角 +
    • 创建一个 Aggregate。去个名字,类似 CommonBuilder
      image.png
    • 选中 CommonBuilder -> Build Phases -> 添加New Run Script Phase
    • 在编辑器内输入我们的脚本代码,请全部复制,黏贴,记得修改第二步引号内的内容为你的framework name
    # 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
    

    如图:


    • scheme 选择 CommonBuilder,任意模拟器,编译,报错了看看报了什么错
    Command /bin/sh failed with exit code 65
    

    你们以后看到这些不用慌,网上看,信息都在上面

    === BUILD TARGET XPKit OF PROJECT XPKit WITH CONFIGURATION Release ===
    
    Check dependencies
    No architectures to compile for (ARCHS=x86_64 i386, VALID_ARCHS=arm64 armv7 armv7s).
    
    ** BUILD FAILED **
    

    分析一下,这里都是我们提到过的指令集。 VALID_ARCHS=arm64 armv7 armv7s 这就是我们开始在 Build Setting ->Valid Architectures 中设置的内容,很明显,意思是脚本里,要制作包含 x86_64i386的包,但是我们的Valid Architectures 中没有。
    那么解决问题就方便了,分别添加x86_64i386


    编译成功~
    来到桌面我们发现XPKit.framework,已经静悄悄的在桌面上了,这就是我们的通用包

    总结一下

    好了,我们总结一下,本篇简单的介绍了一下

    1. 如何用Swift编写,OC项目Swift项目 都能用的 dynamic framework
    2. 如何正确的调试我们的framework
    3. 如何制作通用的 framework 包
    为了能让OC项目也能调用,你还记得 @objc 和 public 吗 ^_^
    

    我们的 framework 如果需要导入其他第三方库,该怎么做
    本来也想写篇文章,有点懒,大家先可以看看
    Swift + framework 的制作(基于pod管理的workspace)

    相关文章

      网友评论

      本文标题:Swift制作Framework,提供给OC项目和Swift项目

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