iOS Runtime(一)-簡(jiǎn)介及使用示例

OC語(yǔ)言是一門(mén)動(dòng)態(tài)語(yǔ)言,它將很多靜態(tài)語(yǔ)言在編譯和鏈接時(shí)期做的事放到了運(yùn)行時(shí)來(lái)處理。這種動(dòng)態(tài)語(yǔ)言的優(yōu)勢(shì)在于:我們寫(xiě)代碼時(shí)更具靈活性,如我們可以把消息轉(zhuǎn)發(fā)給我們想要的對(duì)象,或者隨意交換一個(gè)方法的實(shí)現(xiàn)等。

這種特性意味著OC不僅需要一個(gè)編譯器,還需要一個(gè)運(yùn)行時(shí)系統(tǒng)來(lái)執(zhí)行編譯的代碼。對(duì)于OC來(lái)說(shuō),這個(gè)運(yùn)行時(shí)系統(tǒng)就像一個(gè)操作系統(tǒng)一樣:它讓所有的工作可以正常的運(yùn)行。這個(gè)運(yùn)行時(shí)系統(tǒng)即Objc Runtime。Objc Runtime其實(shí)是一個(gè)Runtime庫(kù),它基本上是用C和匯編寫(xiě)的,這個(gè)庫(kù)使得C語(yǔ)言有了面向?qū)ο蟮哪芰Α?/p>

1. 簡(jiǎn)介

(1)簡(jiǎn)稱運(yùn)行時(shí),是一套比較底層的純C語(yǔ)言API。
(2)Runtime是指將數(shù)據(jù)類型的確定由編譯時(shí)推遲到了運(yùn)行時(shí)。
(3)OC代碼,在程序運(yùn)行過(guò)程中,最終會(huì)轉(zhuǎn)換成Runtime的C語(yǔ)言代碼,Runtime是Object-C的幕后工作者。
(4)OC需要Runtime來(lái)創(chuàng)建類和對(duì)象,進(jìn)行消息發(fā)送和轉(zhuǎn)發(fā)。

2.使用

(1)基本使用

  • 在程序運(yùn)行過(guò)程中,動(dòng)態(tài)的創(chuàng)建類,動(dòng)態(tài)添加、修改這個(gè)類的屬性和方法
  • 遍歷一個(gè)類中所有的成員變量、屬性以及所有方法
  • 消息傳遞和轉(zhuǎn)發(fā)

(2)典型使用

  • 給系統(tǒng)分類添加屬性、方法
  • 方法交換
  • 獲取對(duì)象的屬性、私有屬性
  • 字典轉(zhuǎn)模型
  • KVC、KVO
  • 歸檔(編碼、解碼)
  • block
    ......
3.使用示例
(1)動(dòng)態(tài)交換兩個(gè)方法

應(yīng)用場(chǎng)景:當(dāng)?shù)谌娇蚣芑蛘呦到y(tǒng)原生功能不能滿足我們的時(shí)候,可以保持系統(tǒng)原有方法功能基礎(chǔ)上,添加額外的功能。

#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    /**
     加載一張圖片,提示是否加載成功
     */
    //調(diào)用系統(tǒng)加載圖片的方法
    UIImage *image = [UIImage imageNamed:@"44"];
}
@end
//UIImage分類
#import "UIImage+ImageLoad.h"
#import <objc/message.h>
@implementation UIImage (ImageLoad)
/**
 load方法:把類加載進(jìn)內(nèi)存的時(shí)候調(diào)用,只會(huì)調(diào)用一次
 方法應(yīng)先交換,再去調(diào)用
 */
+ (void)load {
    //1.獲取imageNamed方法地址
    //class_getClassMethod 獲取某個(gè)類的方法
    Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
    //2.獲取in_imageNamed方法地址
    Method in_imageNamedMethpd = class_getClassMethod(self, @selector(in_imageNamed:));
    //3.交換方法地址,相當(dāng)于交換實(shí)現(xiàn)方法
    method_exchangeImplementations(imageNamedMethod, in_imageNamedMethpd);
}
/**
 不會(huì)出現(xiàn)死循環(huán)
 調(diào)用imageNamed: -> in_imageNamed:
 調(diào)用in_imageNamed: -> imageNamed:
 */
