runtime簡介
Runtime就是運行時, 核心就是消息機制. 對OC的函數(shù)調(diào)用,是一個動態(tài)調(diào)用過程,只有在運行的時候runtime系統(tǒng)才能知道真正調(diào)用的哪一個函數(shù)(C語言在函數(shù)調(diào)用過程中, 編譯時候就已經(jīng)決定會調(diào)用哪個函數(shù)了).
iOS Runtime中實例對象和類的本質(zhì)
實例對象的本質(zhì)
OC是一門面向?qū)ο蟮木幊陶Z言,在編譯過程中,編譯器會將OC對象轉(zhuǎn)化成結(jié)構(gòu)體.
在objc.h中找到:
typedef struct objc_class *Class;
typedef struct objc_object *id;
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
可以看到我們常用的Class, id等關(guān)鍵字的定義.
OC中實際的類Class, 會被編譯成struct objc_class. 我們操作的類的對象實例是struct objc_object, 并且該結(jié)構(gòu)體中有一個指針指向struct objc_class.
iOS OC中類的本質(zhì)
OC對象的結(jié)構(gòu)體中有一個Class指針能夠理解, 因為要知道該對象是哪個類的對象.但是我們在objc-runtime-new.h中發(fā)現(xiàn)objc_class繼承自objc_object的.
struct objc_class : objc_object {
// Class ISA; // 繼承了
Class superclass;
...
在runtime.h中, 我們看到OC類的結(jié)構(gòu)體struct objc_class的具體定義
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
OC中類Class中也有一個指針指向Class, 因此類Class本質(zhì)上也是一個對象, 我們一般稱為類對象, 這個指向的Class是就是元類(metaClass)的對象.
當我們調(diào)用對象方法時候, 會通過對象中的Class指針找到對應的Class,然后調(diào)用實例方法,同理當我們調(diào)用類方法時候, 會通過Class中的Class指針找到對應的meta Class,然后調(diào)用meta Class中的方法.
OC一般會隱藏元類, 并且元類也是某個類的實例, 這個類我們一般稱為根元類(root meta Class). 并且所有的元類的根元類都是一個, 并且根元類的元類是它自己. (實際中根元類是NSObject的元類)
NSString實例的isa指針鏈:
iOS 中OC方法調(diào)用的本質(zhì)
OC中的方法調(diào)用稱為消息發(fā)送, 具體格式是[receiver message].例如:
NSMutableString *str = [[NSMutableString alloc] initWithString:@"hello"];
[str appendString:@" world"];
其中str就是receiver, appendString:就是message.
在message.h頭文件中如下方法,這個方法是runtime的核心方法,
void objc_msgSend(void /* id self, SEL op, ... */ )
objc_msgSend(receiever, selector, arg1, arg2, ...)
調(diào)用實例如下:
objc_msgSend(str, @selector(appendString:), @" world");
該消息方法為消息的動態(tài)綁定完成了以下工作:
- 它會主動查找receiver的selector對應的方法實現(xiàn)IMP
- 然后將參數(shù)傳遞給receiver object, 然后調(diào)用這IMP
- 最后返回該方法的返回值
==IMP:一個函數(shù)指針,保存了方法的地址==
為了使得objc_msgSend能完成通過selector查找receiver對應的IMP, 我們知道一個OC類和OC對象會有一個isa指針,指向他們各自的Class, 同時OC類還有一個super指針指向父類.
具體過程就是通過isa指針找到對應的class struct, 然后在dispatch table里面查找selector對應的方法, 如果沒有找到,那么通過super指針查找父類的dispatch table, 一直找下去, 直到NSObject類, 如果還沒有找到,就調(diào)用NSObject的doesNotRecognizeSelector:方法, 然后報unrecognized selector錯誤.
iOS runtime實戰(zhàn)應用
1.iOS runtime 進行添加屬性,并支持KVO監(jiān)聽
iOS 中category和runtime的AssociatedObject是兩大非常重要的工具:
category可以給既有類直接添加方法
associateObject可以給既有類添加屬性(類似成員變量)
結(jié)合這兩個工具, 那么通過category添加property方法.然后結(jié)合associateObject增加關(guān)聯(lián)對象,完成屬性存取.
==需要加入頭文件#import <objc/runtime.h>==
@interface UIViewController (Extension)
@property (nonatomic, copy) NSString * categoryString;
@end
@implementation UIViewController (Extension)
-(NSString *)categoryString{
return objc_getAssociatedObject(self, @selector(categoryString));
}
-(void)setCategoryString:(NSString *)categoryString{
objc_setAssociatedObject(self, @selector(categoryString), categoryString, OBJC_ASSOCIATION_COPY);
}
@end
并且這種方法也支持KVO的監(jiān)聽:
-(void)test{
self.categoryString = @"Runtime生成的屬性";
[self addObserver:self forKeyPath:@"categoryString" options:NSKeyValueObservingOptionNew context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"<接收到通知: object:%@ keyPath:%@ change:%@>", object, keyPath,change);
}
2.(1)交換兩個方法的實現(xiàn)缓呛,(2)攔截系統(tǒng)自帶的方法調(diào)用功能
Method class_getClassMethod(Class cls , SEL name) //獲得某個類的類方法
Method class_getInstanceMethod(Class cls , SEL name)//獲得某個類的實例對象方法
void method_exchangeImplementations(Method m1 , Method m2)//交換兩個方法的實現(xiàn)
案例1:方法簡單的交換
創(chuàng)建一個Person類,類中實現(xiàn)以下兩個類方法墩剖,并在.h 文件中聲明
+ (void)game {
NSLog(@"游戲");
}
+ (void)study {
NSLog(@"學習");
}
[Person study];
[Person game];
下面通過runtime 實現(xiàn)方法交換晾嘶,類方法用class_getClassMethod 果覆,對象方法用class_getInstanceMethod
// 獲取兩個類的類方法
Method m1 = class_getClassMethod([Person class], @selector(run));
Method m2 = class_getClassMethod([Person class], @selector(study));
// 開始交換方法實現(xiàn)
method_exchangeImplementations(m1, m2);
// 交換后
[Person study];
[Person game];
控制臺打印
2018-04-26 15:20:37.224745+0800 runtime-test[15020:7430831] 學習
2018-04-26 15:20:37.224835+0800 runtime-test[15020:7430831] 游戲
2018-04-26 15:20:37.225668+0800 runtime-test[15020:7430831] 游戲
2018-04-26 15:20:37.225720+0800 runtime-test[15020:7430831] 學習
案例2:攔截系統(tǒng)方法
1摇幻、為UIImage建一個分類(UIImage+Category)
2颓芭、在分類中實現(xiàn)一個自定義方法顷锰,方法中寫要在系統(tǒng)方法中加入的語句,添加自己的邏輯判斷
+ (UIImage *)xxx_imageNamed:(NSString *)name {
double version = 11.11;
if (version == 11.11) {
name = [name stringByAppendingString:@"5.1_time"];
}
return [UIImage xxx_imageNamed:name];
}
3亡问、分類中重寫UIImage的load方法官紫,實現(xiàn)方法的交換(只要能讓其執(zhí)行一次方法交換語句,load再合適不過了)
+ (void)load {
// 獲取兩個類的類方法
Method m1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
Method m2 = class_getClassMethod([UIImage class], @selector(xxx_imageNamed:));
// 開始交換方法實現(xiàn)
method_exchangeImplementations(m1, m2);
}
==注意:自定義方法中最后一定要再調(diào)用一下系統(tǒng)的方法州藕,讓其有加載圖片的功能束世,但是由于方法交換,系統(tǒng)的方法名已經(jīng)變成了我們自定義的方法名(就是用我們的名字能調(diào)用系統(tǒng)的方法床玻,用系統(tǒng)的名字能調(diào)用我們的方法)毁涉,這就實現(xiàn)了系統(tǒng)方法的攔截!==
3.獲得一個類的成員變量( Ivar )锈死、屬性( Property )贫堰、方法( Method )、協(xié)議( Protocol )
獲得某個類的所有成員變量(outCount 會返回成員變量的總數(shù))
參數(shù):
1待牵、哪個類
2其屏、放一個接收值的地址,用來存放屬性的個數(shù)
3缨该、返回值:存放所有獲取到的屬性漫玄,通過下面兩個方法可以調(diào)出名字和類型
Ivar *class_copyIvarList(Class cls , unsigned int *outCount)
獲得成員變量的名字
const char *ivar_getName(Ivar v)
獲得成員變量的類型
const char *ivar_getTypeEndcoding(Ivar v)
案例1:獲取Person類中所有成員變量的名字和類型
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([Person class], &outCount);
// 遍歷所有成員變量
for (int i = 0; i < outCount; i++) {
// 取出i位置對應的成員變量
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
const char *type = ivar_getTypeEncoding(ivar);
NSLog(@"成員變量名:%s 成員變量類型:%s",name,type);
}
// 注意釋放內(nèi)存!
free(ivars);
==同樣:==
// 測試 打印屬性列表
- (void)testPrintPropertyList {
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(@"property----="">%@", [NSString stringWithUTF8String:propertyName]);
}
free(propertyList);
}
// 測試 打印方法列表
- (void)testPrintMethodList {
unsigned int count;
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i=0; i<count; i++) {
Method method = methodList[i];
NSLog(@"method----="">%@", NSStringFromSelector(method_getName(method)));
}
free(methodList);
}
// 測試 打印協(xié)議列表
- (void)testPrintProtocolList {
unsigned int count;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i=0; i<count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol----="">%@", [NSString stringWithUTF8String:protocolName]);
}
free(protocolList);
}
案例2:利用Runtime進行 json/dict -> model
-(instancetype)initWithNSDictionary:(NSDictionary *)dict{
self = [super init];
if (self) {
[self processDict:dict];
}
return self;
}
-(void)processDict:(NSDictionary *)dict{
NSMutableArray *keys = [[NSMutableArray alloc] init];
unsigned int count = 0;
objc_property_t *props = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
objc_property_t prop = props[i];
const char *propCStr = property_getName(prop);
NSString *propName = [NSString stringWithCString:propCStr encoding:NSUTF8StringEncoding];
[keys addObject:propName];
}
free(props);
for (NSString *key in keys) {
if ([dict valueForKey:key]) {
[self setValue:[dict valueForKey:key] forKey:key];
}
}
}
案例3:利用runtime 獲取所有屬性來重寫歸檔解檔方法
// 解檔方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
// 獲取所有成員變量
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
// 將每個成員變量名轉(zhuǎn)換為NSString對象類型
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 忽略不需要解檔的屬性
if ([[self ignoredNames] containsObject:key]) {
continue;
}
// 根據(jù)變量名解檔取值压彭,無論是什么類型
id value = [aDecoder decodeObjectForKey:key];
// 取出的值再設(shè)置給屬性
[self setValue:value forKey:key];
// 這兩步就相當于以前的 self.age = [aDecoder decodeObjectForKey:@"_age"];
}
free(ivars);
return self;
}
// 歸檔調(diào)用方法
- (void)encodeWithCoder:(NSCoder *)aCoder {
// 獲取所有成員變量
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
// 將每個成員變量名轉(zhuǎn)換為NSString對象類型
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 忽略不需要歸檔的屬性
if ([[self ignoredNames] containsObject:key]) {
continue;
}
// 通過成員變量名睦优,取出成員變量的值
id value = [self valueForKeyPath:key];
// 再將值歸檔
[aCoder encodeObject:value forKey:key];
// 這兩步就相當于 [aCoder encodeObject:@(self.age) forKey:@"_age"];
}
free(ivars);
}