OC-Runtime-常用API
一. 類相關(guān)API
//動(dòng)態(tài)創(chuàng)建一個(gè)類(參數(shù):父類蒋失,類名,額外的內(nèi)存空間)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
//注冊(cè)一個(gè)類(要在類注冊(cè)之前添加成員變量)
void objc_registerClassPair(Class cls)
//銷毀一個(gè)類
void objc_disposeClassPair(Class cls)
//獲取對(duì)象的isa指向的Class
Class object_getClass(id obj)
//設(shè)置對(duì)象的isa指向的Class
Class object_setClass(id obj, Class cls)
//判斷一個(gè)對(duì)象是否為Class
BOOL object_isClass(id obj)
//判斷一個(gè)Class是否為元類
BOOL class_isMetaClass(Class cls)
//獲取父類
Class class_getSuperclass(Class cls)
-
Class object_getClass(id _Nullable obj)
獲取isa
指向的Class
***********************??MJPerson.h ??**************************
#import <Foundation/Foundation.h>
@interface MJPerson : NSObject
@property (assign, nonatomic) int ID;
@property (assign, nonatomic) int weight;
@property (assign, nonatomic) int age;
@property (copy, nonatomic) NSString *name;
- (void)run;
@end
***********************??MJPerson.m ??**************************
#import "MJPerson.h"
@implementation MJPerson
- (void)print
- (void)run
{
NSLog(@"%s", __func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *person = [[MJPerson alloc] init];
class_isMetaClass(object_getClass([MJPerson class]));
Class cls = object_getClass(person);//實(shí)例對(duì)象的isa指向類對(duì)象
Class metaClass = object_getClass([MJPerson class]);//類對(duì)象的isa指向元類對(duì)象
NSLog(@"是元類對(duì)象 %d", class_isMetaClass(metaClass));
NSLog(@"%d %d %d",
object_isClass(person),
object_isClass([MJPerson class]),
object_isClass(object_getClass([MJPerson class]))
);
}
return 0;
}
RUN????????????
2021-05-10 15:26:50.390907+0800 Interview02-runtime應(yīng)用[3278:157126] 是元類對(duì)象 1 2021-05-10 15:26:50.391387+0800 Interview02-runtime應(yīng)用[3278:157126] 0 1 1
Class object_setClass(id _Nullable obj, Class _Nonnull cls)
設(shè)置 isa的指向的Class
***********************?? MJCar.h ??**************************
@interface MJCar : NSObject
- (void)run;
@end
***********************?? MJCar.m ??**************************
@implementation MJCar
- (void)run
{
NSLog(@"%s", __func__);
}
@end
***********************?? main.m ??**************************
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *person = [[MJPerson alloc] init];
[person run];
object_setClass(person, [MJCar class]);
[person run];
NSLog(@"%d %d %d",
object_isClass(person),
object_isClass([MJPerson class]),
object_isClass(object_getClass([MJPerson class]))
);
}
return 0;
}
RUN????????????
2021-05-10 15:29:57.963729+0800 Interview02-runtime應(yīng)用[3304:159036] -[MJPerson run] 2021-05-10 15:29:57.964308+0800 Interview02-runtime應(yīng)用[3304:159036] -[MJCar run] 2021-05-10 15:29:57.964363+0800 Interview02-runtime應(yīng)用[3304:159036] 0 1 1
- objc_allocateClassPair
Class objc_allocateClassPair(Class superclass, const char * name, size_t extraBytes)
動(dòng)態(tài)創(chuàng)建一個(gè)類,(參數(shù):父類,類名,額外的存儲(chǔ)空間)
objc_registerClassPair(Class cls)
注冊(cè)一個(gè)類 (要在類注冊(cè)之前添加成員變量)
void run(id self, SEL _cmd)
{
NSLog(@"_____ %@ - %@", self, NSStringFromSelector(_cmd));
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 創(chuàng)建類柴底,傳入父類和類名
Class newClass = objc_allocateClassPair([NSObject class], "MJDog", 0);
// 注冊(cè)類之前添加成員變量
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_weight", 4, 1, @encode(int));
class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
// 注冊(cè)類
objc_registerClassPair(newClass);
id dog = [[newClass alloc] init];
[dog setValue:@10 forKey:@"_age"];
[dog setValue:@20 forKey:@"_weight"];
[dog run];
NSLog(@"%@ %@", [dog valueForKey:@"_age"], [dog valueForKey:@"_weight"]);
MJPerson *person = [[MJPerson alloc] init];
//修改person對(duì)象isa指向
object_setClass(person, newClass);
[person run];
// 在不需要這個(gè)類時(shí)釋放
// objc_disposeClassPair(newClass);
}
return 0;
}
RUN>????????????
2021-05-10 15:36:09.336005+0800 Interview02-runtime應(yīng)用[3430:165339] _____ <MJDog: 0x10067f380> - run 2021-05-10 15:36:09.336861+0800 Interview02-runtime應(yīng)用[3430:165339] 10 20 2021-05-10 15:36:09.337011+0800 Interview02-runtime應(yīng)用[3430:165339] _____ <MJDog: 0x100686fd0> - run
在程序運(yùn)行的時(shí)候,動(dòng)態(tài)添加一個(gè)類秸歧,并且添加成員變量泌射、方法,最后使用類宰翅。
- 一定要在注冊(cè)類之前添加成員變量噩死,因?yàn)槌蓡T變量是在_r_o_t表里面颤难,是只讀的,所以要在類的結(jié)構(gòu)確定之前添加成員變量已维。
- 不能使用class_addIvar給已經(jīng)創(chuàng)建的類添加成員變量行嗤,因?yàn)橐呀?jīng)創(chuàng)建的類的結(jié)構(gòu)在代碼寫完就已經(jīng)確定好了,程序運(yùn)行中就不能給已經(jīng)創(chuàng)建的類添加成員變量了垛耳。
- 方法可以在注冊(cè)類之后添加栅屏,因?yàn)榉椒ㄊ窃赺r_w_t表里面飘千,是可讀可寫的。
二. 成員變量相關(guān)API
//獲取類中指定名稱實(shí)例成員變量的信息
Ivar class_getInstanceVariable(Class cls, const char *name)
//獲取成員變量的相關(guān)信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
//設(shè)置和獲取成員變量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
//拷貝實(shí)例變量列表(最后需要調(diào)用free釋放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
//動(dòng)態(tài)添加成員變量(已經(jīng)注冊(cè)的類是不能動(dòng)態(tài)添加成員變量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
-
Ivar class_getInstanceVariable
獲取一個(gè)實(shí)例變量的信息 -
object_setIvar(id obj, Ivar ivar, id value)
設(shè)置實(shí)例變量的值
int main(int argc, const char * argv[]) {
@autoreleasepool {
//獲取類中指定名稱實(shí)例成員變量的信息
//傳入的是一個(gè)類對(duì)象栈雳,所以只能獲取成員變量的信息护奈,并不能獲取成員變量的值
Ivar ageIvar = class_getInstanceVariable([MJPerson class], "_age");
Ivar nameIvar = class_getInstanceVariable([MJPerson class], "_name");
NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar));
//打印:_age i i代表字符編碼int
MJPerson *person = [[MJPerson alloc] init];
//設(shè)置成員變量的值
//傳入的是一個(gè)實(shí)例對(duì)象哥纫,所以可以設(shè)置成員變量的值
object_setIvar(person, nameIvar, @"123");
object_setIvar(person, ageIvar, (__bridge id)(void *)10);
//獲取成員變量的值
id name = object_getIvar(person, nameIvar);
NSLog(@"%@ %d", name, person.age);
//打用蛊臁:123 10
}
return 0;
}
RUN> ??????????????????
2021-05-10 15:40:12.967391+0800 Interview02-runtime應(yīng)用[3454:168018] _age i 2021-05-10 15:40:12.967815+0800 Interview02-runtime應(yīng)用[3454:168018] 123 10
object_setIvar(person, ageIvar, (__bridge id)(void *)10);
上面runtimeAPI內(nèi)部沒(méi)做轉(zhuǎn)換,所以需要傳什么值就傳什么值蛀骇,但是要做一些數(shù)據(jù)類型轉(zhuǎn)換(先轉(zhuǎn)成指針類型厌秒,再轉(zhuǎn)成id類型)。如果是KVC的value值擅憔,可以傳NSNumber類型的值鸵闪,因?yàn)镵VC內(nèi)部會(huì)做轉(zhuǎn)換:[@10 integerValue]。
-
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
動(dòng)態(tài)添加成員變量 (已經(jīng)注冊(cè)的類是不能添加成員變量的) -
const char *ivar_getName(Ivar v)
獲取成員變量 name -
const char * ivar_getTypeEncoding(Ivar v)
獲取成員變量字符串編碼 -
Ivar * class_copyIvarList(Class cls, unsigned int * outCount)
拷貝實(shí)例變量列表,最后需要調(diào)用free
釋放.
用途一: 獲取系統(tǒng)類私有的成員變量 (這種方式在 iOS13 后 已經(jīng)被禁用了,iOS13 后系統(tǒng)禁止訪問(wèn)一些私有的成員變量
)
unsigned int count;
Ivar *ivar = class_copyIvarList([UITextField class], &count);
for (int i = 0; i < count; i ++) {
Ivar iva = ivar[I];
NSLog(@"%s",ivar_getName(iva));
}
self.nameTF.placeholder = @"請(qǐng)輸入姓名";
[self.nameTF setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
free(ivar);
用途二:字典轉(zhuǎn)模型
+ (instancetype)json2Model:(NSDictionary *)json{
id obj = [[self alloc]init];
unsigned int count;
Ivar *ivars = class_copyIvarList([self class], &count);
//遍歷所有的成員變量
for (int i = 0; i < count; i ++) {
Ivar iva = ivars[I];
NSMutableString *ivarStr = [NSMutableString stringWithUTF8String:ivar_getName(iva)];
//去掉成員變量前面的 _
[ivarStr deleteCharactersInRange:NSMakeRange(0, 1)];
[obj setValue:json[ivarStr] forKey:ivarStr];
}
return obj;
}
這樣寫會(huì)有很多問(wèn)題,這只是個(gè)思路,僅供參考
用途三:歸檔,解檔
- (instancetype)initWithCoder:(NSCoder *)coder{
if (self = [super init]) {
unsigned int count;
Ivar *ivars = class_copyIvarList([self class], &count);
//遍歷所有的成員變量
for (int i = 0; i < count; i ++) {
Ivar iva = ivars[I];
NSMutableString *ivarStr = [NSMutableString stringWithUTF8String:ivar_getName(iva)];
//去掉成員變量前面的 _
[ivarStr deleteCharactersInRange:NSMakeRange(0, 1)];
//從文件中取出值
id value = [coder decodeObjectForKey:ivarStr];
//賦值到對(duì)象中
[self setValue:value forKey:ivarStr];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder{
unsigned int count;
Ivar *ivars = class_copyIvarList([self class], &count);
//遍歷所有的成員變量
for (int i = 0; i < count; i ++) {
Ivar iva = ivars[I];
NSMutableString *ivarStr = [NSMutableString stringWithUTF8String:ivar_getName(iva)];
//去掉成員變量前面的 _
[ivarStr deleteCharactersInRange:NSMakeRange(0, 1)];
//從對(duì)象中取出對(duì)應(yīng)的值
id value = [self valueForKey:ivarStr];
//歸檔到文件中
[coder encodeObject:value forKey:ivarStr];
}
}
三. 屬性相關(guān)API
//獲取一個(gè)屬性
objc_property_t class_getProperty(Class cls, const char *name)
//拷貝屬性列表(最后需要調(diào)用free釋放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
//動(dòng)態(tài)添加屬性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
//動(dòng)態(tài)替換屬性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
//獲取屬性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
四. 方法相關(guān)API
//獲取一個(gè)實(shí)例方法暑诸、類方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)
//根據(jù)class和方法名獲取方法的imp
IMP class_getMethodImplementation(Class cls, SEL name)
//設(shè)置方法的imp
IMP method_setImplementation(Method m, IMP imp)
//交換方法的imp
void method_exchangeImplementations(Method m1, Method m2)
//獲取方法名
SEL method_getName(Method m)
//獲取imp
IMP method_getImplementation(Method m)
//獲取方法返回值類型岛马、參數(shù)類型的編碼
const char *method_getTypeEncoding(Method m)
//獲取參數(shù)個(gè)數(shù)
unsigned int method_getNumberOfArguments(Method m)
//獲取返回值類型
char *method_copyReturnType(Method m)
//根據(jù)index獲取參數(shù)
char *method_copyArgumentType(Method m, unsigned int index)
//拷貝方法列表(最后需要調(diào)用free釋放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
//動(dòng)態(tài)添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
//動(dòng)態(tài)替換方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
//根據(jù)SEL獲取名字
const char *sel_getName(SEL sel)
//根據(jù)字符串包裝成一個(gè)SEL,和@selector("方法名字")方法等效
SEL sel_registerName(const char *str)
//根據(jù)block返回一個(gè)imp
IMP imp_implementationWithBlock(id block)
//根據(jù)imp返回一個(gè)block
id imp_getBlock(IMP anImp)
//移除imp對(duì)應(yīng)的block
BOOL imp_removeBlock(IMP anImp)
void myrun()
{
NSLog(@"---myrun");
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *person = [[MJPerson alloc] init];
class_replaceMethod([MJPerson class], @selector(run), (IMP)myrun, "v");
[person run];
}
return 0;
}
RUN> ??????????????
2021-05-10 15:47:37.466763+0800 Interview04-方法[3531:172517] ---myrun
- 將block當(dāng)做方法實(shí)現(xiàn)
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *person = [[MJPerson alloc] init];
class_replaceMethod([MJPerson class], @selector(run), imp_implementationWithBlock(^{
NSLog(@"123123");
}), "v");
[person run];
}
return 0;
}
RUN>????????????
2021-05-10 15:49:19.473743+0800 Interview04-方法[3570:174628] 123123
- 交換方法實(shí)現(xiàn)
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *person = [[MJPerson alloc] init];
Method runMethod = class_getInstanceMethod([MJPerson class], @selector(run));
Method testMethod = class_getInstanceMethod([MJPerson class], @selector(test));
method_exchangeImplementations(runMethod, testMethod);
[person run];
}
return 0;
}
RUN> ????????????
2021-05-10 15:51:18.409680+0800 Interview04-方法[3593:175998] -[MJPerson test]
交換方法實(shí)現(xiàn)的使用
交換方法實(shí)現(xiàn)在開(kāi)發(fā)中經(jīng)常使用屠列,但是實(shí)際上我們使用最多的是交換系統(tǒng)或者第三方框架的方法。
攔截所有按鈕的點(diǎn)擊事件:
UIButton繼承于UIControl伞矩,UIControl有一個(gè)sendAction:to:forEvent:方法笛洛,每當(dāng)觸發(fā)一個(gè)事件就會(huì)調(diào)用這個(gè)方法,所以我們可以給UIControl添加分類乃坤,在分類中交換這個(gè)方法的實(shí)現(xiàn):
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// hook:鉤子函數(shù)
Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method method2 = class_getInstanceMethod(self, @selector(mj_sendAction:to:forEvent:));
method_exchangeImplementations(method1, method2);
});
}
- (void)mj_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
NSLog(@"%@-%@-%@", self, target, NSStringFromSelector(action));
// 調(diào)用系統(tǒng)原來(lái)的實(shí)現(xiàn)
// 因?yàn)榉椒ㄒ呀?jīng)交換了苛让,所以其實(shí)是調(diào)用sendAction:to:forEvent:
[self mj_sendAction:action to:target forEvent:event];
//攔截按鈕事件
if ([self isKindOfClass:[UIButton class]]) {
// 攔截了所有按鈕的事件
}
}
上面交換方法也叫鉤子函數(shù),利用鉤子函數(shù)就實(shí)現(xiàn)了攔截所有UIButton的點(diǎn)擊事件湿诊。
問(wèn)題1:為什么上面要加個(gè)dispatch_once狱杰?
按理說(shuō)load方法只會(huì)調(diào)用一次,萬(wàn)一別人主動(dòng)調(diào)用了load方法那不就調(diào)用兩次了嗎厅须,這樣方法就交換兩次了和沒(méi)交換一樣仿畸,所以加個(gè)dispatch_once。
問(wèn)題2:交換方法實(shí)現(xiàn)的原理是什么朗和?
method_exchangeImplementations方法是傳入兩個(gè)Method错沽,以前我們講過(guò)Method的內(nèi)部結(jié)構(gòu),其實(shí)交換方法實(shí)現(xiàn)就是把Method里面的IMP交換了眶拉,如下圖:
上面說(shuō)的交換方法實(shí)現(xiàn)千埃,交換的是方法列表(methods數(shù)組)里面的method_t(也就是Method),如果這個(gè)方法有緩存忆植,怎么辦放可?
問(wèn)題3:如果這個(gè)方法有緩存谒臼,怎么辦?
其實(shí)耀里,調(diào)用method_exchangeImplementations函數(shù)會(huì)清空緩存蜈缤,這樣就保證了交換方法之后調(diào)用方法不會(huì)出錯(cuò)。
可以在objc4里面搜索到源碼:
void method_exchangeImplementations(Method m1, Method m2)
{
if (!m1 || !m2) return;
rwlock_writer_t lock(runtimeLock);
IMP m1_imp = m1->imp;
m1->imp = m2->imp;
m2->imp = m1_imp;
// RR/AWZ updates are slow because class is unknown
// Cache updates are slow because class is unknown
// fixme build list of classes whose Methods are known externally?
flushCaches(nil);//交換IMP之后就會(huì)清空緩存备韧。
updateCustomRR_AWZ(nil, m1);
updateCustomRR_AWZ(nil, m2);
}
上面源碼很簡(jiǎn)單劫樟,可以發(fā)現(xiàn),交換IMP之后就會(huì)清空緩存织堂。
特別備注
本系列文章總結(jié)自MJ老師在騰訊課堂iOS底層原理班(下)/OC對(duì)象/關(guān)聯(lián)對(duì)象/多線程/內(nèi)存管理/性能優(yōu)化叠艳,相關(guān)圖片素材均取自課程中的課件。如有侵權(quán)易阳,請(qǐng)聯(lián)系我刪除附较,謝謝!