iOS 插件化开发

作者: 飘金 | 来源:发表于2017-08-10 15:09 被阅读667次

framework是Cocoa/Cocoa Touch程序中使用的一种资源打包方式,可以将将代码文件、头文件、资源文件、说明文档等集中在一起,方便开发者使用,作为一名Cocoa/Cocoa Touch程序员每天都要跟各种各样的Framework打交道。Cocoa/Cocoa Touch开发框架本身提供了大量的Framework,比如Foundation.framework/UIKit.framework/AppKit.framework等。需要注意的是,这些framework无一例外都是动态库。

但残忍的是,Cocoa Touch上并不允许我们使用自己创建的framework。不过由于framework是一种优秀的资源打包方式,拥有无穷智慧的程序员们便想出了以framework的形式打包静态库的招数,因此我们平时看到的第三方发布的framework无一例外都是静态库,真正的动态库是上不了AppStore的。

WWDC2014给我的一个很大感触是苹果对iOS的开放态度:允许使用动态库、允许第三方键盘、App Extension等等,这些在之前是想都不敢想的事。

iOS上动态库可以做什么

和静态库在编译时和app代码链接并打进同一个二进制包中不同,动态库可以在运行时手动加载,这样就可以做很多事情,比如:

  • 应用插件化

目前很多应用功能越做越多,软件显得越来越臃肿。因此插件化就成了很多软件发展的必经之路,比如支付宝这种平台级别的软件:

image.png image.png

这边是PiaoJinDylib

创建你测试类PiaoJin

头文件部分

#import <Foundation/Foundation.h>

@interface PiaoJin : NSObject

- (void)love;

@end

实现部分

#import "PiaoJin.h"
#import <UIKit/UIKit.h>

@implementation PiaoJin

- (void)love{
    NSLog(@"love you more than I can say!");
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"love you more than I can say by 飘金!" message:nil delegate:nil cancelButtonTitle:nil otherButtonTitles:@"确定", nil];
    [alertView show];
}

@end

在PiaoJinDylib中引入

#import <UIKit/UIKit.h>

//! Project version number for PiaoJinDylib.
FOUNDATION_EXPORT double PiaoJinDylibVersionNumber;

//! Project version string for PiaoJinDylib.
FOUNDATION_EXPORT const unsigned char PiaoJinDylibVersionString[];

// In this header, you should import all the public headers of your framework using statements like #import <PiaoJinDylib/PublicHeader.h>
#import "PiaoJin.h"

设置开放的头文件

一个库里面可以后很多的代码,但是我们需要设置能够提供给外界使用的接口,可以通过Target—>Build Phases—>Headers来设置,如下图所示:

image.png

我们只需将希望开放的头文件放到Public列表中即可,比如我开放了PiaoJinDylib.h和PiaoJin.h两个头文件,在生成的framework的Header目录下就可以看到这两个头文件.一切完成,Run以后就能成功生成framework文件了。

前面只是我们只是创建了一个动态库文件,但是和静态库类似,该动态库并同时不支持真机和模拟器,可以通过以下步骤创建通用动态库:

创建Aggregate Target(PiaoJinDylib工程下)

image.png image.png

我给Aggregate的Target的命名是CommonDylib。

设置Target Dependencies

按以下路径设置CommonDylib对应的Target Dependencies:

TARGETS-->CommonDylib-->Build Phases-->Target Dependencies 

将真正的动态库PiaoJinDylib Target添加到其中。

添加Run Script

按以下路径为CommonDylib添加Run Script:

TARGETS-->CommonDylib-->Build Phases-->Run Script 

添加的脚本为:

# Sets the target folders and the final framework product. 
FMK_NAME=${PROJECT_NAME} 
 
# Install dir will be the final output to the framework. 
# The following line create it in the root folder of the current project. 
INSTALL_DIR=${SRCROOT}/Products/${FMK_NAME}.framework 
 
# Working dir will be deleted after the framework 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}" ] 
then 
rm -rf "${INSTALL_DIR}" 
fi 
 
mkdir -p "${INSTALL_DIR}" 
 
cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/" 
 
# Uses the Lipo Tool to merge both binary files (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}" 

添加以后的效果:

image.png

脚本的主要功能是:

1.分别编译生成真机和模拟器使用的framework;

2.使用lipo命令将其合并成一个通用framework;

3.最后将生成的通用framework放置在工程根目录下新建的Products目录下。

如果一切顺利,对CommonDylib target执行run操作以后就能生成一个如图所示的通用framework文件了:

image.png image.png

使用动态库

实际过程中动态库是需要从服务器下载并且保存到app的沙盒中的,这边直接模拟已经下载好了动态库并且保存到沙盒中:

image.png

使用动态库

使用NSBundle加载动态库

- (IBAction)loadFrameWorkByBundle:(id)sender {
    //从服务器去下载并且存入Documents下(只要知道存哪里即可),事先要知道framework名字,然后去加载
    NSString *frameworkPath = [NSString stringWithFormat:@"%@/Documents/PiaoJinDylib.framework",NSHomeDirectory()];
    
    NSError *err = nil;
    NSBundle *bundle = [NSBundle bundleWithPath:frameworkPath];
    NSString *str = @"加载动态库失败!";
    if ([bundle loadAndReturnError:&err]) {
        NSLog(@"bundle load framework success.");
        str = @"加载动态库成功!";
    } else {
        NSLog(@"bundle load framework err:%@",err);
    }
    
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:str message:nil delegate:nil cancelButtonTitle:nil otherButtonTitles:@"确定", nil];
    [alertView show];
}

