• 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的形式来编写代码

  • 相关阅读:
    360删除、修改注册表问题
    朗姆达表达式类似IN查询条件
    SQL 分组排序、CASE...WHEN...、是否为空 查询
    Excel 复制Sql查询结果错位
    redis实现购物车秒杀原理
    sphinx的使用
    linux安装
    linux和windows的区别
    Yii2.0实现语言包切换功能
    Linux的7个级别
  • 原文地址:https://www.cnblogs.com/wgb1234/p/16250488.html
Copyright © 2020-2023  润新知