目录
runtime介绍
一、Objective-C是一门动态性比较强的编程语言,跟C/C++等语言有着很大的不同。
编程语言都要经过 编码->预编译->编译->链接->运行,几个阶段,C/C++在运行中的实际结果与编译阶段基本相同,而OC可以在运行过程中做许多事情。
OC的动态性表现在如下几个方面:
1、编码阶段调用test方法,实际运行时可能调用的test2方法或者其他类对象的方法。(方法交换)。
2、运行过程中创建新的类、类对象、类方法。
3、查看私有变量、私有方法。(比如系统类、SDK内的类)。
举个例子:
void test(void){
printf("test");
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
//C语言在运行时调用的是test方法。
test();
//运行时真正调用的有可能不是person对象的test方法。
Person * person = [[Person alloc] init];
[perosn test];
}
return 0;
}
二、Objective-C的动态性是由Runtime API来支撑的,Runtime API提供的接口基本都是C语言的,源码有C/C++/汇编语言实现。
//使用runtime提供的接收是需要引入以下头文件
#import <objc/runtime.h>
isa的数据结构
在arm64架构之前,isa就是一个普通的指针,指向Class对象或者Meta-Class对象。
从arm64架构之后,isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息。此时要想得到Class或Meta-Class的地址需要&ISA_MASK。
举个位域使用的例子:
Person.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
-(BOOL)tall;
-(BOOL)rich;
-(BOOL)handsome;
-(void)setTall:(BOOL)tall;
-(void)setRich:(BOOL)rich;
-(void)setHandsome:(BOOL)handsome;
@end
NS_ASSUME_NONNULL_END
Person.m
#import "Person.h"
struct TallRichHandsome{
BOOL isTall:1;
BOOL isRich:1;
BOOL isHandsome:1;
};
@interface Person(){
struct TallRichHandsome _tallRichHandsome;
}
@implementation Person
-(BOOL)tall{
//二进制位转为bool值时最好用两次!!转下,一个二进制位转为字节时有可能会出现问题
//比如0b1,转为一个字节时为 0b11111111,
return !!_tallRichHandsome.isTall;
}
-(BOOL)rich{
return !!_tallRichHandsome.isRich;
}
-(BOOL)handsome{
return !!_tallRichHandsome.isHandsome;
}
-(void)setTall:(BOOL)tall{
_tallRichHandsome.isTall = tall;
}
-(void)setRich:(BOOL)rich{
_tallRichHandsome.isRich = rich;
}
-(void)setHandsome:(BOOL)handsome{
_tallRichHandsome.isHandsome = handsome;
}
@end
举个共用体使用的例子:
Person.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
-(BOOL)tall;
-(BOOL)rich;
-(BOOL)handsome;
-(void)setTall:(BOOL)tall;
-(void)setRich:(BOOL)rich;
-(void)setHandsome:(BOOL)handsome;
@end
NS_ASSUME_NONNULL_END
Person.m
#import "Person.h"
#define PersonTallMask (1<<0)
#define PersonRichMask (1<<1)
#define PersonHandsomeMask (1<<2)
union TallRichHandsome{
char bits;
struct{
BOOL isTall:1;
BOOL isRich:1;
BOOL isHandsom:1;
};
};
@interface Person(){
union TallRichHandsome _tallRichHandsome;
}
@end
@implementation Person
-(BOOL)tall{
//二进制位转为bool值时最好用两次!!转下,一个二进制位转为字节时有可能会出现问题
//比如0b1,转为一个字节时为 0b11111111,
return !!(_tallRichHandsome.bits & PersonTallMask);
}
-(BOOL)rich{
return !!(_tallRichHandsome.bits & PersonRichMask);
}
-(BOOL)handsome{
return !!(_tallRichHandsome.bits & PersonHandsomeMask);
}
-(void)setTall:(BOOL)tall{
if (tall) {
_tallRichHandsome.bits |= PersonTallMask;
}else{
_tallRichHandsome.bits &= ~PersonTallMask;
}
}
-(void)setRich:(BOOL)rich{
if (rich) {
_tallRichHandsome.bits |= PersonRichMask;
}else{
_tallRichHandsome.bits &= ~PersonRichMask;
}
}
-(void)setHandsome:(BOOL)handsome{
if (handsome) {
_tallRichHandsome.bits |= PersonHandsomeMask;
}else{
_tallRichHandsome.bits &= ~PersonHandsomeMask;
}
}
通过 iOS_objc4-756.2 源码 我们来看下isa指针的结构。
我们将部分源码摘抄出来,看下数据结构:
@interface NSObject <NSObject> {
Class isa;
}
typedef struct objc_class *Class;
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
struct objc_object {
private:
isa_t isa;
}
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
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指针的数据结构是一个共用体isa_t,我们将源码精简下变为下面这样
union isa_t {
Class cls;
uintptr_t bits;
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
};
};
nonpointer
0,代表普通的指针,存储着Class或者Meta-Class对象的内存地址。
1,代表优化过,采用位域存储更多的信息。
has_assoc
是否有设置过关联对象,如果没有释放时更快。
has_cxx_dtor
是否有C++的析构函数(.cxx_destruct),如果没有释放时更快。
shiftcls
存储着Class、Meta-Class对象的内存地址信息。
magic
用于在调试时分辨对象是否未完成初始化
weakly_referenced
是否有被弱引用指向过,如果没有,释放时更快。
deallocating
对象是否正在释放
has_sidetable_rc
引用计数器是否过大无法存储在isa中,如果为1,那么引用计数会存储在一个叫SideTable的类的属性中。
extra_rc
里面存储的值是引用计数器减1
isa的数据查看
代码如下
Person * person = [[Person alloc] init];
NSLog(@"%p",object_getClass(person));
日志及lldb调试结果:
//Person类对象的地址
0x10082d930
(lldb) p/x person->isa
(Class) $1 = 0x000001a10082d935 Person
我们先来验证下isa & ISA_MASK 能否得到正确的类对象的地址0x10082d930
//由上边的源码可知
#define ISA_MASK 0x0000000ffffffff8ULL
(lldb) p/x 0x000001a10082d935 & 0x0000000ffffffff8ULL
(unsigned long long) $7 = 0x000000010082d930
//可以看出我们得到了正确的类对象的地址
下面我们把isa的值0x000001a10082d935拆开来看看每一个bit位的情况
0x35:0011 0101
0xd9:1101 1001
0x82:1000 0010
0x00:0000 0000
0xa1:1010 0001
0x01:0000 0001
0x00:0000 0000
0x00:0000 0000
成员变量 | 位域 | 占用位的值 | 说明 |
nonpointer | 1 | 1 | 优化过的isa指针 |
has_assoc | 1 | 0 | 没有设置关联对象 |
has_cxx_dtor | 1 | 1 | 有C++的析构函数 |
shiftcls | 33 | 0001 0000 0000 1000 0010 1101 1001 0011 0 | 类对象地址 |
magic | 6 | 01 1010 | 用于在调试时分辨对象是否未完成初始化 |
weakly_referenced | 1 | 0 | 没有弱引用 |
deallocating | 1 | 0 | 没有正在释放 |
has_sidetable_rc | 1 | 0 | 引用计数存储在isa中 |
uintptr_t extra_rc | 19 | 0000 0000 0000 0000 000 | 该数代表引用计数减1,可知引用计数为1 |
来重点看下shiftcls这个成员变量其值为:0001 0000 0000 1000 0010 1101 1001 0011 0
我们将其最右边的三个bit位补上零后得到:0001 0000 0000 1000 0010 1101 1001 0011 0000
(lldb) p/x 0b000100000000100000101101100100110000
(long) $9 = 0x000000010082d930
//我们看到这个值正好是Person类对象的值
//与isa & ISA_MASK的结果是一致的。
行者常至,为者常成!