iOS动态库加载探究

猜想: 能否通过服务器下发动态库实现App动态更新?

要上App Store的话,答案是不能的~

新建一个动态库
  • 新建一个framwork,修改mach-O为动态库类型(Xcode13.2.1默认就是)
  • 把需要公开的Headers头文件暴露出来即可

例子:

#import "DynamicClass.h"

@implementation DynamicClass

+ (void)hello {
    NSLog(@"我是动态库的方法: %s",__FUNCTION__);
}

- (void)hello{
    NSLog(@"我是动态库的方法: %s",__FUNCTION__);
}

@end
下载动态库

依赖zip解压库 SSZipArchive

- (NSString *)fileNameWithURL:(NSURL *)url {
    NSString *path = url.absoluteString;
    NSRange range = [path rangeOfString:@"/" options:(NSBackwardsSearch)];
    if( range.location != NSNotFound ){
        return [path substringFromIndex:range.location+1];
    }
    return @"";
}

- (void)downloadDynamicFramework{
    //把编译好带签名的动态库下载到Documents目录下并解压
    NSURL *url=[NSURL URLWithString:@"http://192.168.1.122:8080/DynamicLib.framework.zip"];
    NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];
    [[session downloadTaskWithRequest:req completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"%@",location.absoluteString);
        NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
        NSString *targetZipPath = [doc stringByAppendingPathComponent:@"DynamicLib.framework.zip"];
        NSString *targetPath = [doc stringByAppendingPathComponent:@"DynamicLib.framework"];
        BOOL result = [[NSFileManager defaultManager] copyItemAtURL:location toURL:[NSURL fileURLWithPath:targetZipPath] error:nil];
        NSLog(@"result: %@",result? @"成功!":@"失败!");
        BOOL unzip = [SSZipArchive unzipFileAtPath:targetZipPath toDestination:targetPath];
        if (unzip) {
            NSLog(@"解压成功!!");
            [[NSFileManager defaultManager] removeItemAtURL:[NSURL fileURLWithPath:targetZipPath] error:nil];
        }else{
            NSLog(@"解压失败!!");
        }
    }] resume];
}
加载动态库
// 加载沙盒里的动态库代码
- (void)loadFrameworkNamed:(NSString *)bundleName {
        NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documentDirectory = nil;
        if ([paths count] != 0) {
            documentDirectory = [paths objectAtIndex:0];
        }
        
        NSFileManager *manager = [NSFileManager defaultManager];
        NSString *bundlePath = [documentDirectory stringByAppendingPathComponent:[bundleName stringByAppendingString:@".framework"]];
        
        // Check if new bundle exists
        if (![manager fileExistsAtPath:bundlePath]) {
            NSLog(@"No framework update");
            bundlePath = [[NSBundle mainBundle]
                          pathForResource:bundleName ofType:@"framework"];
            
            // Check if default bundle exists
            if (![manager fileExistsAtPath:bundlePath]) {
                UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Oooops" message:@"Framework not found" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
                [alertView show];
                return ;
            }
        }
        
        // Load bundle
        NSError *error = nil;
        NSBundle *frameworkBundle = [NSBundle bundleWithPath:bundlePath];
        if (frameworkBundle && [frameworkBundle loadAndReturnError:&error]) {
            NSLog(@"Load framework successfully");
        }else {
            NSLog(@"Failed to load framework with err: %@",error);
            return ;
        }
        
        // Load class
        Class cls = NSClassFromString(@"DynamicClass");
        if (!cls) {
            NSLog(@"Unable to load class");
            return ;
        }
        [cls performSelector:@selector(hello)];
        NSObject *obj = [cls new];
        [obj performSelector:@selector(hello)];
    }

经过实际验证:
 下载framework 动态下发~  事实证明并不能加载成功 目前只支持内嵌(Embedded)的动态库  动态下发到沙盒加载还是不行🚫的
 报错信息如下: 
/**
 Failed to load framework with err: Error Domain=NSCocoaErrorDomain Code=4 "The bundle “DynamicLib.framework” couldn’t be loaded because its executable couldn’t be located." UserInfo={NSLocalizedFailureReason=The bundle’s executable couldn’t be located., NSLocalizedRecoverySuggestion=Try reinstalling the bundle., NSBundlePath=/var/mobile/Containers/Data/Application/8E73B713-16B1-476A-96CB-C7DD0A1930A4/Documents/DynamicLib.framework, NSLocalizedDescription=The bundle “DynamicLib.framework” couldn’t be loaded because its executable couldn’t be located.}
 */