使用dlopen加载动态库

以PiaoJinDylib.framework为例,动态库中真正的可执行代码为PiaoJinDylib.framework/PiaoJinDylib文件,因此使用dlopen时指定加载动态库的路径为PiaoJinDylib.framework/PiaoJinDylib。

 NSString *documentsPath = [NSString stringWithFormat:@"%@/Documents/PiaoJinDylib.framework/PiaoJinDylib",NSHomeDirectory()]; 
[self dlopenLoadDylibWithPath:documentsPath];
    if (dlopen([path cStringUsingEncoding:NSUTF8StringEncoding], RTLD_NOW) == NULL) { 
        char *error = dlerror(); 
        NSLog(@"dlopen error: %s", error); 
    } else { 
        NSLog(@"dlopen load framework success."); 
 } 

调用动态库中的方法

//调用FrameWork的方法,利用runTime运行时
- (IBAction)callMethodOfFrameWork:(id)sender {
    Class piaoJinClass = NSClassFromString(@"PiaoJin");
    if(piaoJinClass){
        //事先要知道有什么方法在这个FrameWork中
        id object = [[piaoJinClass alloc] init];
        //由于没有引入相关头文件故通过performSelector调用
        [object performSelector:@selector(love)];
    }else {
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"调用方法失败!" message:nil delegate:nil cancelButtonTitle:nil otherButtonTitles:@"确定", nil];
        [alertView show];

    }
}

监测动态库的加载和移除

我们可以通过下述方式,为动态库的加载和移除添加监听回调:

+ (void)load 
{ 
  _dyld_register_func_for_add_image(&image_added); 
  _dyld_register_func_for_remove_image(&image_removed); 
} 

github上有一个完整的示例代码

后记

这边只是一件最简单的例子,实际项目中肯定需要与动态库所代表的模块进行交互,数据的共享等等,这些都是需要去根据实际项目场景再去设计解决的!

如果是真机调试可以通过运行需打开iTunes导入到PiaoJinFrameWorkDemo应用中。

该Demo已经上传GitHub,有需要的码友可以去看看

参考文档:

WWDC2014之iOS使用动态库

相关文章

  • iOS开发必备的Xcode插件(2)

    iOS开发必备的Xcode插件(2) iOS开发必备的Xcode插件(2)

  • iOS越狱开发:hook(拦截)一个自己写的方法

    开发越狱插件需要先配置开发环境 Theos: iOS越狱插件开发工具本文主要搬运至:iOS逆向工程(手动HOOK自...

  • Android热修复总结

    插件化和热修复是Android开发较为高级的知识点,是中级开发人员通向高级开发中必备知识点,插件化知识:插件化。下...

  • Android热修复总结

    插件化和热修复是Android开发较为高级的知识点,是中级开发人员通向高级开发中必备知识点,插件化知识:插件化。下...

  • [ios开发Cordova插件] - 支持入参及调回的插件开发

    引言: 本文仅针对ios开发者,初次涉及cordova插件开发插件开发前首先确认你已配置好cordova的开发环境...

  • 插件化与组件化开发

    1.插件化 [Android] 开发资料收集:动态加载、插件化、热修复技术 2.【转】Android插件化从入门到...

  • Android组件化和插件化开发

    Android组件化和插件化开发 什么是组件化和插件化? 组件化开发 就是将一个app分成多个模块,每个模块都是一...

  • Xcode常用插件

    Xcode插件神器 作为iOS开发人员,不了解些常用的插件,不使用插件,开发效率怎么会够快呢?那么问题来了,现在的...

  • BiBi - Android 插件化

    From:Android插件化开发指南 目录 预备知识1.1 简介 插件化的用途 插件化的发展史1.2 Binde...

  • Android组件化和插件化开发

    Android组件化和插件化开发 什么是组件化和插件化? 组件化开发就是将一个app分成多个模块,每个模块都是一个...

网友评论

  • Sunshine_an:应该运行git里的哪个demo。PiaoJinDylib还是PiaoJinFrameWorkDemo?PiaoJinDylib跑不起来,PiaoJinFrameWorkDemo运行起来后显示动态库加载失败。
    飘金:@Sunshine_an 跑PiaoJinFrameWorkDemo ,是模拟器还是真机?
  • Zona政:真机跑报错 file system sandbox blocked mmap() 怎么解决
    飘金:(file system sandbox blocked mmap() ), iOS10以下还可以,以上就不行了,没得搞了:joy:
    飘金:@Zona政 抱歉没遇到这个问题
  • 苦笑男神:网络下发模块,App动态变化页面和模块,这样有什么好的思路吗?
    飘金:@Miaoz0070 这个没试过,可以去我的gitHub上把代码下载来跑一边试试。
    Miaoz0070:@飘金 请问下,现在9系统之后还能动态下载加载framework吗?
    飘金:1.Wax(https://github.com/piaojin/iOS-WaxPatch)
    2.JSPatch
    3.本文所述.把模块做成framework放在服务器上,每次启动app去判断是或要加载新模块,需要就下载对应framework,通过本文所述去调用。
    以上仅供参考,能不能上架AppStore都是个问题。

本文标题:iOS 插件化开发

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