Runtime
isa
在ARM 64之前,isa是一个指针,存储着Class
、Meta-Class
对象的内存地址。
ARM 64之后,isa是一个共用体,除了存储Class
、Meta-Class
对象的内存地址,还保存了更多的信息。ARM 64情况下,isa共用体的定义。
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
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的位域的意义
- nonpointer
- 0 ,表示指针,存储Class
对象地址
- 1,使用共用体,存储更多信息 - has_assoc
- 是否设置过关对象。如果没有,该对象会释放地更快 - has_cxx_dtor
- 是否有C++析构函数。如果没有,该对象会释放地更快 - shiftcls
- 类对象,元类对象的内存地址 - magic
- 对象是否完成初始化 - weakly_referenced
- 是否被若引用指向过。如果没有,该对象会释放地更快 - deallocating
- 是否正在被释放 - has_sidetable_rc
- 是否引用计数器过大,不能保存在isa中。如果过大,会保存在SideTable
中 - extra_rc
- 保存该对象的引用计数减1
method_t
struct method_t {
SEL name; // 底层结构类似于char *
const char *types;
IMP imp; // 该表函数的具体实现
};
SEL
代表方法\函数名,一般叫做选择器,底层结构跟char *类似
- 可以通过
@selector()
和sel_registerName()
获得 - 可以通过
sel_getName()
和NSStringFromSelector()
- 不同类中相同名字的方法,所对应的方法选择器是相同的
types
包含了函数返回值,参数编码的字符串
// i 24 @ 0 : 8 i 16 f 20
/* i 24,表示所有参数占据的字节数
@ 0,表示第一个参数,self,从第0个参数开始
: 8,表示第二个参数,SEL,从第8个开始
i 16,表示(int)age
f 20,表示(float)height
*/
- (void)test:(int)age height:(float)height {
}
IMP
表示函数的具体实现
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
cache_t
struct cache_t {
struct bucket_t *_buckets; // 数组
mask_t _mask; // 数组长度
mask_t _occupied; // 已经使用的长度
}
struct bucket_t {
private:
cache_key_t _key; // SEL
IMP _imp;
}
散列表使用的hash算法,就是&运算
static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{
return (mask_t)(key & mask);
}
如果发生Hash碰撞,则从前一个开始找,直到索引为0。如果还找不到,就再从数组最后一个,倒序开始找。
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);
}
子类调用父类方法之后,先在子类找不到方法,然后去父类找,
- 也是先找父类的缓存,再找父类的方法列表
- 找到之后,会把方法缓存到子类的cache中
缓存的时候,如果缓存满了,则清除所有缓存,并且2倍扩容
void cache_t::expand()
{
cacheUpdateLock.assertLocked();
uint32_t oldCapacity = capacity();
uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
if ((uint32_t)(mask_t)newCapacity != newCapacity) {
// mask overflow - can't grow further
// fixme this wastes one bit of mask
newCapacity = oldCapacity;
}
reallocate(oldCapacity, newCapacity);
}
obj_msgSend()
OC中的方法调用,都是转换成调用obj_msgSend()
函数。整个过程可以分成3个阶段:
- 消息发送:根据OC对象模型图,从子类到父类,去找方法
- 动态方法解析:可能会向类对象添加方法
- 消息转发:可能将该方法调用,转到其他对象去调用。
_class_lookupMethodAndLoadCache3
lookUpImpOrForward
getMethodNoSuper_nolock、search_method_list、log_and_fill_cache
cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache
_class_resolveInstanceMethod
_objc_msgForward_impcache
汇编调用
由于obj_msgSend()
调用十分频繁,所以obj_msgSend()
有一部分是使用汇编实现的。
一般如果是C函数是objc_msgSend
,则对应的汇编函数就是加上下划线_objc_msgSend
汇编做了如下工作:
- 判断receiver是否为nil,如果是nil就直接返回
- 查找缓存,如果缓存没有命中,就在方法列表查找方法
objc-msg-arm64.s
ENTRY _objc_msgSend
b.le LNilOrTagged
CacheLookup NORMAL
.macro CacheLookup
.macro CheckMiss
STATIC_ENTRY __objc_msgSend_uncached
.macro MethodTableLookup
__class_lookupMethodAndLoadCache3
查找方法
汇编函数是__class_lookupMethodAndLoadCache3
,则对应的runtime方法是_class_lookupMethodAndLoadCache3
在C函数还是会查找一遍缓存,原因是:再执行到这次的查找缓存之前,可能动态添加一些方法,缓存方法变化。
- 在本类进行查找
- 再次查找缓存,
- 查找方法,在本类对象的方法列表,还可以分成二分查找(如果已经排好序)和线性查找。如果找到了,会进行缓存
- 不断地向上,在父类进行查找。不管是
- 先找父类的缓存。如果找到了,在自己的类缓存
- 在找父类的方法列表。如果找到了,在自己的类缓存
动态方法解析
当通过上面的方法查找,找不到方法,就会进入动态方法解析。
- 通过
triedResolver
,只解析一次 -
goto retry
会重新进入方法查找:先查找本类缓存,在找本类方法列表;再父类
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
使用如下示例代码,进行动态方法解析
void c_anotherMethod(id self, SEL _cmd)
{
NSLog(@"c_another class method");
}
- (void)anotherTest {
NSLog(@"instance %s",__func__);
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 第一个参数是object_getClass(self)
class_addMethod(object_getClass(self), sel, (IMP)c_anotherMethod, "v16@0:8");
return YES;
}
return [super resolveClassMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
Method anotherMethod = class_getInstanceMethod(self, @selector(anotherTest));
class_addMethod(self,
sel,
method_getImplementation(anotherMethod),
method_getTypeEncoding(anotherMethod));
return YES;
}
return [super resolveInstanceMethod:sel];
}
消息转发
消息转发.png首先是调用forwardingTargetForSelector:
,如果返回不为nil,就进入target
的方法调用流程。forwarding:
没有开源,可以通过逆向,了解其实现的。
如果最终能够走到forwardInvocation:
,即使forwardInvocation:
是空实现,该方法调用也能成功完成。
- 调用forwardInvocation之前,系统会先调用
resolveInstanceMethod:
,传入的selector
为_forwardStackInvocation:
- 如果实现
methodSignatureForSelector :
,但是没有实现forwardInvocation :
,也会unrecognized selector
- (id)forwardingTargetForSelector:(SEL)aSelector {
return [[ForwardTarget alloc] init];
}
// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v24@0:8@16"];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
// anInvocation.target 方法调用者
// anInvocation.selector 方法名
// anInvocation.methodSignature methodSignatureForSelector:方法返回的
// [anInvocation getArgument:NULL atIndex:0]
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation invokeWithTarget:[[ForwardTarget alloc] init]];
}
Super
[super message]的底层实现,最初是objc_msgSendSuper(arg, sel)
,其中第一个参数是结构体,该结构体包含两个成员变量:
- 消息接收者,就是子类实例对象
- 父类类对象,找方法会从父类类对象开始找
struct objc_super {
__unsafe_unretained _Nonnull id receiver; // 消息接收者
__unsafe_unretained _Nonnull Class super_class; // 消息接收者的父类
};
后来实现变成objc_msgSendSuper2(arg, sel)
,第一个参数是结构体,其成员变量是:
- 消息接收者,仍然是子类对象
- 消息接收者的类对象。但是其内部实现,仍然是获取父类类对象,从父类开始找
struct objc_super2 {
__unsafe_unretained _Nonnull id receiver; // 消息接收者
__unsafe_unretained _Nonnull Class current_class; // 消息接收者的类对象
};
- (instancetype)init
{
if (self = [super init]) {
NSLog(@"[self class] = %@", [self class]); // MJStudent
NSLog(@"[self superclass] = %@", [self superclass]); // MJPerson
NSLog(@"--------------------------------");
// objc_msgSendSuper({self, [MJPerson class]}, @selector(class));
NSLog(@"[super class] = %@", [super class]); // MJStudent
NSLog(@"[super superclass] = %@", [super superclass]); // MJPerson
}
return self;
}
isKindOf
objc的源码如下,要特别注意的是
-
+ (BOOL)isKindOfClass:
,要求传入的是metaClass,但是最后一步会走到NSObject的class对象。
// 这句代码的方法调用者不管是哪个类(只要是NSObject体系下的),都返回YES
NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); // 1
NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]); // 0
NSLog(@"%d", [MJPerson isKindOfClass:[Person class]]); // 0
NSLog(@"%d", [MJPerson isMemberOfClass:[Person class]]); // 0
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
面试题
以下代码中,是否能够正常打印
@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
- (void)print;
@end
@implementation MJPerson
- (void)print
{
NSLog(@"my name is %@", self->_name);
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [MJPerson class];
void *obj = &cls;
[(__bridge id)obj print];
}
首先分析,变量之间的关系,如下图所示
- 与正常调用实例方法的结构相同,都包含指针变量,对象的ISA指针,类对象
- 所以可以调用到
print
方法
变量之间的关系.png
执行方法时,栈中变量地址的位置,如下图所示
- 栈中的变量地址是从高到低,即先出现的变量,占据高地址
-
[super viewDidLoad];
,上文说过,会有一个结构体变量;接着是cls
,接着是obj
- 根据实例对象的结构图,访问
name
成员变量,就是访问self
占据的指针
栈中的变量地址.png
API使用
- 成员变量是基本数据类型,需要先转成指针,再bridge
// 创建类
Class newClass = objc_allocateClassPair([NSObject class], "MJDog", 0);
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_weight", 4, 1, @encode(int));
class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
// 注册类
objc_registerClassPair(newClass);
// 如果是基本数据类型,需要先转成指针,再bridge
object_setIvar(person, ageIvar, (__bridge id)(void *)10);
具体应用
- 利用关联对象(AssociatedObject)给分类添加属性
- 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
- 交换方法实现(交换系统的方法,
method_exchangeImplementions
会清空方法缓存 - 利用消息转发机制解决方法找不到的异常问题
数组和字典的方法交换
集合类型,由于使用了类簇.
- 可变数组真正的类型是
__NSArrayM
- 可变字典真正的类型是
__NSDictionaryM
- 不可变字典真正的类型是
__NSDictionaryI
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(my_insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
});
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = NSClassFromString(@"__NSDictionaryM");
Method method1 = class_getInstanceMethod(cls, @selector(setObject:forKeyedSubscript:));
Method method2 = class_getInstanceMethod(cls, @selector(my_setObject:forKeyedSubscript:));
method_exchangeImplementations(method1, method2);
Class cls2 = NSClassFromString(@"__NSDictionaryI");
Method method3 = class_getInstanceMethod(cls2, @selector(objectForKeyedSubscript:));
Method method4 = class_getInstanceMethod(cls2, @selector(my_objectForKeyedSubscript:));
method_exchangeImplementations(method3, method4);
});