+ (UIImage *) in_imageNamed:(NSString *)name{
    //實(shí)際上調(diào)用的是系統(tǒng)的imageNamed:
    UIImage *image = [UIImage in_imageNamed:name];
    if (image) {
        NSLog(@"加載成功");
    }else{
        NSLog(@"加載失敗");
    }
    return image;
}
/**
 不能在分類中重寫(xiě)系統(tǒng)方法imageNamed:,會(huì)把系統(tǒng)的功能覆蓋掉,而且分類中不能調(diào)用super
+ (UIImage *)imageNamed:(NSString *)name{
    
}
*/
@end
(2)給分類動(dòng)態(tài)添加屬性

原理:給一個(gè)類添加屬性,其實(shí)本質(zhì)就是給這個(gè)類添加關(guān)聯(lián),并不是直接把這個(gè)值的內(nèi)存空間添加到類存空間。(應(yīng)用場(chǎng)景:給系統(tǒng)的類添加屬性的時(shí)候,可以使用runtime【系統(tǒng) NSObject 添加一個(gè)分類,我們知道在分類中是不能夠添加成員屬性的,雖然我們用了@property,但是僅僅會(huì)自動(dòng)生成get和set方法的聲明,并沒(méi)有帶下劃線的成員變量和方法實(shí)現(xiàn)生成。但是我們可以通過(guò)runtime就可以做到給它方法的實(shí)現(xiàn)。】)

#import <Foundation/Foundation.h>
@interface NSObject (Property)
//@property在分類中,只會(huì)生產(chǎn)get、set、方法聲明,不會(huì)生產(chǎn)實(shí)現(xiàn),也不會(huì)生產(chǎn)帶下劃線的成員屬性
@property (nonatomic,strong)NSString *name;
@end
#import "NSObject+Property.h"
#import <objc/message.h>
@implementation NSObject (Property)
-(void)setName:(NSString *)name{
    /**
     objc_setAssociatedObject將某個(gè)值跟某個(gè)對(duì)象關(guān)聯(lián)起來(lái),將某個(gè)值存儲(chǔ)到某個(gè)對(duì)象中
     */
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)name{
    return objc_getAssociatedObject(self, @"name");
}
@end
#import "ViewController.h"
#import "NSObject+Property.h"
@interface ViewController ()
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSObject *objc = [[NSObject alloc]init];
    objc.name = @"zhangsan";
    NSLog(@"%@",objc.name);
}

@end
(3)字典轉(zhuǎn)模型

字典轉(zhuǎn)模型的方式:
1)一個(gè)一個(gè)屬性賦值
2)字典轉(zhuǎn)模型KVC實(shí)現(xiàn)
KVC字典轉(zhuǎn)模型必須保證模型中屬性和字典中的key一一對(duì)應(yīng),可以重寫(xiě)重寫(xiě)對(duì)象的setValue:forUndefinedKey:,把系統(tǒng)的方法覆蓋

#import <Foundation/Foundation.h>
@interface Person : NSObject

@property (nonatomic,strong)NSString *name;
@property (nonatomic,assign)NSInteger age;
#pragma mark - 模型構(gòu)造函數(shù)
+(instancetype)personWithDict:(NSDictionary *)dict;
-(instancetype)initWithDict:(NSDictionary *)dict;
@end
#import "Person.h"

@implementation Person

+(instancetype)personWithDict:(NSDictionary *)dict{
    return [[self alloc]initWithDict:dict];
}

-(instancetype)initWithDict:(NSDictionary *)dict{
    self = [super init];
    if (self) {
        //方法一:直接設(shè)置
        _name = dict[@"name"];
        _age = [dict[@"age"] integerValue];
        //方法二:使用KVC設(shè)置
        [self setValue:dict[@"name"] forKey:@"name"];
        [self setValue:dict[@"age"] forKey:@"age"];
        //方法三:遍歷字典設(shè)置
        for (NSString *key in dict) {
            id value = dict[key];
            [self setValue:value forKey:key];
        }
        //方法四:簡(jiǎn)化方法三
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
    
}
@end

3)字典轉(zhuǎn)模型runtime實(shí)現(xiàn)

