内容简介:对于RunTime恐怕几乎每一个做iOS的人都听说过,都用过吧,但是对于其具体实现好多人应该都不太清楚吧,今天我这分4部分,详细的讲解一下Runtime,让大家对Runtime有一个全局的了解我们在研究OC对象的时候已经知道了,实力对象的我们在这里下载runtime源码,然后查找
对于RunTime恐怕几乎每一个做iOS的人都听说过,都用过吧,但是对于其具体实现好多人应该都不太清楚吧,今天我这分4部分,详细的讲解一下Runtime,让大家对Runtime有一个全局的了解
- 1、isa解析
- 2、方法缓存
- 3、objc_msgSend执行流程
- 4、RunTime的相关API
isa指针
我们在研究OC对象的时候已经知道了,实力对象的 isa
指向类对象,类对象的 isa
指向元类对象。其实这样说还是有一点不对的,应该说在 arm64架构
之前,isa就是一个普通的指针,存储着 Class
、 Meta-Class
对象的内存地址;但是从 arm64
之后,对 isa
进行了优化,变成了一个 共用体(union)
结构,还使用 位域
来存放跟多的信息。
我们在这里下载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、位运算
- 2、字节和位
- 3、位域
- 4、共用体
位运算
位运算的运算符有下面几个
<< >> | & ~ ^
与操作& 与操作&
:都是1则为1,一个0就是0。可以用来取出来特定的位。例如一个二进制 0b 0000 0111
,我们分别想取出第一位 1
和第四位 0
。
0000 0111 0000 0111 &0000 0001 &0000 1000 -------------- -------------- 0000 0001 0000 0000 复制代码
我们可以发现我们使用按位与&的时候,我们如果想取出哪一位,把改为设置为1,其他位设置为0就可以了。
介绍到了 &
,我再来介绍一个概念, 掩码:一般用来按位与(&)运算的
,具体有什么作用,我们下面会进行讲解
或操作|
或操作|
:一个是1,则为1,全部是0才为0。 例如一个二进制 0b 0101 1010
。
0101 1010 | 0001 1100 -------------- 0101 1110 复制代码
如果我们想要某一位,就该该位或上一个0
左移: <<
二进制位全部左移若干位,左边的丢弃,右边补0
- 1、1<<0 1左移0位,0b0000 0001
- 2、1<<1 1左移1位,0b0000 0010
- 3、1<<2 1左移2位,0b0000 0100
- 4、1<<3 1左移3位,0b0000 1000
右移: >>
二进制右移若干位,正数左边补0,负数左边补1,右边丢弃。
例如 12>>2 0000 1100 = 12 0000 0011 = 2 (右移后)
特点:每右移一位,就除以一次2。a>>n 就是 a除以2的n次方
字节和位
- Bit意为“位”,是计算机运算的基础,属于二进制的范畴;
- Byte意为“字节”,是计算机文件大小的基本计算单位;
通常用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语言 提供的一种数据结构。
使用位域的好处是:
- 1.有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。这样节省存储空间,而且处理简便。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。
- 2.可以很方便的利用位域把一个变量给按位分解。比如只需要4个大小在0到3的随即数,就可以只rand()一次,然后每个位域取2个二进制位即可,省时省空间
struct 位域结构名 { 位域列表 }; 其中位域列表的形式为: 类型说明符 位域名:位域长度;
struct { char tall : 1; char rich : 1; char handsome : 1; } _tallRichHandsome; 复制代码
4、共用体
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; 复制代码
我们知道这三个属性占用了 6个字节
。其实这个时候我们可以考虑到使用 位域
或者 共用体
的概念,使用 位(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 has_assoc shiftcls magic weakly_referenced deallocating extra_rc has_sidetable_rc
第三条解释不知道为啥违反政治安全问题了,不让写,只能截图了
方法缓存
我们先来整体的看一下结构
- 1、class类中只要有
isa指针
、superClass
、cache方法缓存
、bits具体的类信息
- 2、
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
类型为 :
- 1、第一个参数
i
返回值 - 2、第二个参数
@
是id 类型的self
- 3、第三个参数
:
是SEL 类型的_msg
- 4、第四个参数
i
是Int age
- 5、第五个参数
f
是float height
其中加载的数字其实是跟所占字节有关
- 1、
24
总共占有多少字节 - 2、
@0
是id 类型的self
的起始位置为0 - 3、
:8
是因为id 类型的self
占字节为8,所以SEL 类型的_msg`的起始位置为8
方法缓存
Class内部结构中有一个方法缓存 cache_t
,用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度。
cache_t
结构体里面有三个元素
-
buckets
散列表,是一个数组,数组里面的每一个元素就是一个bucket_t
,bucket_t
里面存放两个_key _imp
-
_mask
散列表的长度 -
_occupied
已经缓存的方法数量
为什么会用到方法缓存
这张图片是我们方法产找路径,如果我们的一个类有多个父类,需要调用父类方法,他的查找路径为
- 1、先遍历自己所有的方法
- 2、如果在自己类中找不到方法,则遍历父类所有方法,没有查找到调用方法之前,一直重复该动作 如果每一次方法调用都是走这样的步骤,对于
系统级方法
来说,其实还是比较消耗资源的,为了应对这个情况。出现了方法缓存
,调用过的方法,都放在缓存列表中,下次查找方法的时候,现在缓存中查找,如果缓存中查找不到,然后在执行上面的方法查找流程。
散列表结构
散列表的结构大概就像上面那样,数组的下标是通过 @selector(方法名)&_mask
来求得,具体每一个数组的元素是一个结构体,里面包含两个元素 _imp
和 @selector(方法名)作为的key
我们在上一篇文章中知道,一个值 与&上一个_mask
,得出的结果一定小于等于 _mask
值,而 _mask
值为数组长度-1,所以任何时候,也不会越界。
其实这就是散列表的算法,也有一些其他的算法, 取余
,一个值 取余
和 &
的效果是相同的。
但是这其实是有几个疑虑的
- 1、初始
_mask
是多少? - 初始_mask
我简单了尝试了一下,第一次可能给3 - 2、随着方法的增加,方法数量超过
_mask
值了怎么办 - 随着方法的增多,方法数量肯定会超过_mask
,这个时候会清空缓存散列表,然后_mask
*2 - 3、如果两个值
&_mask
的值相同了怎么办 - 如果两个值&_mask
的值相同时,第二个&
减一,知道找到空值,如果减到0还没有找到空位置,那就放在最大位置 - 4、在没有存放
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老师
封装的能够查看对象各种属性的方法,想要使用的可以在 这里 查看
objc_msgSend执行流程
OC中的方法调用,其实都是转化为 objc_msgSend
函数的调用, objc_msgSend
的执行流程可以分为3大阶段
- 1、消息发送
- 2、动态方法解析
- 3、消息转发
消息发送
消息发送流程是我们平时最经常使用的流程,其他的像 动态方法解析
和 消息转发
其实是补救措施。具体流程如下
- 1、首先判断消息接受者
receiver
是否为nil,如果为nil直接退出消息发送 - 2、如果存在消息接受者
receiverClass
,首先在消息接受者receiverClass
的cache
中查找方法,如果找到方法,直接调用。如果找不到,往下进行 - 3、没有在消息接受者
receiverClass
的cache
中找到方法,则从receiverClass
的class_rw_t
中查找方法,如果找到方法,执行方法,并把该方法缓存到receiverClass
的cache
中;如果没有找到,往下进行 - 4、没有在
receiverClass
中找到方法,则通过superClass指针
找到superClass
,也是现在缓存中查找,如果找到,执行方法,并把该方法缓存到receiverClass
的cache
中;如果没有找到,往下进行 - 5、没有在消息接受者
superClass
的cache
中找到方法,则从superClass
的class_rw_t
中查找方法,如果找到方法,执行方法,并把该方法缓存到receiverClass
的cache
中;如果没有找到,重复4、5步骤。如果找不到了superClass
了,往下进行 - 6、如果在最底层的
superClass
也找不到该方法,则要转到动态方法解析
动态方法解析
-
开发者可以实现以下方法,来动态添加方法实现
- +resolveInstanceMethod:
- +resolveClassMethod:
-
动态解析过后,会重新走“消息发送”的流程,从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:
方法 - 开发者可以在forwardInvocation:方法中自定义任何逻辑
- 以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)
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封装了一个方法调用,包括:方法调用者、方法名、方法参数
- anInvocation.target 方法调用者
- anInvocation.selector 方法名
- [anInvocation getArgument:NULL atIndex:0]
我们也可以先执行 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处理都会用到这个方法
RunTime的相关API
类方法
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) Class object_setClass(id obj, Class cls) BOOL object_isClass(id obj) BOOL class_isMetaClass(Class cls) 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) 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); 复制代码
常用的方案
- 1、JSON转Model
- 2、常看写控件都有哪些元素,然后进行修改
属性
-
1、
objc_property_t class_getProperty(Class cls, const char *name)
获取一个属性 -
2、
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
拷贝属性列表(最后需要调用free释放) -
3、
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
动态添加属性 -
4、
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
动态替换属性 -
5、
const char *property_getName(objc_property_t property)
获取属性的一些信息 -
6、
const char *property_getAttributes(objc_property_t property)
获取属性的一些信息方法
-
1、获得一个实例方法、类方法 -
Method class_getInstanceMethod(Class cls, SEL name)
-Method class_getClassMethod(Class cls, SEL name)
-
2、方法实现相关操作 -
IMP class_getMethodImplementation(Class cls, SEL name)
-IMP method_setImplementation(Method m, IMP imp)
-void method_exchangeImplementations(Method m1, Method m2)
-
3、拷贝方法列表(最后需要调用free释放)
-
Method *class_copyMethodList(Class cls, unsigned int *outCount)
-
-
4、动态添加方法
-
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
-
-
5、动态替换方法
-
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
-
-
6、选择器相关
const char *sel_getName(SEL sel) SEL sel_registerName(const char *str)
-
7、用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]; 复制代码
我们经常会看一些面试题,但是好多面试题我们都是知其然不知其所以然,你如果认真的看了我上面总结的几十篇文章,那么你也会知其所以然。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Beginning PHP and MySQL 5
W. Jason Gilmore / Apress / 2006-01-23 / USD 44.99
Beginning PHP and MySQL 5: From Novice to Professional, Second Edition offers comprehensive information about two of the most prominent open source technologies on the planet: the PHP scripting langua......一起来看看 《Beginning PHP and MySQL 5》 这本书的介绍吧!