iOS Runtime 簡單使用

一、 發(fā)送消息

開發(fā)使用場景:調(diào)用未暴露的方法,前提條件,這個方法已經(jīng)實(shí)現(xiàn)
  • 導(dǎo)入#import <objc/message.h>
  • -> Build Settings -> Enable Strict Checking of objc_msgSend Calls -> No
  • objc_msgSend異常處理
  • 方法調(diào)用的本質(zhì),就是讓對象發(fā)送消息。
  • objc_msgSend,只有對象才能發(fā)送消息,因此以objc開頭.

objc_msgSend調(diào)用方法 不需要有方法的聲明
objc_msgSend參數(shù)

  • 第一個代表著,消息發(fā)送給誰,
  • 第二個是要發(fā)送的消息,也就是執(zhí)行的方法,
  • 在往后就是,可能方法要傳遞的參數(shù)(可無)
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. 對象方法調(diào)用

CallMeBoy *callMe = [[CallMeBoy alloc] init];
//1 對象方法調(diào)用
[callMe callMeNow];
//本質(zhì) 讓對象發(fā)送消息(三種書寫方式)
objc_msgSend(callMe,@selector(callMeNow));
((void (*) (id, SEL)) (void *)objc_msgSend)(callMe, @selector(callMeNow));
((void (*) (id, SEL)) (void *)objc_msgSend)(callMe, sel_registerName("callMeNow"));

//帶參數(shù)和返回值的使用
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. 類方法調(diào)用

//2 類方法調(diào)用
[CallMeBoy callMeNow];//類名調(diào)用
[[CallMeBoy class] callMeNow];//類對象調(diào)用
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"));

二、 交換方法

開發(fā)使用場景:系統(tǒng)自帶的方法功能不夠,給系統(tǒng)自帶的方法擴(kuò)展一些功能,并且保持原有的功能。
  • 方式一:繼承系統(tǒng)的類,重寫方法.
  • 方式二:使用runtime,交換方法.

例子:[UIImage imageNamed:@"隨便的名字"];該圖片為空時打印
2017-08-17 14:27:35.599 SocketGG[13015:1396365] 提示加載空的圖片

創(chuàng)建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:));
    // 交換方法地址,相當(dāng)于交換實(shí)現(xiàn)方式
    method_exchangeImplementations(imageWithName, imageName);
}
// 不能在分類中重寫系統(tǒng)方法imageNamed,因?yàn)闀严到y(tǒng)的功能給覆蓋掉,而且分類中不能調(diào)用super.
// 既能加載圖片又能打印
+ (instancetype)imageWithName:(NSString *)name {
    // 這里調(diào)用imageWithName,相當(dāng)于調(diào)用imageName
    UIImage *image = [self imageWithName:name];
    if (image == nil) {
        NSLog(@"提示加載空的圖片");
    }
    return image;
}
@end

三、動態(tài)添加方法

開發(fā)使用場景:如果一個類方法非常多,加載類到內(nèi)存的時候也比較耗費(fèi)資源,需要給每個方法生成映射表,可以使用動態(tài)給某個類,添加方法解決

這個機(jī)制中所涉及的方法主要有兩個:

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

CallMeBoy 分類代碼如下
class_addMethod第四個參數(shù)含義官網(wǎng)鏈接

#import "CallMeBoy+CallWhat.h"
#import <objc/runtime.h>

// void(*)() // 默認(rèn)方法都有兩個隱式參數(shù)
void callMeBaby(id self,SEL _cmd) {
    NSLog(@"%@ %@",self,NSStringFromSelector(_cmd));
}
// 帶一個參數(shù)
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)) {
        // 第一個參數(shù):給哪個類添加方法
        // 第二個參數(shù):添加方法的方法編號
        // 第三個參數(shù):添加方法的函數(shù)實(shí)現(xiàn)(函數(shù)地址)
        // 第四個參數(shù):函數(shù)的類型,(返回值+參數(shù)類型) 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
外部調(diào)用方式
//對象方法
[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), @"青椒");

打印結(jié)果

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

擴(kuò)展:performSelectorCocoa內(nèi)置只支持兩個參數(shù),多個參數(shù)的處理

增加方法

-(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];

四、給分類添加屬性

原理:給一個類聲明屬性,其實(shí)本質(zhì)就是給這個類添加關(guān)聯(lián),并不是直接把這個值的內(nèi)存空間添加到類存空間。
分類中不允許定義變量,也不會實(shí)現(xiàn)getter和setter方法,只能用runtime類實(shí)現(xiàn)
#import <Foundation/Foundation.h>
@interface NSObject (Objc)
@property(nonatomic,copy)NSString *name;
@end
#import "NSObject+Objc.h"
#import <objc/runtime.h>
// 定義關(guān)聯(lián)的key
static const char *key = "name";
@implementation NSObject (Objc)

- (NSString *)name {
    // 根據(jù)關(guān)聯(lián)的key,獲取關(guān)聯(lián)的值。
    return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name {
    // 第一個參數(shù): 給哪個對象添加關(guān)聯(lián)
    // 第二個參數(shù): 關(guān)聯(lián)的key,通過這個key獲取
    // 第三個參數(shù): 關(guān)聯(lián)的value
    // 第四個參數(shù): 關(guān)聯(lián)的策略
    /*
     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--青椒辣不辣

五、結(jié)后語

學(xué)習(xí)是一件既痛苦又快樂的事情,非學(xué)無以廣才,非志無以成學(xué),循序漸進(jìn),慢慢來就一定能積少成多.莫要知難就退,一知半解,與君共勉

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容