iOS Runtime 简单使用

一、 发送消息

开发使用场景:调用未暴露的方法,前提条件,这个方法已经实现
  • 导入#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--青椒辣不辣

五、结后语

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容