iOS编程基础-OC(九)-专家级技巧:使用运行时系统API

该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!


     第九章 专家级技巧:使用运行时系统API

     

     第7章和第8章介绍了OC的动态特性和用于实现这些特性的运行时系统结构;

     本章将通过几个示例程序使你获得使用运行时系统功能及其API的实践经验;

        你将使用NSInvocation API创建一个动态代理;

        使用NSBundle API动态加载你编写的框架包;

        还将编写一个广泛使用运行时系统库API的程序;

     本章之后,您将能够掌握错综复杂的OC运行时系统;

     

     是不是很期待;

     

     9.1 使用可选包扩展程序

     

     OC提供了非常多的动态特性,他们提供了这门语言的能力和灵活性;使你能够在执行程序的过程中修改程序;

        第七章介绍了动态加载,还简要介绍了使用Foundation框架中的NSBundle类管理包的方式;

        接下来我们将使用这些特性编写一个程序,使用可选包扩展正在运行的程序;

     

     9.1.1 方法

     

     这个例子实际需要编写两个工程;

        需要在一个工程中编写可以使用可选包的程序;

        在另一个工程中创建可选包;

     

     具体步骤如下:

     1)在工程1中,编写一个协议和遵循该协议的类;

     2)在工程2中,创建一个包含另一个遵守该协议的类的可选包;

     3)在工程1中,使用NSBundle类找到并加载这个包(在步骤2中创建的),使用该包中的类创建一个对象,然后调用这个对象中的方法;

     

     不是很难理解,我们继续看;

     

     9.1.2 步骤1:编写基础代码

     

     工程1我们就是用当前的这个项目;

     接下来创建一个协议和遵守这个协议的类;

        我们已经知道(第二章)使用协议可以声明通用的方法和属性;

        将协议与动态类型一起使用可以获得一种理想的机制;

            通过包以动态方式加载的类设置实现的方法;

        接下来新建这个协议;

            选择协议模板,这样就在导航栏窗格中添加了一个名为C9Greeter.h的头文件;

            点击可以看到这个C9Greeter协议;之后添加一个方法;

        再建一个遵循这个协议的类C9BasicGreeter;

     (Code9_1:C9Greeter.h C9BasicGreeter.h C9BasicGreeter.m)

#import <Foundation/Foundation.h>

@protocol C9Greeter <NSObject>

-(NSString *)greeting:(NSString *)salutation;

@end

#import <Foundation/Foundation.h>
#import "C9Greeter.h"

@interface C9BasicGreeter : NSObject

@end

#import "C9BasicGreeter.h"

@implementation C9BasicGreeter

-(NSString *)greeting:(NSString *)salutation{
    return [NSString stringWithFormat:@"%@,Wrold",salutation];
}

@end


     现在我们来进行测试这段代码;

        导入C9BasicGreeter类的头文件;

        实现如下代码;

  

    id<C9Greeter> greeter1 = [[C9BasicGreeter alloc] init];
    NSLog(@"%@",[greeter1 greeting:@"Hello"]);

      log:

      2017-12-15 15:06:30.574490+0800 精通Objective-C[38928:3729761] Hello,Wrold

      

      我们慢下脚步,来看看这段代码:

        首先,在遵守协议的时候,我们并没有通过扩展的方式,声明当前类已经遵循了这个协议;

            @interface C9BasicGreeter()<C9Greeter>

            @end

        而是手动将这个协议的方法在C9BasicGreeter类中进行了实现;

        这个里创建的BasicGreeter对象赋给了一个名为greeter的变量;

        这个变量被声明为id类型并且遵守Greeter协议;

        然后,调用了对象中的方法greeting:,最后显示了结果;

      

      使用id<C9Greeter> greeter1声明的变量,就可以将任何遵守C9Greeter协议的OC对象赋值给他;

      

      接下来要创建另一个遵守C9Greeter协议的类,只不过这次是在可选包中创建;

      

     9.1.3 步骤2:创建一个可选包

      

      新建一个工程;参考下图:


      (Pic9_1)

      

      命名为C9CustomGreeter;这样我们就创建了一个空包;

      

      我们来看下这个工程的导航栏窗格,如下图:


      (Pic9_2)

      1)products分组下边有一个.bundle文件,这个就是要添加到你编写的代码和资源中的包;

      2)info.plist也是一个必不可少的文件,含有这个包的配置信息;

      

      接下来向该包中添加一个实现C9Greeter协议的类;

        要做到这一点,首先需要将协议对应的头文件包含到这儿工程中;

        我们按住Control点选导航栏窗格,xcode会打开一个下拉菜单,我们可以添加文件到当前工程;(如下图)


      (Pic9_3)


      (Pic9_4)

        继续我们的操作,在可选包工程中添加一个遵守C9Greeter协议的新类:C9CustomGreeter;

      (Code9_2:C9CustomGreeter.h C9CustomGreeter.m)

      