#import <Foundation/Foundation.h>
@protocol ModelDelegate<NSObject>
@optional
//提供一個(gè)協(xié)議,只要準(zhǔn)備這個(gè)協(xié)議的類,都能把數(shù)組中的字典轉(zhuǎn)成模型(返回字典為數(shù)組屬性名:模型名)
+(NSDictionary *)arrayContainModelClass;
@end
@interface NSObject (Model)
//字典轉(zhuǎn)模型
+(instancetype)objectWithDoct:(NSDictionary *)dict;
@end
#import "NSObject+Model.h"
#import <objc/message.h>

@implementation NSObject (Model)

+(instancetype)objectWithDoct:(NSDictionary *)dict{
    //創(chuàng)建模型對(duì)象
    id objc = [[self alloc]init];
    unsigned int count = 0;
    //獲取成員屬性數(shù)組
    Ivar *ivarList = class_copyIvarList(self, &count);
    //遍歷所有的成員屬性名,一個(gè)一個(gè)去字典中取出對(duì)應(yīng)的value給模型屬性賦值
    for (int i = 0; i < count; i++) {
        //獲取成員屬性
        Ivar ivar = ivarList[i];
        //獲取成員屬性名 c -> oc字符串
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        //_成員屬性名 -> 字典key
        NSString *key = [ivarName substringFromIndex:1];
        //字典取出對(duì)應(yīng)value給模型屬性賦值
        id value = dict[key];
        //獲取成員屬性類型
        NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        //二級(jí)轉(zhuǎn)換,字典中還有字典,也需要把字典轉(zhuǎn)換成模型
        //判斷value是不是字典
        if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) {
            //是字典對(duì)象,并且屬性名對(duì)應(yīng)類型是自定義類型
            //處理類型字符串 @\"User\" -> User
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
            //自定義對(duì)象,并且值是字典
            Class modelClass = NSClassFromString(ivarType);
            if (modelClass) {
                //字典轉(zhuǎn)模型
                value = [modelClass objectWithDoct:value];
            }
        }
        //三級(jí)轉(zhuǎn)換,NSArray中也是字典,把數(shù)組中的字典轉(zhuǎn)換成模型
        //判斷值是否是數(shù)組
        if ([value isKindOfClass:[NSArray class]]) {
            //判斷對(duì)應(yīng)類有沒(méi)有實(shí)現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組協(xié)議
            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
                //轉(zhuǎn)換成id類型,就能調(diào)用任何對(duì)象的方法
                id idSelf = self;
                //獲取數(shù)組中字典對(duì)應(yīng)的模型
                NSString *type = [idSelf arrayContainModelClass][key];
                //生產(chǎn)模型
                Class classModel = NSClassFromString(type);
                NSMutableArray *arrM = [NSMutableArray array];
                //遍歷字典數(shù)組,生產(chǎn)模型數(shù)組
                for (NSDictionary *dict in value) {
                    id model = [classModel objectWithDoct:dict];
                    [arrM addObject:model];
                }
                //把模型數(shù)組賦值給value
                value = arrM;
            }
        }
        if (value) {
            //KVC字典轉(zhuǎn)模型
            [objc setValue:value forKey:key];
        }
    }
    //返回對(duì)象
    return objc;
}
@end

測(cè)試:

#import <Foundation/Foundation.h>
#import "CarType.h"
@interface Car : NSObject
@property (nonatomic,strong)CarType *carType;
@property (nonatomic,assign)NSInteger speed;
@property (nonatomic,strong)NSArray * CarColorArr;
@end
#import "Car.h"
#import "NSObject+Model.h"
@interface Car()<ModelDelegate>
@end
@implementation Car
//實(shí)現(xiàn)協(xié)議方法
+ (NSDictionary *)arrayContainModelClass{
    return @{@"CarColorArr":@"CarColor"};
}
@end
#import <Foundation/Foundation.h>
@interface CarType : NSObject
@property (nonatomic,strong)NSString *type;
@end
#import <Foundation/Foundation.h>
@interface CarColor : NSObject
@property (nonatomic,strong)NSString *color;
@end
#import "ViewController.h"
#import "NSObject+Model.h"
#import "Car.h"
#import "CarType.h"
#import "CarColor.h"
@interface ViewController ()
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSDictionary * dict = @{
                            @"carType":@{@"type":@"BMW"},
                            @"speed":@120,
                            @"CarColorArr":@[@{@"color":@"red"},@{@"color":@"blue"}]
                            };
    Car *car = [Car objectWithDoct:dict];
    NSLog(@"type == %@,speed == %ld",car.carType.type,car.speed);
    CarColor *color = car.CarColorArr[0];
    NSLog(@"color == %@",color.color);
    
}
@end
(4)動(dòng)態(tài)添加方法

