面试经典——有意思的Runtime(USE)

分类(Category):

    在不子类化的情况下,为已经存在的类增加功能;

    分类中的方法会成为类的组成部分,并且会被子类继承;

 

扩展:

    是一种匿名分类,可以声明实例变量、属性和方法,我们常见的.m文件中@interface的一段就是一个扩展;

    通常用在类实现的.m文件中,声明私有的实例变量、属性和方法;

 

扩展和分类的区别在于 他可以声明实例变量和属性;

 

在runtime中Category定义的是如下的结构体:

 

Category

typedef struct objc_category * Categoy;

 

struct objc_category{

    char * category_name;

    char * class_name;

    struct objc_method_list * instance_methods;

    struct objc_method_list * class_methods;

    struct objc_protocol_list * protocols;

}

    我们注意到Category是一个执行该结构体的指针,和我们之前见到的Object、Class类似;

    该结构体中包含了 对象方法列表,类方法列表,协议列表,相应的Category支持添加对象方法、类方法和协议,但不能保存成员变量;

 

如果想在Category中添加可用的属性可以使用 关联对象:

    在分类中加入属性,虽然运行没有问题,但是不会生成对应的成员变量,getter和setter也没有;调用的时候也会报错;

    使用关联对象在分类的实现文件.m中,使用objc_setAssociatedObject和objc_getAssociatedObject即可实现;

    而且该属性还可以被子类调用;关联对象会在对象释放时,由Runtime查找并释放,不需要手动释放;

 


 

我们使用了runtime通过关联对象在分类中增加了属性,接下来看看runtime一些其他的操作;

 

Method Swizzling方法交换:

    这个可以在程序运行时,修改一个方法的实现;下面示例我们要修改doSomethingSwizzled方法的实现;

1)动态添加方法-替换:

 

2)方法存在-直接交换:

 

为了保证方法只会被交换一次,可以把这个放在只会调用一次的方法中,然后再放到dispatch_once里;

(RSSwizzle可以使这个过程更简单)

 


 

打印类的属性、方法、成员变量和遵循的协议: 

【Code-MyObject】(代码包含了方法替换的部分,关注最下边的实现就好)

#import <Foundation/Foundation.h>

@protocol MyObjectDelegate <NSObject>

-(void)MyObjectDelegateMethod;

@end

@interface MyObject : NSObject

@property (nonatomic , copy) NSString * pro1;
@property (nonatomic , copy) NSString * pro2;
@property (nonatomic , copy) NSString * pro3;

-(void)doSomethingOriginal;
-(void)doSomethingSwizzled;
-(void)method_swizzling;

-(void)printPropertyMethodIvarList;

@end
#import "MyObject.h"
#import "objc/runtime.h"

@interface MyObject()<MyObjectDelegate>{
    int _ivar1;
    int _ivar2;
    int _ivar3;
}

@end

@implementation MyObject

-(void)MyObjectDelegateMethod{
    
}

-(void)doGreeting{
    NSLog(@"%@",@"Hello");
}
-(void)doSomethingOriginal{
    NSLog(@"%@",@"doSomethingOriginal");
}
-(void)doSomethingSwizzled{
    NSLog(@"%@",@"doSomethingSwizzled");
}
-(void)method_swizzling{
    Method greeting = class_getInstanceMethod([self class], @selector(doGreeting));
    
    Method original = class_getInstanceMethod([self class], @selector(doSomethingOriginal));
    Method swizzled = class_getInstanceMethod([self class], @selector(doSomethingSwizzled));
    
    if (!class_addMethod([self class], @selector(doSomethingOriginal), method_getImplementation(greeting), method_getTypeEncoding(greeting))) {
        NSLog(@"由于方法已存在 添加失败 直接交换(这样做是为了避免父类方法与子类方法交换带来的问题)");
        method_exchangeImplementations(original, swizzled);
    }else{
        NSLog(@"方法不存在 添加方法成功 此添加方法的实现来自greeting 使用添加的方法 替换doSomethingSwizzled方法的实现");
        original = class_getInstanceMethod([self class], @selector(doSomethingOriginal));
        class_replaceMethod([self class], @selector(doSomethingSwizzled), method_getImplementation(original), method_getTypeEncoding(original));
    }
}

-(void)printPropertyMethodIvarList{
    NSLog(@"_________________打印属性_____________________");

    {
        unsigned int count;
        objc_property_t * propertyList = class_copyPropertyList([self class], &count);
        for (unsigned int i = 0; i < count; i++) {
            const char * propertyName = property_getName(propertyList[i]);
            NSLog(@"%@",[NSString stringWithUTF8String:propertyName]);
        }
        free(propertyList);
    }
    NSLog(@"_________________打印方法_____________________");

    {
        unsigned int count;
        Method * methodList = class_copyMethodList([self class], &count);
        for (unsigned int i = 0; i < count; i++) {
            Method method = (methodList[i]);
            NSLog(@"%@",NSStringFromSelector(method_getName(method)));
        }
        free(methodList);
        
    }
    NSLog(@"_________________打印变量_____________________");

    {
        unsigned int count;
        Ivar * ivarList = class_copyIvarList([self class], &count);
        for (unsigned int i = 0; i < count; i++) {
            Ivar ivar = (ivarList[i]);
            const char * ivarName = ivar_getName(ivar);
            NSLog(@"%@",[NSString stringWithUTF8String:ivarName]);
        }
        free(ivarList);
        
    }
    NSLog(@"_________________打印协议_____________________");

    {
        unsigned int count;
        
        __unsafe_unretained Protocol ** protocolList = class_copyProtocolList([self class], &count);
        for (unsigned int i = 0; i < count; i++) {
            Protocol * protocol = (protocolList[i]);
            const char * protocolName = protocol_getName(protocol);
            NSLog(@"%@",[NSString stringWithUTF8String:protocolName]);
        }
        free(protocolList);
        
    }
}

