对于RunTime恐怕几乎每个作iOS的人都据说过,都用过吧,可是对于其具体实现好多人应该都不太清楚吧,今天我这分4部分,详细的讲解一下Runtime,让你们对Runtime有一个全局的了解git
咱们在研究OC对象的时候已经知道了,实力对象的isa
指向类对象,类对象的isa
指向元类对象。其实这样说仍是有一点不对的,应该说在arm64架构
以前,isa就是一个普通的指针,存储着Class
、 Meta-Class
对象的内存地址;可是从arm64
以后,对isa
进行了优化,变成了一个共用体(union)
结构,还使用位域
来存放跟多的信息。github
咱们在这里下载runtime源码,而后查找struct objc_object
里面的isa
,这里咱们只研究arm64架构isa
面试
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
复制代码
咱们发现isa
的结构是这种共用体(union)
结构,其实使用这种共用体是一种优化,isa
不在单独存放的是一个指针信息了,里面存放了更多的其余信息。算法
想要明白isa
变成共用体(union)
结构,是一种优化,咱们须要先了解一些概念数组
位运算的运算符有下面几个缓存
<<
>>
|
&
~
^
其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1与操做& 与操做&
:都是1则为1,一个0就是0。能够用来取出来特定的位。例如一个二进制0b 0000 0111
,咱们分别想取出第一位1
和第四位0
。安全
0000 0111 0000 0111
&0000 0001 &0000 1000
-------------- --------------
0000 0001 0000 0000
复制代码
咱们能够发现咱们使用按位与&的时候,咱们若是想取出哪一位,把改成设置为1,其余位设置为0就能够了。bash
介绍到了&
,我再来介绍一个概念,掩码:通常用来按位与(&)运算的
,具体有什么做用,咱们下面会进行讲解数据结构
或操做|多线程
或操做|
:一个是1,则为1,所有是0才为0。 例如一个二进制0b 0101 1010
。
0101 1010
| 0001 1100
--------------
0101 1110
复制代码
若是咱们想要某一位,就该该位或上一个0
左移:<<
二进制位所有左移若干位,左边的丢弃,右边补0
右移:>>
二进制右移若干位,正数左边补0,负数左边补1,右边丢弃。
例如 12>>2
0000 1100 = 12
0000 0011 = 3 (右移后)
特色:每右移一位,就除以一次2。a>>n 就是 a除以2的n次方
一般用bit来做数据传输的单位,由于物理层,数据链路层的传输对于用户是透明的,而这种通讯传输是基于二进制的传输。在应用层一般是用byte来做单位,表示文件的大小,在用户看来就是可见的数据大小
换算 1 Byte = 8 Bits 1 KB = 1024 Bytes 1 MB = 1024 KB 1 GB = 1024 MB 另外,Byte一般简写为B(大写),而bit一般简写为b(小写)。能够这么记忆,大写的为大单位,实际数值小,小写的为小单位,实际数值较大,1B=8b。
所谓”位域“是把一个字节中的二进位划分为几 个不一样的区域, 并说明每一个区域的位数。每一个域有一个域名,容许在程序中按域名进行操做。它其实是C语言提供的一种数据结构。
使用位域的好处是:
struct 位域结构名 { 位域列表 }; 其中位域列表的形式为: 类型说明符 位域名:位域长度;
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
} _tallRichHandsome;
复制代码
union中能够定义多个成员,union的大小由最大的成员的大小决定
;
union成员共享同一块大小的内存,一次只能使用其中的一个成员; 对union某一个成员赋值,会覆盖其余成员的值(但前提是成员所占字节数相同,当成员所占字节数不一样时只会覆盖相应字节上的值,好比对char成员赋值就不会把整个int成员覆盖掉,由于char只占一个字节,而int占四个字节); union量的存放顺序是全部成员都从低地址开始存放的。
例如咱们建立一个Person
类,里面有三个Bool
属性,tall
、rich
、handsome
。
@property (nonatomic,assign) BOOL tall;
@property (nonatomic,assign) BOOL rich;
@property (nonatomic,assign) BOOL handsome;
复制代码
咱们知道这三个属性占用了3个字节
。其实这个时候咱们能够考虑到使用位域
或者共用体
的概念,使用位(Bit)的0和1来表明这三个属性的YES NO
,那个三个属性就只是占用了2个字节
位域代码
@interface Person()
{
// 位域
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
} _tallRichHandsome;
}
@end
@implementation Person
- (void)setTall:(BOOL)tall
{
_tallRichHandsome.tall = tall;
}
- (BOOL)isTall
{
return !!_tallRichHandsome.tall;
}
- (void)setRich:(BOOL)rich
{
_tallRichHandsome.rich = rich;
}
- (BOOL)isRich
{
return !!_tallRichHandsome.rich;
}
- (void)setHandsome:(BOOL)handsome
{
_tallRichHandsome.handsome = handsome;
}
- (BOOL)isHandsome
{
return !!_tallRichHandsome.handsome;
}
复制代码
为何会出现!!
,咱们知道!(-1) == NO
,!
上一个存在的值是NO
,!!
两次那么只会出现YES 和 NO了。
共用体
其实咱们观察isa的类型,发现isa实际上是使用的共用体
,
#define TallMask (1<<0)
#define RichMask (1<<1)
#define HandsomeMask (1<<2)
@interface Person()
{
union {
int bits;
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
};
} _tallRichHandsome;
}
@end
@implementation Person
- (void)setTall:(BOOL)tall
{
if (tall) {
_tallRichHandsome.bits |= TallMask;
} else {
_tallRichHandsome.bits &= ~TallMask;
}
}
- (BOOL)isTall
{
return !!(_tallRichHandsome.bits & TallMask);
}
- (void)setRich:(BOOL)rich
{
if (rich) {
_tallRichHandsome.bits |= RichMask;
} else {
_tallRichHandsome.bits &= ~RichMask;
}
}
- (BOOL)isRich
{
return !!(_tallRichHandsome.bits & RichMask);
}
- (void)setHandsome:(BOOL)handsome
{
if (handsome) {
_tallRichHandsome.bits |= HandsomeMask;
} else {
_tallRichHandsome.bits &= ~HandsomeMask;
}
}
- (BOOL)isHandsome
{
return !!(_tallRichHandsome.bits & HandsomeMask);
}
复制代码
#define TallMask (1<<0)
这是掩码,为了方便阅读。
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
};
复制代码
其实也仅仅是方便阅读的做用,让咱们知道tall
、rich
、handsome
是在哪一位上,去掉并不影响代码。
其实咱们能够看到苹果官方文档上面有不少地方运用到了位运算
typedef NS_ENUM(NSInteger, LXDAuthorizationType)
{
LXDAuthorizationTypeNone = 0,
LXDAuthorizationTypePush = 1 << 0, ///< 推送受权
LXDAuthorizationTypeLocation = 1 << 1, ///< 定位受权
LXDAuthorizationTypeCamera = 1 << 2, ///< 相机受权
LXDAuthorizationTypePhoto = 1 << 3, ///< 相册受权
LXDAuthorizationTypeAudio = 1 << 4, ///< 麦克风受权
LXDAuthorizationTypeContacts = 1 << 5, ///< 通信录受权
};
复制代码
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
复制代码
太多了,我就不一一列举了。其实咱们在有些状况下也能够参考这样的设计。 例如
typedef enum {
OptionsOne = 1<<0, // 0b0001
OptionsTwo = 1<<1, // 0b0010
OptionsThree = 1<<2, // 0b0100
OptionsFour = 1<<3 // 0b1000
} Options
- (void)setOptions:(Options)options
{
if (options & OptionsOne) {
NSLog(@"包含了OptionsOne");
}
if (options & OptionsTwo) {
NSLog(@"包含了OptionsTwo");
}
if (options & OptionsThree) {
NSLog(@"包含了OptionsThree");
}
if (options & OptionsFour) {
NSLog(@"包含了OptionsFour");
}
}
调用上面方法
[self setOptions: OptionsOne | OptionsFour];
复制代码
最后咱们在看一下isa结构吧
nonpointer
:0,表明普通的指针,存储着Class、Meta-Class对象的内存地址;1,表明优化过,使用位域存储更多的信息has_assoc
:是否有设置过关联对象,若是没有,释放时会更快shiftcls
:存储着Class、Meta-Class对象的内存地址信息magic
:用于在调试时分辨对象是否未完成初始化weakly_referenced
:是否有被弱引用指向过,若是没有,释放时会更快deallocating
:对象是否正在释放extra_rc
:里面存储的值是引用计数器has_sidetable_rc
:引用计数器是否过大没法存储在isa中;若是为1,那么引用计数会存储在一个叫SideTable的类的属性中第三条解释不知道为啥违反政治安全问题了,不让写,只能截图了
咱们先来总体的看一下结构
isa指针
、superClass
、cache方法缓存
、bits具体的类信息
bits & FAST_DATA_MASK
指向一个新的结构体Class_rw_t
,里面包含着methods方法列表
、properties属性列表
、protocols协议列表
、class_ro_t类的初始化信息
等一些类信息Class_rw_t Class_rw_t
里面的methods方法列表
、properties属性列表
都是二维数组,是可读可写的,包含类的初始内容
,分类的内容
class_ro_t
class_ro_t
里面的baseMethodList,baseProtocols,Ivars,baseProperties是一维数组,是只读的,包含类的初始化内容
method_t
method_t
是对方法的封装
struct method_t{
SEL name;//函数名
const char *types;//编码(返回值类型,参数类型)
IMP imp;//指向函数的指针(函数地址)
}
复制代码
IMP
IMP表明函数的具体实现
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
复制代码
第一个参数是指向self的指针(若是是实例方法,则是类实例的内存地址;若是是类方法,则是指向元类的指针),第二个参数是方法选择器(selector)
SEL
SEL表明方法名,通常叫作选择器,底层结构跟char *
相似
@selector()
和sel_registerName()
得到sel_getName()
和NSStringFromSelector()
转成字符串typedef struct objc_selector *SEL
types
types包含了函数返回值,参数编码的字符串
结构为:返回值 参数1 参数2...参数N
iOS中提供了一个叫作@encode
的指令,能够将具体的类型表示成字符串编码
例如
// "i24@0:8i16f20"
// 0id 8SEL 16int 20float == 24
- (int)test:(int)age height:(float)height
复制代码
每个方法都有两个默认参数self
和_msg
咱们能够查到id
类型为@
,SEL
类型为:
i
返回值@
是id 类型的self
:
是SEL 类型的_msg
i
是Int age
f
是float height
其中加载的数字实际上是跟所占字节有关
24
总共占有多少字节@0
是id 类型的self
的起始位置为0:8
是由于id 类型的self
占字节为8,因此SEL 类型的_msg`的起始位置为8Class内部结构中有一个方法缓存cache_t
,用散列表(哈希表)来缓存曾经调用过的方法,能够提升方法的查找速度。
cache_t
结构体里面有三个元素
buckets
散列表,是一个数组,数组里面的每个元素就是一个bucket_t
,bucket_t
里面存放两个
_key
SEL做为key_imp
函数的内存地址_mask
散列表的长度
_occupied
已经缓存的方法数量
为何会用到方法缓存
这张图片是咱们方法产找路径,若是咱们的一个类有多个父类,须要调用父类方法,他的查找路径为
系统级方法
来讲,其实仍是比较消耗资源的,为了应对这个状况。出现了方法缓存
,调用过的方法,都放在缓存列表中,下次查找方法的时候,如今缓存中查找,若是缓存中查找不到,而后在执行上面的方法查找流程。散列表结构
散列表的结构大概就像上面那样,数组的下标是经过@selector(方法名)&_mask
来求得,具体每个数组的元素是一个结构体,里面包含两个元素_imp
和@selector(方法名)做为的key
咱们在上一篇文章中知道,一个值与&上一个_mask
,得出的结果必定小于等于_mask
值,而_mask
值为数组长度-1,因此任什么时候候,也不会越界。
其实这就是散列表的算法,也有一些其余的算法,取余
,一个值取余
和&
的效果是相同的。
可是这实际上是有几个疑虑的
_mask
是多少? - 初始_mask
我简单了尝试了一下,第一次可能给3_mask
值了怎么办 - 随着方法的增多,方法数量确定会超过_mask
,这个时候会清空缓存散列表,而后_mask
*2&_mask
的值相同了怎么办 - 若是两个值&_mask
的值相同时,第二个&
减一,知道找到空值,若是减到0尚未找到空位置,那就放在最大位置cach_t
的数组位置怎么处理
NULL
源码查看 咱们在objc-cache.mm
文件中查找bucket_t * cache_t::find(cache_key_t k, id receiver)
方法。
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m);
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}
复制代码
计算index值
mask_t begin = cache_hash(k, m);
复制代码
这个方式是计算下标的,咱们点击进入查看具体实现,就是@selector(方法名)&_mask
当两个值求的下标相同时
(i = cache_next(i, m)) != begin
复制代码
具体实现为
arm64
和x86
实现方法不同
这里有一个MJ老师
封装的可以查看对象各类属性的方法,想要使用的能够在这里查看
OC中的方法调用,其实都是转化为objc_msgSend
函数的调用,objc_msgSend
的执行流程能够分为3大阶段
消息发送流程是咱们平时最常用的流程,其余的像动态方法解析
和消息转发
实际上是补救措施。具体流程以下
receiver
是否为nil,若是为nil直接退出消息发送receiverClass
,首先在消息接受者receiverClass
的cache
中查找方法,若是找到方法,直接调用。若是找不到,往下进行receiverClass
的cache
中找到方法,则从receiverClass
的class_rw_t
中查找方法,若是找到方法,执行方法,并把该方法缓存到receiverClass
的cache
中;若是没有找到,往下进行receiverClass
中找到方法,则经过superClass指针
找到superClass
,也是如今缓存中查找,若是找到,执行方法,并把该方法缓存到receiverClass
的cache
中;若是没有找到,往下进行superClass
的cache
中找到方法,则从superClass
的class_rw_t
中查找方法,若是找到方法,执行方法,并把该方法缓存到receiverClass
的cache
中;若是没有找到,重复四、5步骤。若是找不到了superClass
了,往下进行superClass
也找不到该方法,则要转到动态方法解析
开发者能够实现如下方法,来动态添加方法实现
动态解析事后,会从新走“消息发送”的流程,从receiverClass的cache中查找方法这一步开始执行
咱们建立一个Person
类,而后在.h
文件中写一个- (void)test
,可是不写具体实现,而后调用。会打印出最多见的unrecognized selector sent to instance 0x100559b60
。
动态方法解析1
动态方法解析须要调用resolveInstanceMethod
或者resolveClassMethod
一个对应实例方法,一个对应类方法。咱们这里是实例方法使用resolveInstanceMethod
咱们看一下resolveInstanceMethod
的解释,在咱们须要执行动态方法解析
的时候咱们最好返回YES
。
- (void)other{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)) {
//获取其余方法
Method method = class_getInstanceMethod(self, @selector(other));
//动态添加test的方法
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
}
return [super resolveInstanceMethod:sel];
}
@end
复制代码
在class_addMethod
方法中咱们须要imp
,types
,可是OC并无提供相关属性,全部咱们能够调用相关方法来获取相关参数
动态方法解析2
这里咱们在随便验证一下method
的结构是否是这种
struct method_t {
SEL sel;
char *types;
IMP imp;
};
复制代码
咱们代码改为这样
struct method_t {
SEL sel;
char *types;
IMP imp;
};
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)) {
//获取其余方法
struct method_t *method = (struct method_t *)class_getInstanceMethod(self, @selector(other));
//动态添加test的方法
class_addMethod(self, sel, method->imp, method->types);
return YES;
}
return [super resolveInstanceMethod:sel];
}
复制代码
动态方法解析3
其实咱们还能够用C语言验证一下,提示:C语言中函数方法就是函数的地址
void c_other(id self, SEL _cmd)
{
NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)) {
class_addMethod(self, sel, (IMP)c_other, "v16@0:8");
return YES;
}
return [super resolveInstanceMethod:sel];
}
复制代码
若是方法一个方法在消息发送阶段
没有找到相关方法,也没有进行动态方法解析
,这个时候就会走到消息转发阶段了。
forwardingTargetForSelector
,返回值不为nil时,会调用objc_msgSend(返回值, SEL)
methodSignatureForSelector
,返回值不为nil,调用forwardInvocation:
方法;返回值为nil时,调用doesNotRecognizeSelector:
方法forwardingTargetForSelector
咱们建立一个命令行项目,建立两个类,person
和Student
,在person.h
里面写一个实例方法,可是不去实现相关方法。
@interface Person : NSObject
- (void)test;
@end
@interface Student : NSObject
- (void)test;
@end
#import "Student.h"
@implementation Student
- (void)test{
NSLog(@"%s",__func__);
}
@end
复制代码
调用的时候回报出咱们最多见的错误unrecognized selector sent to instance 0x100747a50
若是咱们在person
里面实现这个方法
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
return [[Student alloc]init];
}
return nil;
}
复制代码
调用forwardingTargetForSelector
,返回值不为nil时,会调用objc_msgSend(返回值, SEL)
,结果就是调用了objc_msgSend(Student,test)
methodSignatureForSelector(方法签名)
当forwardingTargetForSelector
返回值为nil,或者都没有调用该方法的时候,系统会调用methodSignatureForSelector
方法。调用methodSignatureForSelector
,返回值不为nil,调用forwardInvocation:
方法;返回值为nil时,调用doesNotRecognizeSelector:
方法
对于方法签名的生成方式
[NSMethodSignature signatureWithObjCTypes:"i@:i"]
[[[Student alloc]init] methodSignatureForSelector:aSelector];
实现方法签名之后咱们还要实现forwardInvocation
方法,当调用person
的test
的方法的时候,就会走到这个方法中
NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
咱们也能够先执行NSLog(@"========");
在执行Student的test方法
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"========");
anInvocation.target = [[Student alloc]init];
[anInvocation invoke];
// [anInvocation invokeWithTarget:[[Student alloc] init]];
}
复制代码
其中这两个方法是同样的 [anInvocation invokeWithTarget:[[Student alloc] init]];
anInvocation.target = [[Student alloc]init];
[anInvocation invoke];
复制代码
其实这个方法仍是比较有用的,像网上一些对bug处理都会用到这个方法
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
动态建立一个类(参数:父类,类名,额外的内存空间)void objc_registerClassPair(Class cls)
注册一个类(要在类注册以前添加成员变量)void objc_disposeClassPair(Class cls)
销毁一个类Class object_getClass(id obj)
获取isa指向的ClassClass object_setClass(id obj, Class cls)
设置isa指向的ClassBOOL object_isClass(id obj)
判断一个OC对象是否为ClassBOOL class_isMetaClass(Class cls)
判断一个Class是否为元类Class class_getSuperclass(Class cls)
获取父类我在方法缓存讲过,在建立一个实例对象之后,里面的成员变量就固定了,不能在修改了。所以咱们在用objc_registerClassPair
注册类的时候,咱们必须把成员变量写在注册以前。 简单使用,由于这里面的都是runtime底层方法写的,全部点语法和set方法都不可使用,若是想要遍历里面的属性和方法仍是须要使用runtime
提供的方法
建立类
// 建立类
Class newClass = objc_allocateClassPair([NSObject class], "MJDog", 0);
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_weight", 4, 1, @encode(int));
//注册类
objc_registerClassPair(newClass);
// 成员变量的数量
unsigned int count;
Ivar *ivars = class_copyIvarList(newClass, &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[i];
NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);
// 在不须要这个类时释放
objc_disposeClassPair(newClass);
复制代码
设置isa指向的Class
Person *p = [[Person alloc]init];
object_setClass(p, [Cat class]);
NSLog(@"%@",p);
复制代码
Ivar class_getInstanceVariable(Class cls, const char *name)
获取一个实例变量信息Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
拷贝实例变量列表(最后须要调用free释放)void object_setIvar(id obj, Ivar ivar, id value)
设置成员变量的值id object_getIvar(id obj, Ivar ivar)
获取成员变量的值BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
动态添加成员变量(已经注册的类是不能动态添加成员变量的)const char *ivar_getName(Ivar v), const char *ivar_getTypeEncoding(Ivar v)
获取成员变量的相关信息最经常使用的方法就是获取类的成员变量
unsigned int count;
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[i];
NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);
复制代码
经常使用的方案
一、objc_property_t class_getProperty(Class cls, const char *name)
获取一个属性
二、objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
拷贝属性列表(最后须要调用free释放)
三、BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
动态添加属性
四、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)
获取属性的一些信息
一、得到一个实例方法、类方法 - Method class_getInstanceMethod(Class cls, SEL name)
- Method class_getClassMethod(Class cls, SEL name)
二、方法实现相关操做 - IMP class_getMethodImplementation(Class cls, SEL name)
- IMP method_setImplementation(Method m, IMP imp)
- void method_exchangeImplementations(Method m1, Method m2)
三、拷贝方法列表(最后须要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
四、动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
五、动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
六、选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)
七、用block做为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
最多见的就是动态方法交换
Method runMethod = class_getInstanceMethod([Person class], @selector(run));
Method testMethod = class_getInstanceMethod([Person class], @selector(test));
method_exchangeImplementations(runMethod, testMethod)
复制代码
还有一个方法替换
MJPerson *person = [[Person alloc] init];
// class_replaceMethod([Person class], @selector(run), (IMP)myrun, "v");
class_replaceMethod([Person class], @selector(run), imp_implementationWithBlock(^{
NSLog(@"123123");
}), "v");
[person run];
复制代码
咱们常常会看一些面试题,可是好多面试题咱们都是知其然不知其因此然,你若是认真的看了我上面总结的几十篇文章,那么你也会知其因此然。