美文网首页加密累
iOS Framework开发实践-2019

iOS Framework开发实践-2019

作者: 勇往直前888 | 来源:发表于2019-05-10 15:01 被阅读0次

    动态库 vs 静态库

    iOS中的framework既可以是动态库,也可以是静态库

    image.png
    • Framework实际上是一种打包方式,将库的二进制文件,头文件和有关的资源文件打包到一起,方便管理和分发。可以是动态的,也可以是静态的。

    • 系统的Framework 是动态库,不需要拷贝到目标程序中。我们自己做出来的Framework哪怕是动态的,最后也还是要拷贝到App 中(AppExtensionBundle是共享的),因此苹果又把这种 Framework 称为 Embedded Framework

    • iOS 8之前,iOS平台不支持使用动态Framework,开发者可以使用的 Framework 只有苹果自家的UIKit.Framework,Foundation.Framework 等。iOS 8/Xcode 6 推出之后,iOS平台添加了动态库的支持,同时 Xcode 6 也原生自带了Framework 支持(动态和静态都可以)。

    • Xcode创建framework时,默认是动态库,可以改为静态库

    image.png

    如果是动态库

    • Xcode的默认设置,什么都不用改

    • 需要在工程的General里的Embedded Binaries添加这个动态库才能使用,不然会找不到。

    image.png
    • cocoapodsuse_framework!使用的是动态库方式。只不过添加Embedded Binaries的过程通过脚本做了,不需要手动操作。

    • 在主工程的的Frameworks文件夹下,以xxx.framework的形式独立存在。不会和主工程合并。

    • 对于资源文件,(包括图片,故事版等),在这个Frameworks下,与framework中的可执行文件在同一级。

    image.png

    如果是静态库

    • 和主工程合并为同一个,看不到独立的文件。

    • 大多数文章介绍,资源文件(图片,故事版等)需要一个同名的xxx.bundle文件,和静态Framework一同引入到主工程中。编译链接之后,静态Framework已经与主工程合并为一个,看不到,但是这个xxx.bundle文件却是在yyy.app包中,和主工程同一级别。所以访问资源,就是访问这个xxx.bundle中的资源。

    image.png
    • 如果用cocoapods打包,也就是不加use_framework!,打出来的就是静态库,同名的xxx.bundle文件会自动生成。

    • 可以简单理解为“静态的framework”只能放代码,不能放资源。使用后,“静态的framework”和主工程代码合并,成为一个可执行文件。至于资源文件,需要放在一个独立的“bundle”文件中,加入主工程。

    选哪种?Embedded Framework

    • 个人偏向于选择动态的framework,也就是Embedded Framework

    • 静态framework需要将资源文件分离出来,额外创建同名的.bundle文件,需要多很多步骤。本来,framework本质上是一种打包方式,可以将代码,头文件,资源都放在一起;但是现在又要将资源文件重新独立出来,感觉在走回头路。

    • 动态的framework,也就是Embedded Framework,在使用后仍然能保持独立性,分离更彻底。并且把代码,头文件,资源文件都打包在一起,对外统一提供接口,有更好的封装和隔离。

    • 今后,扩展会有发展,“共享”的成分会逐步增多。

    • 从本质上来说,静态framework只能包含代码和头文件,并不能包含资源,没有把framework的优势体现出来。

    资源读取

    • 这里只考虑动态的framework的资源读取过程。至于静态framework,其实应该从主工程的角度来考虑资源读取问题,这里暂时不考虑。

    • 可以把动态的framework当做一个独立的打包单元,或者文件夹,或者是一个比较大的独立的bundle。资源(图片或者故事版)和可执行文件处于同一级。(XCode默认build之后是这样的)

    image.png
    • Frameworks文件夹是General->Embedded Binaries操作之后固定生成的,下面可以放很多的framework。
    • KJTCashierDemo是主工程的可执行文件。
    • KJTSingle.storyboaddc是主工程中的资源
    • KJTCashier是framework中的可执行文件,与主工程的可执行文件相互独立。
    • KJTPay.storyboaddc是framework中资源,与KJTCashier在同一目录,由KJTCashier来读取,跟主工程KJTCashierDemo没关系。
    • KJTCashier. framework相当于一个独立的bundle,可执行文件,资源等全部自给自足,跟主工程的可执行文件和资源都没有关系。
    • 资源(图片,故事版)等,在读取时,都有一个bundle参数。所以,读取的关键就是确定bundle参数。
    // 读图片函数
    + (nullable UIImage *)imageNamed:(NSString *)name inBundle:(nullable NSBundle *)bundle compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection NS_AVAILABLE_IOS(8_0);
    
    // 故事版函数
    + (UIStoryboard *)storyboardWithName:(NSString *)name bundle:(nullable NSBundle *)storyboardBundleOrNil;
    
    • 如果bundle参数为nil,那么就是只主工程的bundle,所以,有不需要bundle参数的图片读取函数。
     // load from main bundle
    + (nullable UIImage *)imageNamed:(NSString *)name;     
    

    如何确定bundle参数?

    • 对于动态framework,它本身就是一个bundle。打包之后,与主工程的资源和可执行文件都在一个.app包中。

    • 动态framework这个bundle,包含自己的可执行文件,资源等等,它们在同一目录下。

    • 可以通过下面这个函数,确定动态frameworkbundle

    // 可执行文件和资源同在一个bundle中,类编译后就在可执行文件中
    return [NSBundle bundleForClass:[self class]];
    

    生成framework之后,所有的类都被编译到了动态framework的可执行文件中。类所在的bundle,也就是可执行文件所在的bundle,也就是动态framework这个bundle

    例子代码

    // 从故事版读取Controller
    + (nullable __kindof UIViewController *)controllerWithStoryboardName:(NSString *)storyboardName identifier:(NSString *)identifier {
        UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle: [NSBundle bundleForClass:[self class]]];
        if (storyboard == nil) {
            return nil;
        }
        __kindof UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:identifier];
        vc.hidesBottomBarWhenPushed = YES;
        return vc;
    }
    
    // 读取本地图片
    + (nullable UIImage *)imageWithName:(NSString *)name {
        UIImage *image = [UIImage imageNamed:name inBundle:[NSBundle bundleForClass:[self class]] compatibleWithTraitCollection:nil];
        return image;
    }
    

    对外的头文件

    • 系统会默认生成一个和framework同名的头文件,作为对外的唯一接口。这个文件要保留好,不要删除。这个文件如果不存在,会有警告。

    • 其他要暴露的头文件,需要在Build PhaseHeader里面添加到Public部分。

    image.png
    • 并且需要包含在默认生成的,与framework同名的头文件中。
    image.png

    合并framework

    • 一般的使用习惯,先用模拟器,然后用真机。但是XCode生成的framework,要么是模拟器的,要么是真机的,很不方便。

    • 所以,网上很多文章就提出了使用脚本命令进行合并的方法,很好用。

    1. Build Phases 中点击左上角+ ,选择New Run Script Phase 添加一项
    image.png
    1. 将下面的脚本代码粘贴至提示“Type a script or drag…”输入框内
    if [ "${ACTION}" = "build" ]
    then
    INSTALL_DIR=${SRCROOT}/Products/${PROJECT_NAME}.framework
    DEVICE_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework
    SIMULATOR_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphonesimulator/${PROJECT_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}/${PROJECT_NAME}" "${SIMULATOR_DIR}/${PROJECT_NAME}" -output "${INSTALL_DIR}/${PROJECT_NAME}"
    open "${DEVICE_DIR}"
    open "${SRCROOT}/Products"
    fi
    
    1. 测试一下脚本,在release环境下分别选择真机和模拟器运行,运行成功后会自动打开Finder并定位到工程根目录下的Products:
    image.png
    • 注意,工程中的Products:和这里的Products:是不一样的。
    image.png

    如何调试?

    • 有两种方法,一种是workspace中的两个project;另外一种是一个project中的两个target。这里采用第二种。

    • 在工程里新建一个target,作为使用者,也就是主工程。并且在Build Phase部分建立相互依赖。也就是主工程target依赖framework target

    image.png
    • 主工程target使用framework target中文件就像使用自己的文件一样方便。

    • 如果要调试,在framework target中相应的地方打断点就可以了,和在一个target调试感觉差不多。

    如何使用?

    • 由于使用是给第三方的,所以和在一个project下两个target的调试情况不一样,不能建立依赖。

    • 新建一个Demo工程,将合并过的framework加入进来。Files -> Add Files To “”...。没有独立的资源bundle,资源文件也在framework中。因为这是“动态的”。

    image.png
    • General -》Embedded Binaries添加framework,否则会找不到这个framework
    image.png
    • 注意,外部主工程只能调用framework导出的头文件中的方法。这与调试的时候差异很大。

    其他的一些配置

    • 最低支持版本需要设置一下,默认是最新的版本。Build Setting -> iOS Development Target
    image.png

    参考文章

    相关文章

      网友评论

        本文标题:iOS Framework开发实践-2019

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