@end

log:

2019-01-29 11:21:37.617924+0800 test[94980:4682027] _________________打印属性_____________________
2019-01-29 11:21:37.618056+0800 test[94980:4682027] testP
2019-01-29 11:21:37.618138+0800 test[94980:4682027] pro1
2019-01-29 11:21:37.618224+0800 test[94980:4682027] pro2
2019-01-29 11:21:37.618293+0800 test[94980:4682027] pro3
2019-01-29 11:21:37.618381+0800 test[94980:4682027] hash
2019-01-29 11:21:37.618475+0800 test[94980:4682027] superclass
2019-01-29 11:21:37.618544+0800 test[94980:4682027] description
2019-01-29 11:21:37.618613+0800 test[94980:4682027] debugDescription
2019-01-29 11:21:37.618693+0800 test[94980:4682027] _________________打印方法_____________________
2019-01-29 11:21:37.618774+0800 test[94980:4682027] printPropertyMethodIvarList
2019-01-29 11:21:37.618856+0800 test[94980:4682027] setTestP:
2019-01-29 11:21:37.618925+0800 test[94980:4682027] testP
2019-01-29 11:21:37.619000+0800 test[94980:4682027] doGreeting
2019-01-29 11:21:37.619076+0800 test[94980:4682027] doSomethingOriginal
2019-01-29 11:21:37.619191+0800 test[94980:4682027] doSomethingSwizzled
2019-01-29 11:21:37.619361+0800 test[94980:4682027] MyObjectDelegateMethod
2019-01-29 11:21:37.619463+0800 test[94980:4682027] method_swizzling
2019-01-29 11:21:37.619624+0800 test[94980:4682027] pro1
2019-01-29 11:21:37.619809+0800 test[94980:4682027] setPro1:
2019-01-29 11:21:37.621500+0800 test[94980:4682027] pro2
2019-01-29 11:21:37.621576+0800 test[94980:4682027] setPro2:
2019-01-29 11:21:37.621633+0800 test[94980:4682027] pro3
2019-01-29 11:21:37.621710+0800 test[94980:4682027] setPro3:
2019-01-29 11:21:37.621784+0800 test[94980:4682027] .cxx_destruct
2019-01-29 11:21:37.621862+0800 test[94980:4682027] _________________打印变量_____________________
2019-01-29 11:21:37.621966+0800 test[94980:4682027] _ivar1
2019-01-29 11:21:37.622067+0800 test[94980:4682027] _ivar2
2019-01-29 11:21:37.622127+0800 test[94980:4682027] _ivar3
2019-01-29 11:21:37.622201+0800 test[94980:4682027] _pro1
2019-01-29 11:21:37.622273+0800 test[94980:4682027] _pro2
2019-01-29 11:21:37.622338+0800 test[94980:4682027] _pro3
2019-01-29 11:21:37.622499+0800 test[94980:4682027] _________________打印协议_____________________
2019-01-29 11:21:37.622682+0800 test[94980:4682027] MyObjectDelegate

 

Runtime用途-AOP:

    可以提供对面向切面编程的支持(AOP);

    比如我们要统计所有的Btn点击事件,表示出当前点击按钮的currentTitle;

 

我们新建一个UIButton+aop的分类,来实现下,这样做技能对业务逻辑分离,也能降低耦合:

【Code-UIButton+aop】

#import "UIButton+aop.h"
#import "objc/runtime.h"

@implementation UIButton (aop)

+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [[self class] method_swizzling];
    });
}
//-(void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
//    [super sendAction:action to:target forEvent:event];
//}
-(void)sendForAopAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
    NSString * title = [self currentTitle];
    NSLog(@"%@",title);
    
    [super sendAction:action to:target forEvent:event];
}

+(void)method_swizzling{
    Method tmp = class_getInstanceMethod([self class], @selector(sendForAopAction:to:forEvent:));
    
    Method original = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:));
    Method swizzled = class_getInstanceMethod([self class], @selector(sendForAopAction:to:forEvent:));
    
    if (!class_addMethod([self class], @selector(sendAction:to:forEvent:), method_getImplementation(tmp), method_getTypeEncoding(tmp))) {
        NSLog(@"由于方法已存在 添加失败 直接交换(这样做是为了避免父类方法与子类方法交换带来的问题)");
        method_exchangeImplementations(original, swizzled);
    }else{
        NSLog(@"方法不存在 添加方法成功 此添加方法的实现来自sendForAopAction:to:forEvent: 使用添加的方法 替换sendAction:to:forEvent:方法的实现");
        original = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:));
        class_replaceMethod([self class], @selector(sendForAopAction:to:forEvent:), method_getImplementation(original), method_getTypeEncoding(original));
    }
}
@end

点击按钮log如下(使用了两种方式):

 


 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值