應(yīng)用場(chǎng)景:如果一個(gè)類的方法非常多,加載類到內(nèi)存的時(shí)候也比較耗費(fèi)資源,需要給每個(gè)方法生成映射表,可以使用動(dòng)態(tài)給某個(gè)類添加方法解決。

#import <Foundation/Foundation.h>
@interface Person : NSObject
@end
#import "Person.h"
#import <objc/message.h>
@implementation Person
//沒(méi)有返回值,一個(gè)參數(shù)
//void,(id,SEL)
void aaa(id self,SEL _cmd,NSNumber *meter){
    NSLog(@"走了%@米",meter);
}
/**
 任何方法默認(rèn)都有兩個(gè)隱式參數(shù),self和_cmd(當(dāng)前方法的方法編號(hào))
 resolveInstanceMethod:只要一個(gè)對(duì)象調(diào)用了一個(gè)未實(shí)現(xiàn)的方法,就會(huì)調(diào)用此方法進(jìn)行處理(消息轉(zhuǎn)發(fā)機(jī)制)
 作用:動(dòng)態(tài)添加方法,處理未實(shí)現(xiàn)方法
 */
+(BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == NSSelectorFromString(@"walk:")) {
        class_addMethod(self, sel, (IMP)aaa, "v@:@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
@end
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *p = [[Person alloc]init];
    [p performSelector:@selector(walk:) withObject:@100];
}
@end
(5)動(dòng)態(tài)變量控制
#import "ViewController.h"
#import "Person.h"
#import <objc/message.h>
@interface ViewController ()
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *xiaoming = [[Person alloc]init];
    unsigned int count = 0;
    Ivar *ivarList = class_copyIvarList([xiaoming class], &count);
    for (int i = 0; i<count; i++) {
        Ivar ivar = ivarList[i];
        const char *ivarName = ivar_getName(ivar);
        NSString *name = [NSString stringWithUTF8String:ivarName];
        if ([name isEqualToString:@"_age"]) {
            object_setIvar(xiaoming, ivar, @"22");
            break;
        }
    }
    NSLog(@"%@",xiaoming.age);
}
@end
(6)數(shù)組越界

動(dòng)態(tài)交換方法,防止數(shù)組越界導(dǎo)致崩潰


#import "NSArray+DJSafeIndex.h"
#import <objc/runtime.h>

@implementation NSArray (DJSafeIndex)

+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        // NSArray 是一個(gè)類簇,具體有三個(gè)子類__NSArray0,__NSSingleObjectArrayI,__NSArrayI,
        
               // 還有一個(gè)__NSPlaceholderArray是占位的,不實(shí)際使用
               // 對(duì)__NSArray0,__NSSingleObjectArrayI來(lái)說(shuō),下面三種調(diào)用的同一個(gè)方法objectAtIndex
               // 對(duì)__NSArrayI,__NSArrayM來(lái)說(shuō),objectAtIndex 和 objectAtIndexedSubscript 有不同的實(shí)現(xiàn),
      
        Method method = class_getInstanceMethod(objc_getClass("_NSArray0"), @selector(objectAtIndex:));
        Method changeMethod = class_getInstanceMethod(objc_getClass("_NSArray0"), @selector(emptyObjectIndex:));
        method_exchangeImplementations(method, changeMethod);
        
        
        Method method1 = class_getInstanceMethod(objc_getClass("__NSSingleObjectArrayI"), @selector(objectAtIndex:));
        Method changeMethod1 = class_getInstanceMethod(objc_getClass("__NSSingleObjectArrayI"), @selector(singleObjectIndex:));
        method_exchangeImplementations(method1, changeMethod1);
        
        Method method2 = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
        Method changeMethod2 = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(safe_arrObjectIndex:));
        method_exchangeImplementations(method2, changeMethod2);
        
        
        Method method3 = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndexedSubscript:));
        Method changeMethod3 = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(safe_objectAtIndexedSubscript:));
        method_exchangeImplementations(method3, changeMethod3);
        
        
    });
}