#import <Foundation/Foundation.h>
#import "C9Greeter.h"

@interface C9CustomGreeter : NSObject

@end

#import "C9CustomGreeter.h"

@implementation C9CustomGreeter

-(NSString *)greeting:(NSString *)salutation{
    return [NSString stringWithFormat:@"%@,Universe",salutation];
}

@end

      通过Product菜单中选择Build选项,编译了这个包;

        这样我们就创建了一个包;

        选择这个包文件,可以在右侧文件检查器显示出完整的文件路径;

        当你使用NSBundle类加载这个包的时候,需要用到这个路径;



     9.1.4 步骤3:动态加载包

      

      接下来我们需要通过该包以动态的方式创建一个对象(通过该包中的类),并调用这个对象的方法;

      

      在实际测试代码运行是前,我们还需要做一些准备:

        最主要的就是如何获取bundle包的地址;

        我们将通过main函数的参数argv[],来获取对应传入的参数(这个可以参考我的C语言blog中关于这部分的讲解),稍后会有介绍;

        复制包路径,按照下图在工程1中进行配置:


        (Pic9_6)

        同时在工程目录下新建一个plist文件,用来记录和保存这个路径参数;(其他的保存路径方式均可)

        (Code_main.m)

      

#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "C9Greeter.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        NSString * bundleGreeterPath;
        if (argc != 2) {
            NSLog(@"没有获得包路径!");
        }else{
            bundleGreeterPath = [NSString stringWithUTF8String:argv[1]];
            if (bundleGreeterPath == nil) {
                NSLog(@"Bundle not found at this path!");
            }else{

                NSString * filePath = [[NSBundle mainBundle]pathForResource:@"PathList" ofType:@"plist"];
                NSDictionary * dic = [NSDictionary dictionaryWithContentsOfFile:filePath];
                NSMutableDictionary * pathData;
                if (!dic) {
                    pathData = [NSMutableDictionary dictionary];
                }else{
                    pathData = [NSMutableDictionary dictionaryWithDictionary:dic];
                }
                [pathData setValue:bundleGreeterPath forKey:@"bundleGreeterPath"];
                BOOL success = [pathData writeToFile:filePath atomically:YES];
                NSLog(@"%d",success);
            }
        }
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

      接下来,回到当前工程(工程1),执行如下代码;

 

id greeter = [[C9BasicGreeter alloc] init];
    NSLog(@"%@",[greeter greeting:@"Hello-1"]);
    
    id greeterB;
    
    NSString * filePath = [[NSBundle mainBundle]pathForResource:@"PathList" ofType:@"plist"];
    NSDictionary * dic = [NSDictionary dictionaryWithContentsOfFile:filePath];
    NSLog(@"%@",dic);
    NSString * bundlePath =dic[@"bundleGreeterPath"];
    
    NSBundle * greeterBundle = [NSBundle bundleWithPath:bundlePath];
    
    if (greeterBundle == nil) {
        NSLog(@"Bundle not found at this path!");
    }else{
        NSError * error;
        BOOL isLoaded = [greeterBundle loadAndReturnError:&error];
        if (!isLoaded) {
            NSLog(@"Error = %@",[error localizedDescription]);
        }else{
            //加载包后使用该包创建一个对象并向这个对象发送一条消息
            Class greeterClass = [greeterBundle classNamed:@"C9CustomGreeter"];
            greeterB = [[greeterClass alloc] init];
            NSLog(@"%@",[greeterB greeting:@"Hello-2"]);
            
            //使用完以动态方式加载的包,现在卸载它
            //先释放所有用包中类创建的对象!
            greeterB = nil;
            BOOL isUnloaded = [greeterBundle unload];
            if (!isUnloaded) {
                NSLog(@"Counld not unload bundle!");
            }
        }
    }

    这段代码虽然看起来很长,我们来分段理解下:

       1)获取包的路径参数;

       2)创建一个NSBundle对象;

       3)加载包;

       4)获得包中的类;

       5)卸载包;

       6)设置包路径参数;

       

    再逐条研究下这些代码的逻辑:

       1.获取包的路径参数

       OC中main()函数会设置两个参数:argc和argv;

       使用这些参数能够在运行程序的过程中向程序传递参数;

       int main(int argc, char * argv[]);

        参数argc设置了程序参数的数量;

        argc数组存储了以空格分隔的参数值;

            argv[0]存储的是程序的名字,对应的argc的值大于等于1;(如果实在命令行中执行程序 程序名以及其之后空格间隔的内容就是main函数接受的参数,这一部分在我的C语言blog中有详解);

       我们刚刚在scheme中添加argument就是工程2中生成的包的路径;

       这样这一点对应的代码就好理解了;


       2.创建一个NSBundle对象

       NSBundle * greeterBundle = [NSBundle bundleWithPath:bundlePath];

       if (greeterBundle == nil) {

       }

       根据路径创建一个NSBundle对象,并验证该对象是否成功创建;

       

       3.加载包

       -(BOOL)loadAndReturnError:(NSError **)error;

       NSBundle提供了多个向OC运行时系统中加载包的方法,上边的就是其中之一,出错的话也会及时返回,成功则返回BOOL值YES;

       

       4.获得包中的类

       从包中获取类,然后创建一个对象使用;

       这里也可以使用[greeterBundle principalClass]的方式获取类greeterClass;

            principalClass被用于获取包的首要类;

            首要类用于控制包中含有的其他类;

            包的Info.plist文件舍之类首要类的名字(没有设置的话,那么从包中加载的第一个类就是首要类);


       (Pic9_7)

            这个生成的实例被赋值给了已经声明的变量greeterB,这个变量在声明时最好直接声明为id<C9Greeter> greeterB;


       5.卸载包

       不再需要的包可以卸载,以节省系统资源;

       

       6.设置包路径参数

       其实要做的就是查明bundle包的路径,在程序运行时将这个路径设置为它的输入参数;

       如何查找该路径以及如何设置,前面我们都已截图说明过,这里不再赘述;

       

       这里有必要知道,scheme方案定义了Xcode功能和工作区的创建和测试设置;

       创建配置信息中含有执行目标程序时传递的参数;因此在工程1中这里设置的是包的完整路径;

       

       说了这么多一直还没给大家看log呢;我们来运行下看看:

       2017-12-18 14:16:24.005835+0800 精通Objective-C[50675:5339959] Error = The bundle “C9CustomGreeter” couldn’t be loaded because it is damaged or missing necessary resources.

       报错了!

       这个问题我大致查了一下,应该是涉及到动态加载更新代码的方式,在客户端是受限的,或者有其他方式;

            比如我们将一个模块独立成一个包,然后从服务器下载,再到客户端去使用,会达到动态更新的目的;

            这个问题,暂时还没有更加深入的理解,也在网上同人交流,mark在这,日后清楚之后,再分出一节单独讨论吧;(本段标红)

       

       为了证明这种方式的可行性,我们新建了一个项目C9DynaLoader,将其作为工程1的替代,将协议和实现协议的类都在这个工程中使用;

       在其main.m中执行上述逻辑的测试代码如下:

       (Code_C9DynaLoader_main.m)

       

