開(kāi)篇
學(xué)習(xí)一門(mén)編程語(yǔ)言格二。不僅僅是用它來(lái)做項(xiàng)目歪沃,要懂得它的原理,這樣做心里踏實(shí)橄霉。想要更加深入的掌握OC或者做好iOS開(kāi)發(fā)窃爷,runtime無(wú)疑是打開(kāi)這個(gè)門(mén)的鑰匙。
那兒_并不遠(yuǎn)
OC語(yǔ)言中的runtime機(jī)制
- OC語(yǔ)言是一門(mén)動(dòng)態(tài)語(yǔ)言姓蜂,他將很多其他的靜態(tài)語(yǔ)言在編譯和連接時(shí)期做的事放在了運(yùn)行時(shí)來(lái)處理按厘。所以說(shuō)OC不僅需要編譯器,他還需要一個(gè)運(yùn)行時(shí)系統(tǒng)來(lái)處理編譯的代碼钱慢,這個(gè)運(yùn)行時(shí)系統(tǒng)就像一個(gè)操作系統(tǒng)一樣逮京,讓所有的工作可以正常順利的進(jìn)行。
- 運(yùn)行時(shí)系統(tǒng)就是我們所說(shuō)的運(yùn)行時(shí)機(jī)制(Objc Runtime)束莫,它是一套由C語(yǔ)言和匯編語(yǔ)言開(kāi)發(fā)的開(kāi)源庫(kù)懒棉,由很多數(shù)據(jù)結(jié)構(gòu)和函數(shù)組成,通過(guò)runtime我們可以動(dòng)態(tài)的修改類(lèi)览绿,對(duì)象中的屬性或者方法策严,無(wú)論公有私有。
類(lèi)和對(duì)象的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; // 指針饿敲,實(shí)例的isa指向類(lèi)的對(duì)象妻导,類(lèi)對(duì)象的isa指向元類(lèi)。
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類(lèi)
const char *name OBJC2_UNAVAILABLE; // 類(lèi)名
long version OBJC2_UNAVAILABLE; // 類(lèi)的版本信息,默認(rèn)為0
long info OBJC2_UNAVAILABLE; // 類(lèi)信息,供運(yùn)行期使用的一些位標(biāo)識(shí)
long instance_size OBJC2_UNAVAILABLE; // 該類(lèi)的實(shí)例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類(lèi)的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
在上述的runtime封裝的類(lèi)的數(shù)據(jù)結(jié)構(gòu)中通常我們會(huì)對(duì)一下幾個(gè)變量感興趣:
- isa : 學(xué)習(xí)過(guò)C語(yǔ)言的同學(xué)知道指針的作用诀蓉,指針其實(shí)就是我們將數(shù)據(jù)存放到內(nèi)存中的地址栗竖,同理isa就是我們創(chuàng)建的類(lèi)或者對(duì)象存放在內(nèi)存中的地址(在這里我們需要注意,在OC中渠啤,所有的類(lèi)自身也是一個(gè)對(duì)象,這個(gè)對(duì)象的Class里面也有一個(gè)isa指針添吗,它指向metaClass(元類(lèi)))沥曹,當(dāng)我們向一個(gè)OC對(duì)象發(fā)送消息時(shí),運(yùn)行時(shí)庫(kù)會(huì)根據(jù)實(shí)例對(duì)象的isa指針找到這個(gè)實(shí)例對(duì)象所屬的類(lèi),Runtime庫(kù)會(huì)在類(lèi)的方法列表及父類(lèi)的方法列表中尋找與消息對(duì)應(yīng)的selector指向的方法.找到后即運(yùn)行這個(gè)方法.
- cache : 用于緩存我們最近調(diào)用的方法,當(dāng)我們調(diào)用一個(gè)對(duì)象的方法時(shí)妓美,會(huì)根據(jù)isa指針找到這個(gè)對(duì)象僵腺,然后遍歷方法鏈表,但是壶栋,一個(gè)對(duì)象中會(huì)存在很多的方法辰如,很多都是很少用的,如果每調(diào)用一次對(duì)象的方法都去遍歷一次方法鏈表贵试,勢(shì)必會(huì)降低性能琉兜,所以這是,我們把常用的方法緩存到cache中毙玻,這樣每次調(diào)用方法時(shí)豌蟋,runtime會(huì)優(yōu)先到cache中查找,如果沒(méi)有找到再去遍歷方法鏈表桑滩。
獲取屬性梧疲,變量,方法运准,協(xié)議鏈表
- 這里先創(chuàng)建了一個(gè)Person類(lèi)幌氮,下面分別為Person.h,Person.m文件
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *sex;
@property (nonatomic, copy) NSString *age;
- (void)sayHi;
@end
#import "Person.h"
#import <objc/runtime.h>
@interface Person ()
@property (nonatomic, copy) NSString *privacy;
@end
@implementation Person
- (void)sayHi {
NSLog(@"my name is coco");
}
}
@end
- 接下來(lái)我們?cè)趤?lái)獲取Person中鏈表
// 獲取屬性
- (void)runtimeTestForProperName {
unsigned int count;
objc_property_t *properList = class_copyPropertyList([Person class], &count);
for (unsigned i = 0; i < count; i++) {
const char *properName = property_getName(properList[i]);
NSLog(@"屬性名稱(chēng) %@", [NSString stringWithUTF8String:properName]);
}
}
// 獲取變量
- (void)runtimeTestForIvar {
unsigned int count;
Ivar *ivarList = class_copyIvarList([Person class], &count);
for (unsigned int i = 0; i < count; i++) {
const char *ivarName = ivar_getName(ivarList[i]);
NSLog(@"成員變量 %@", [NSString stringWithUTF8String:ivarName]);
}
}
// 獲取方法
- (void)runtimeForMethod {
unsigned int count;
Method *methodList = class_copyMethodList([Person class], &count);
for (unsigned int i = 0; i < count; i++) {
Method method = methodList[i];
SEL methodName = method_getName(method);
NSLog(@"方法名稱(chēng) %@", NSStringFromSelector(methodName));
}
}
// 獲取協(xié)議
- (void)runtimeForProtocol {
unsigned int count;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([Person class], &count);
for (unsigned int i; i<count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"協(xié)議方法 %@", [NSString stringWithUTF8String:protocolName]);
}
}
動(dòng)態(tài)添加屬性
- 向person對(duì)象中添加school屬性
- (void)setupProperty {
/** objc_setAssociatedObject: 方法的參數(shù)說(shuō)明
object: 添加的對(duì)象
key: 添加的屬性名稱(chēng)(key值胁澳,獲取時(shí)會(huì)用到)
value: 添加對(duì)應(yīng)的屬性值 (value值)
policy: 添加的策略(屬性對(duì)應(yīng)的修飾)
**/
Person *person = [[Person alloc] init];
objc_setAssociatedObject(person, @"school", @"吉林師范", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
NSString *associatedValue = objc_getAssociatedObject(person, @"school");
NSLog(@"設(shè)置的屬性 :%@", associatedValue);
}
其中 我們可以點(diǎn)進(jìn)policy這個(gè)參數(shù)中该互,我們可以清除的看到這是關(guān)于我們定義屬性的時(shí)候用到的幾種修飾。
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
- 上述是向現(xiàn)有類(lèi)中添加屬性听哭,如果你查看runtime的API你就會(huì)發(fā)現(xiàn)慢洋,還有一個(gè) class_addIvar:方法可以添加變量,但是這個(gè)方法不支持現(xiàn)有類(lèi)的添加陆盘,官方是這樣說(shuō)的
// 此方法僅可在objc_allocateclasspair方法和objc_registerclasspair之間使用
* This function may only be called after objc_allocateClassPair and before objc_registerClassPair.
// 不支持在現(xiàn)有類(lèi)中添加一個(gè)實(shí)例變量
* Adding an instance variable to an existing class is not supported.
接下來(lái)我們通過(guò)動(dòng)態(tài)創(chuàng)建一個(gè)類(lèi)普筹,并向其中添加變量“恚看代碼
- (void)createMyClass
{
/** objc_allocateClassPair: 參數(shù)說(shuō)明
superclass: 父類(lèi)
name: 類(lèi)名
extraButes: 額外分配的字節(jié)(寫(xiě)0就可以)
**/
/** class_addIvar: 參數(shù)說(shuō)明
cls: 類(lèi)
name: 變量名
size: 變量的所占內(nèi)存的字節(jié)
alignment: 對(duì)齊方式(寫(xiě)0就可以)
types: 變量類(lèi)型
**/
// rumtime 動(dòng)態(tài)創(chuàng)建一個(gè)類(lèi)
Class MyClass = objc_allocateClassPair([NSObject class], "myclass", 0);
/ / 添加一個(gè)NSString的變量
if (class_addIvar(MyClass, "motto", sizeof(NSString *), 0, "@")) {
NSLog(@"add ivar success");
}
// 注冊(cè)這個(gè)類(lèi)到runtime系統(tǒng)中才可以使用它
objc_registerClassPair(MyClass);
// 生成了一個(gè)實(shí)例化對(duì)象
id myobj = [[MyClass alloc] init];
NSString *string = @"it's my live";
// 賦值
[myobj setValue: string forKey:@"motto"];
Ivar ivarOFset = class_getInstanceVariable([myobj class], "motto");
id value = object_getIvar(myobj, ivarOFset);
NSLog(@"添加的變量的值 = %@", value);
}
動(dòng)態(tài)添加方法
- (void)setupMethod {
/** class_addMethod: 參數(shù)說(shuō)明
cls: 類(lèi)
name: 方法名
imp: 方法的地址
types: 方法類(lèi)型
**/
// 獲取 song 方法的類(lèi)型
Method method = class_getInstanceMethod([self class], @selector(song));
const char *methodType = method_getTypeEncoding(method);
NSLog(@"參數(shù)類(lèi)型 %@", [NSString stringWithUTF8String:methodType]);
// 添加 song 方法
class_addMethod([Person class], @selector(song), class_getMethodImplementation([self class], @selector(song)), methodType);
// 創(chuàng)建實(shí)例對(duì)象
Person *person = [[Person alloc] init];
// 調(diào)用添加的方法
[person performSelector:@selector(song) withObject:nil];
}
- (void)song {
NSLog(@"大家好太防,為大家來(lái)一首 - 《那就這樣吧》");
}
通過(guò)ruantime交換,替換方法
- 交換兩個(gè)方法(方法A酸员,方法B)蜒车,當(dāng)兩個(gè)方法交換成功后,調(diào)用方法A后就會(huì)執(zhí)行方法B中的代碼幔嗦。
- 替換方法酿愧,很好理解,就是將方法A替換成方法B邀泉。
** 無(wú)論是交換還是替換方法嬉挡,實(shí)質(zhì)rumtime就是將方法的指針做了交換或者替換 **
- (void)exchangeMethod {
// 獲取兩個(gè)方法的Method
Method funcA = class_getInstanceMethod([self class], @selector(functionA));
Method funcB = class_getInstanceMethod([self class], @selector(functionB));
// 交換兩個(gè)方法
method_exchangeImplementations(funcA, funcB);
[self functionA];
[self functionB];
/** 官方文檔是這樣介紹 method_exchangeImplementations 方法實(shí)現(xiàn)的:
* @note This is an atomic version of the following:
* \code
* IMP imp1 = method_getImplementation(m1);
* IMP imp2 = method_getImplementation(m2);
* method_setImplementation(m1, imp2);
* method_setImplementation(m2, imp1);
* \endcode
**/
}
- (void)replaceMethod {
Method funcB = class_getInstanceMethod([self class], @selector(functionB));
// 將方法A替換成方法B
class_replaceMethod([self class], @selector(functionA), method_getImplementation(funcB), method_getTypeEncoding(funcB));
[self functionA];
[self functionB];
}
- (void)functionA {
NSLog(@"我是方法A");
}
- (void)functionB {
NSLog(@"我是方法B");
}
總結(jié)
通過(guò)上述的介紹我想大家對(duì)runtime有了一個(gè)初步的了解钝鸽,同時(shí)對(duì)于OC也會(huì)又一個(gè)更深的認(rèn)識(shí)。上面代碼的運(yùn)行結(jié)果我就不給大家一一截圖了庞钢,我都是驗(yàn)證后才寫(xiě)在博客上的拔恰。runtime中還有很多方法,但是都是大同小異基括,用法都是可以舉一反三的颜懊。希望這篇博客會(huì)對(duì)大家有所啟發(fā),同時(shí)歡迎大家提出建議和不同的看法风皿,見(jiàn)解河爹,分享可以讓我們共同進(jìn)步。