- (id)emptyObjectIndex:(NSInteger)index {

   NSLog(@"__NSArray0 取一個(gè)空數(shù)組 objectAtIndex , 崩潰") ;

   return nil;

}

- (id)singleObjectIndex:(NSInteger)index {

   if (index >= self.count || index < 0) {

       NSLog(@"__NSSingleObjectArrayI 取一個(gè)不可變單元素?cái)?shù)組時(shí)越界 objectAtIndex , 崩潰") ;

       return nil;

   }

   return [self singleObjectIndex:index];

}

- (id)safe_arrObjectIndex:(NSInteger)index{

   if (index >= self.count || index < 0) {

       NSLog(@"__NSArrayI 取不可變數(shù)組時(shí)越界 objectAtIndex , 崩潰") ;

       return nil;

   }

   return [self safe_arrObjectIndex:index];
}

- (id)safe_objectAtIndexedSubscript:(NSInteger)index{

   if (index >= self.count || index < 0) {

       NSLog(@"__NSArrayI 取不可變數(shù)組時(shí)越界 objectAtIndexedSubscript , 崩潰") ;

       return nil;

   }

   return [self safe_objectAtIndexedSubscript:index];
}

- (id)mutableArray_safe_objectAtIndexedSubscript:(NSInteger)index{

   if (index >= self.count || index < 0) {

       NSLog(@"__NSArrayM 取可變數(shù)組時(shí)越界 objectAtIndexedSubscript , 崩潰") ;

       return nil;

   }

   return [self mutableArray_safe_objectAtIndexedSubscript:index];
}

- (id)safeObjectIndex:(NSInteger)index{

   if (index >= self.count || index < 0) {

       NSLog(@"__NSArrayM 取可變數(shù)組時(shí)越界 objectAtIndex , 崩潰") ;

       return nil;

   }

   return [self safeObjectIndex:index];

}

- (void)safeInsertObject:(id)object atIndex:(NSUInteger)index{

   if (index>self.count) {

       NSLog(@"__NSArrayM 添加元素越界 insertObject:atIndex: , 崩潰") ;

       return ;

   }

   if (object == nil) {

       NSLog(@"__NSArrayM 添加空元素 insertObject:atIndex: , 崩潰") ;

       return ;

   }

   [self safeInsertObject:object atIndex:index];

}

- (void)safeAddObject:(id)object {

   if (object == nil) {

       NSLog(@"__NSArrayM 添加空元素 addObject , 崩潰") ;

       return ;

   }
   [self safeAddObject:object];

}

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

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

  • Runtime簡(jiǎn)介以及常見(jiàn)的使用場(chǎng)景 Runtime簡(jiǎn)稱運(yùn)行時(shí),是一套比較底層的純C語(yǔ)言的API,作為OC的核心...
    輕云_閱讀 1,285評(píng)論 5 23
  • 一、RunTime概念 RunTime簡(jiǎn)稱運(yùn)行時(shí),我們總是聽(tīng)說(shuō)OC是動(dòng)態(tài)語(yǔ)言運(yùn)行時(shí)機(jī)制,也就是系統(tǒng)在運(yùn)行時(shí)候的一些...
    劉光軍_MVP閱讀 3,435評(píng)論 0 7
  • 1.runtime簡(jiǎn)介 因?yàn)镺bjc是一門(mén)動(dòng)態(tài)語(yǔ)言,所以它總是想辦法把一些決定工作從編譯連接推遲到運(yùn)行時(shí)。也就是說(shuō)...
    杰哥ios工程獅閱讀 4,315評(píng)論 0 47
  • 一、runtime簡(jiǎn)介 Runtime簡(jiǎn)稱運(yùn)行時(shí)。OC就是運(yùn)行時(shí)機(jī)制,其中最主要的是消息機(jī)制。對(duì)于C語(yǔ)言,函數(shù)的調(diào)...
    半路qzg閱讀 441評(píng)論 0 1
  • 參考https://www.cnblogs.com/ioshe/p/5489086.html 一. Runtime...
    末上閱讀 437評(píng)論 0 1

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