#import <Foundation/Foundation.h>
#import "C9BasicGreeter.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        id greeter = [[C9BasicGreeter alloc] init];
        NSLog(@"%@",[greeter greeting:@"Hello-1"]);
        NSString * bundleGreeterPath;
        if (argc != 2) {
            NSLog(@"没有获得包路径!");
        }else{
            bundleGreeterPath = [NSString stringWithUTF8String:argv[1]];
            if (bundleGreeterPath == nil) {
                NSLog(@"Bundle not found at this path!");
            }else{
                id<C9Greeter> greeterB;
                NSBundle * greeterBundle = [NSBundle bundleWithPath:bundleGreeterPath];
                if (greeterBundle == nil) {
                    NSLog(@"Bundle not found at this path!");
                }else{
                    NSError * error;
                    BOOL isLoaded = [greeterBundle loadAndReturnError:&error];
                    if (!isLoaded) {
                        NSLog(@"Error = %@",[error localizedDescription]);
                    }else{
                        //加载包后使用该包创建一个对象并向这个对象发送一条消息
                        Class greeterClass = [greeterBundle classNamed:@"C9CustomGreeter"];
                        greeterB = [[greeterClass alloc] init];
                        NSLog(@"%@",[greeterB greeting:@"Hello-2"]);
                        
                        //使用完以动态方式加载的包,现在卸载它
                        //先释放所有用包中类创建的对象!
                        greeterB = nil;
                        BOOL isUnloaded = [greeterBundle unload];
                        if (!isUnloaded) {
                            NSLog(@"Counld not unload bundle!");
                        }
                    }
                }
            }
        }
        return 0;
    }
}

       目录结构截图署截图说明如下:


       (Pic9_8)


(Pic9_9)


       和原来的工程1一样,还是要设置包路径参数;

       运行C9DynaLoader,log如下:

       2017-12-18 15:11:32.521433+0800 C9DynaLoader[51745:5414801] Hello, World!

       2017-12-18 15:11:32.521733+0800 C9DynaLoader[51745:5414801] Hello-1,Wrold

       2017-12-18 15:11:32.530178+0800 C9DynaLoader[51745:5414801] Hello-2,Universe

       

       至此,我们看到:

        greeterB对象是通过以动态方式加载的C9CustomGreeter包创建的;这就是通过使用NSBundle类以动态的方式加载包,向正在运行的程序添加代码和资源的方式;

       




评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值