目前已尝试以下的两种方式:
  1. 把动态库添加进主工程并设置 Embed & sign 可以正常使用,但是应用一启动framework就会自动被loaded
  2. 把动态库当做bundle资源,在Build phases -> Copy Bundle Resources里添加.framework文件, 通过代码拷贝到沙盒,这种方式也可以从沙盒加载,实际上也是从一开始就打包打进去了,就类似于那种不需要启动app时一股脑儿加载一堆的库,而是使用到了才去加载,这种就类似于懒加载原理吧,从某种意义上(依赖的动态库数量级比较大的时候)来说可以优化启动时间
 NSLog(@"%@",[NSBundle allFrameworks]); 
//通过打印已加载的frameworks 发现并没有DynamicLib.framework 

//Resources资源文件默认是放在mainBunle 把它拷贝到Documents目录下 (当然也可以直接加载mainBundle的路径 只是这里加载方法写的是Documents目录的)
 NSString *filePath = [[NSBundle mainBundle] pathForResource:@"DynamicLib" ofType:@"framework"];
    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    NSString *targetPath = [doc stringByAppendingPathComponent:@"DynamicLib.framework"];
    BOOL result = [[NSFileManager defaultManager] copyItemAtPath:filePath toPath:targetPath error:nil];
    NSLog(@"result: %@",result? @"copy成功!":@"copy失败!");


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self loadFrameworkNamed:@"DynamicLib"]; //动态加载DynamicLib.framework 
    NSLog(@"%@",[NSBundle allFrameworks]);// 现在发现DynamicLib.framework也加载进来了
}

//2022-05-09 15:51:25.955926+0800  Demo[66285:11922021] 我是动态库的方法: +[DynamicClass hello]
//2022-05-09 15:51:25.956082+0800  Demo[66285:11922021] 我是动态库的方法: -[DynamicClass hello]

//    "NSBundle </var/mobile/Containers/Data/Application/D645554B-097B-4AF7-944B-3445ABE84482/Documents/DynamicLib.framework> (loaded)",

	//mainBundle直接加载如下: 
    NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"DynamicLib" ofType:@"framework"];
    NSError *error = nil;
    NSBundle *frameworkBundle = [NSBundle bundleWithPath:bundlePath];
    if (frameworkBundle && [frameworkBundle loadAndReturnError:&error]) {
        NSLog(@"Load framework successfully");
    }else {
        NSLog(@"Failed to load framework with err: %@",error);
        return ;
    }
    Class objCls = NSClassFromString(@"DynamicClass");
    SEL  sel = NSSelectorFromString(@"hello");
		//类方法调用一 
    void *(*msgSend)(id, SEL) = (void *)objc_msgSend;
    (void)(msgSend(objCls, sel));
		//类方法调用二 
    [objCls performSelector:@selector(hello)];
		//实例方法调用
    NSObject *obj = [objCls new];
    [obj performSelector:@selector(hello)];

 // 使用dlopen加载如下, 需要导入头文件#include <dlfcn.h>
	    NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"DynamicLib.framework/DynamicLib" ofType:nil];
		//参数1: 可执行文件的路径  
		//参数2: 模式,立即加载/懒加载等几个宏定义常量
    void* res= dlopen([bundlePath cStringUsingEncoding:NSUTF8StringEncoding], RTLD_NOW);
    if (res) {
        Class objCls = NSClassFromString(@"DynamicClass");
        SEL  sel = NSSelectorFromString(@"hello");
        void *(*msgSend)(id, SEL) = (void *)objc_msgSend;
        (void)(msgSend(objCls, sel));
        [objCls performSelector:@selector(hello)];
        NSObject *obj = [objCls new];
        [obj performSelector:@selector(hello)];
    }

当然针对接口调用还是采用Protocol形式暴露出相关的接口进行调用,尽可能避免字符串调用hardcode的形式来编写代码

posted @ 2022-05-09 19:11  CoderWGB  阅读(1048)  评论(0编辑  收藏  举报