美文网首页
iOS Runtime 简单使用

iOS Runtime 简单使用

作者: 青椒辣不辣 | 来源:发表于2017-08-17 16:53 被阅读94次

    一、 发送消息

    开发使用场景:调用未暴露的方法,前提条件,这个方法已经实现
    • 导入#import <objc/message.h>
    • -> Build Settings -> Enable Strict Checking of objc_msgSend Calls -> No
    • objc_msgSend异常处理
    • 方法调用的本质,就是让对象发送消息。
    • objc_msgSend,只有对象才能发送消息,因此以objc开头.

    objc_msgSend调用方法 不需要有方法的声明
    objc_msgSend参数

    • 第一个代表着,消息发送给谁,
    • 第二个是要发送的消息,也就是执行的方法,
    • 在往后就是,可能方法要传递的参数(可无)
    CallMeBoy类代码如下
    #import <Foundation/Foundation.h>
    @interface CallMeBoy : NSObject
    //-(void)callMeNow;
    //+(void)callMeNow;
    //-(NSString *)callMeNowWithName:(NSString *)name;
    @end
    
    #import "CallMeBoy.h"
    @implementation CallMeBoy
    -(void)callMeNow{
        NSLog(@"-%s",__func__);
    }
    +(void)callMeNow{
        NSLog(@"+%s",__func__);
    }
    -(NSString *)callMeNowWithName:(NSString *)name{
        if ([name isEqualToString:@"青椒"]) {
            return @"GLW";
        }
        return @"HZ";
    }
    @end
    

    1. 对象方法调用

    CallMeBoy *callMe = [[CallMeBoy alloc] init];
    //1 对象方法调用
    [callMe callMeNow];
    //本质 让对象发送消息(三种书写方式)
    objc_msgSend(callMe,@selector(callMeNow));
    ((void (*) (id, SEL)) (void *)objc_msgSend)(callMe, @selector(callMeNow));
    ((void (*) (id, SEL)) (void *)objc_msgSend)(callMe, sel_registerName("callMeNow"));
    
    //带参数和返回值的使用
    NSString *str  = [callMe callMeNowWithName:@"青椒"];
    NSString *str1 = objc_msgSend(callMe, @selector(callMeNowWithName:), @"青椒");
    NSString *str2 = ((NSString* (*)(id,SEL,NSString *))objc_msgSend)(callMe, @selector(callMeNowWithName:), @"青椒");
    NSLog(@"%@--%@--%@",str,str1,str2);
    

    2. 类方法调用

    //2 类方法调用
    [CallMeBoy callMeNow];//类名调用
    [[CallMeBoy class] callMeNow];//类对象调用
    objc_msgSend([CallMeBoy class],@selector(callMeNow));
    objc_msgSend([CallMeBoy class],sel_registerName("callMeNow"));
    ((void (*) (id, SEL)) (void *)objc_msgSend)([CallMeBoy class], @selector(callMeNow));
    ((void (*) (id, SEL)) (void *)objc_msgSend)([CallMeBoy class], sel_registerName("callMeNow"));
    

    二、 交换方法

    开发使用场景:系统自带的方法功能不够,给系统自带的方法扩展一些功能,并且保持原有的功能。
    • 方式一:继承系统的类,重写方法.
    • 方式二:使用runtime,交换方法.

    例子:[UIImage imageNamed:@"随便的名字"];该图片为空时打印
    2017-08-17 14:27:35.599 SocketGG[13015:1396365] 提示加载空的图片

    创建UIImage分类#import "UIImage+image.h"
    代码如下

    #import <UIKit/UIKit.h>
    @interface UIImage (image)
    @end
    
    #import "UIImage+image.h"
    #import <objc/runtime.h>
    @implementation UIImage (image)
    +(void)load{
        // 交换方法
        // 获取 imageWithName方法地址
        Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
        // 获取 imageNamed方法地址
        Method imageName = class_getClassMethod(self, @selector(imageNamed:));
        // 交换方法地址,相当于交换实现方式
        method_exchangeImplementations(imageWithName, imageName);
    }
    // 不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉,而且分类中不能调用super.
    // 既能加载图片又能打印
    + (instancetype)imageWithName:(NSString *)name {
        // 这里调用imageWithName,相当于调用imageName
        UIImage *image = [self imageWithName:name];
        if (image == nil) {
            NSLog(@"提示加载空的图片");
        }
        return image;
    }
    @end
    

    三、动态添加方法

    开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决

    这个机制中所涉及的方法主要有两个:

    + (BOOL)resolveInstanceMethod:(SEL)sel
    + (BOOL)resolveClassMethod:(SEL)sel
    

    CallMeBoy 分类代码如下
    class_addMethod第四个参数含义官网链接

    #import "CallMeBoy+CallWhat.h"
    #import <objc/runtime.h>
    
    // void(*)() // 默认方法都有两个隐式参数
    void callMeBaby(id self,SEL _cmd) {
        NSLog(@"%@ %@",self,NSStringFromSelector(_cmd));
    }
    // 带一个参数
    void callYouThen(id self, SEL _cmd, NSString *name) {
        NSLog(@"call you %@ ", name);
    }
    void callYouThenClass(id self, SEL _cmd, NSString *name) {
        NSLog(@"call you %@ class", name);
    }
    @implementation CallMeBoy (CallWhat)
    
    +(BOOL)resolveInstanceMethod:(SEL)sel{
        if (sel == @selector(callMeBaby)) {
            // 第一个参数:给哪个类添加方法
            // 第二个参数:添加方法的方法编号
            // 第三个参数:添加方法的函数实现(函数地址)
            // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
            class_addMethod(self, sel, (IMP)callMeBaby, "v@:");
            return YES;
        }
        if (sel == @selector(callName)) {
            class_addMethod([self class], sel, (IMP)callYouThen, "v@:@");
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    +(BOOL)resolveClassMethod:(SEL)sel{
        if (sel == @selector(callNameClass)) {
            class_addMethod(objc_getMetaClass("CallMeBoy"), sel,(IMP)callYouThenClass,"v#:@");
            return YES;
        }
        return [super resolveClassMethod:sel];
    }
    
    @end
    
    外部调用方式
    //对象方法
    [callMe performSelector:@selector(callMeBaby)];
    objc_msgSend(callMe,@selector(callMeBaby));
    objc_msgSend(callMe,@selector(callName),@"青椒辣不辣");
    [callMe performSelector:@selector(callName) withObject:@"青椒辣不辣"];
    //类对象
    objc_msgSend([CallMeBoy class], @selector(callNameClass), @"青椒");
    

    打印结果

    2017-08-17 16:13:38.611 SocketGG[18190:1936576] <CallMeBoy: 0x608000012470> callMeBaby
    2017-08-17 16:13:38.612 SocketGG[18190:1936576] <CallMeBoy: 0x608000012470> callMeBaby
    2017-08-17 16:13:38.612 SocketGG[18190:1936576] call you 青椒辣不辣 
    2017-08-17 16:13:38.612 SocketGG[18190:1936576] call you 青椒辣不辣 
    2017-08-17 16:13:42.037 SocketGG[18190:1936576] call you 青椒 class
    

    扩展:performSelectorCocoa内置只支持两个参数,多个参数的处理

    增加方法

    -(id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2 withObject:(id)object3 withObject:(id)object4 withObject:(id)object5{
        NSMethodSignature *meSig = [self methodSignatureForSelector:aSelector];
        if (meSig) {
            NSInvocation* invo = [NSInvocation invocationWithMethodSignature:meSig];
            [invo setTarget:self];
            [invo setSelector:aSelector];
            if (object1) {
                [invo setArgument:&object1 atIndex:2];
            }
            if (object2) {
                [invo setArgument:&object2 atIndex:3];
            }
            if (object3) {
                [invo setArgument:&object3 atIndex:4];
            }
            if (object4) {
                [invo setArgument:&object4 atIndex:5];
            }
            if (object5) {
                [invo setArgument:&object5 atIndex:6];
            }
            [invo invoke];
            if (meSig.methodReturnLength) {
                id anObject;
                [invo getReturnValue:&anObject];
                return anObject;
            } else {
                return nil;
            }
        } else {
            return nil;
        }
    }
    

    使用

    void callYouThenMany(id self, SEL _cmd, NSString *one, NSString *two, NSString *three) {
        NSLog(@"call you %@--%@--%@ ", one,two,three);
    }
    +(BOOL)resolveInstanceMethod:(SEL)sel{
        if (sel == @selector(callNameMany)) {
            class_addMethod([self class], sel, (IMP)callYouThenMany, "v@:@@@");
        }
        return [super resolveInstanceMethod:sel];
    }
    
    [callMe performSelector:@selector(callNameMany) withObject:@"1" withObject:@"2" withObject:@"3" withObject:nil withObject:nil];
    

    四、给分类添加属性

    原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。
    分类中不允许定义变量,也不会实现getter和setter方法,只能用runtime类实现
    #import <Foundation/Foundation.h>
    @interface NSObject (Objc)
    @property(nonatomic,copy)NSString *name;
    @end
    
    #import "NSObject+Objc.h"
    #import <objc/runtime.h>
    // 定义关联的key
    static const char *key = "name";
    @implementation NSObject (Objc)
    
    - (NSString *)name {
        // 根据关联的key,获取关联的值。
        return objc_getAssociatedObject(self, key);
    }
    - (void)setName:(NSString *)name {
        // 第一个参数: 给哪个对象添加关联
        // 第二个参数: 关联的key,通过这个key获取
        // 第三个参数: 关联的value
        // 第四个参数: 关联的策略
        /*
         OBJC_ASSOCIATION_ASSIGN;            //assign策略
         OBJC_ASSOCIATION_COPY_NONATOMIC;    //copy策略
         OBJC_ASSOCIATION_RETAIN_NONATOMIC;  //retain策略
         OBJC_ASSOCIATION_RETAIN;
         OBJC_ASSOCIATION_COPY;
         */
        objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    @end
    

    外界使用

    NSObject *objc = [[NSObject alloc] init];
    objc.name = @"青椒辣不辣";
    NSLog(@"name--%@",objc.name);
    

    2017-08-17 16:36:26.967 SocketGG[19114:2092692] name--青椒辣不辣

    五、结后语

    学习是一件既痛苦又快乐的事情,非学无以广才,非志无以成学,循序渐进,慢慢来就一定能积少成多.莫要知难就退,一知半解,与君共勉

    相关文章

      网友评论

          本文标题:iOS Runtime 简单使用

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