iOS入门
文章目录
NSObject
NSObject *p = [[NSObject alloc] init];
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.mm -o main_objc.cpp
NSObject *p = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
化简后得到
NSObject *p = objc_msgSend( objc_msgSend( objc_getClass("NSObject"), sel_registerName("alloc")) , sel_registerName("init") );
objc_getClass
会返回一个 Class 类指针
typedef struct objc_class *Class;
typedef struct objc_object *id;
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
NSObject *p
就是一个指向Class的指针
所以当一个OC对象调用方法的时候,实际上就是对Class对象的isa指针进行操作
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
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
// ...
};
struct objc_object {
private:
isa_t isa;
public:
// ...
}
// 8字节
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# if __arm64__
# 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)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
struct {
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
}
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
};
};
精简:
# define ISA_MASK 0x0000000ffffffff8ULL // 36位
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {
// 0代表普通的指针,存储着Class,Meta-Class对象的内存地址。
// 1代表优化后的使用位域存储更多的信息。
uintptr_t nonpointer : 1
// 是否有设置过关联对象,如果没有,释放时会更快
uintptr_t has_assoc : 1
// 是否有C++析构函数,如果没有,释放时会更快
uintptr_t has_cxx_dtor : 1
// 存储着Class、Meta-Class对象的内存地址信息
uintptr_t shiftcls : 33 /*MACH_VM_MAX_ADDRESS 0x1000000000*/ // 地址
// 用于在调试时分辨对象是否未完成初始化
uintptr_t magic : 6
// 是否有被弱引用指向过。
uintptr_t weakly_referenced : 1;
// 对象是否正在释放
uintptr_t deallocating : 1
// 引用计数器是否过大无法存储在isa中
// 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
uintptr_t has_sidetable_rc : 1
// 里面存储的值是引用计数器减1
uintptr_t extra_rc : 19
}
};
获得真正的类对象指针 p
& ISA_MASK
OC对象的分类
- instance实例对象 由类alloc出来的对象,每次调用alloc会产生新的实例对象
- class类对象 每个类在内存中有且只有一个类对象
- meta-class 元类对象 每个类的内存中有且只有一个元类对象
instance实例对象的存储结构
- isa指针
- 其它成员变量
类对象 存储
- isa指针
- superClass指针
- 成员变量(描述性信息,比如变量类型)
- 类的对象方法
- 类的协议信息
- 类的属性信息
元类对象 存储
- isa指针
- superClass指针
- 类方法(
+
方法)
isa,superclass的总结
- instance的isa指向class
- class的isa指向meta-class
- meta-class的isa指向基类的meta-class
- class的superclass指向父类的class
- meta-class的superclass指向基类的父类的meta-class
- instance调用方法的轨迹:isa找到父类的class,如果方法不存在就通过superclass找父类
- class调用类方法的轨迹,isa找到meta-class,方法不存在,就通过superclass找父类。
iOS Runtime
方法调用
[person test];
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main_objc.cpp
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("test"));
OC的方法调用也叫消息机制,给方法的调用者发送消息。
消息发送的三个阶段
- 从类及父类的缓存列表查找。
- 动态解析,动态的添加方法实现。
- 消息转发阶段:如果也没有实现动态解析方法,将消息转发给处理消息的接收者来处理。
否就unrecognzied selector sent to instance
// objc_msgSend
// x0 是 对象指针, x1 是 sel
objc-msg-arm64.s
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class // x16会存放 isa
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend // 寻找缓存
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
cmp x10, x16
b.ne LGetIsaDone
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend
首先判断指针是否为0,如果不为0,进入CacheLookup NORMAL查缓存。
如果传入消息不为nil则执行CacheLookup,内部方法缓存列表进行查找,如果找到则执行CacheHit,进而调用方法。否则执行CheckMiss,CheckMiss内部调用 __objc_msgSend_uncached
__objc_msgSend_uncached
内会执行 MethodTableLookup 也就是方法列表查找,MethodTableLookup内部的核心代码 __class_lookupMethodAndLoadCache3
也就是c语言函数 _class_lookupMethodAndLoadCache3
看CacheLookup
.macro CacheLookup
//
// Restart protocol:
//
// As soon as we're past the LLookupStart$1 label we may have loaded
// an invalid cache pointer or mask.
//
// When task_restartable_ranges_synchronize() is called,
// (or when a signal hits us) before we're past LLookupEnd$1,
// then our PC will be reset to LLookupRecover$1 which forcefully
// jumps to the cache-miss codepath which have the following
// requirements:
//
// GETIMP:
// The cache-miss is just returning NULL (setting x0 to 0)
//
// NORMAL and LOOKUP:
// - x0 contains the receiver
// - x1 contains the selector
// - x16 contains the isa
// - other registers are set as per calling conventions
//
LLookupStart$1:
// p1 = SEL, p16 = isa
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
and p10, p11, #~0xf // p10 = buckets
and p11, p11, #0xf // p11 = maskShift
mov p12, #0xffff
lsr p11, p12, p11 // p11 = mask = 0xffff >> p11
and p12, p1, p11 // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
// p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p12, p12, p11, LSL #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
LLookupEnd$1:
LLookupRecover$1:
3: // double wrap
JumpMiss $0
.endmacro
lookupMethodInClassAndLoadCache 在类中寻找方法,
IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel)
{
Method meth;
IMP imp;
// fixme this still has the method list vs method cache race
// because it doesn't hold a lock across lookup+cache_fill,
// but it's only used for .cxx_construct/destruct and we assume
// categories don't change them.
// 命中缓存
// Search cache first.
imp = _cache_getImp(cls, sel);
if (imp) return imp;
// Cache miss. Search method list.
// 在当前类的方法中寻找,
meth = _class_getMethodNoSuper(cls, sel);
if (meth) {
// Hit in method list. Cache it.
_cache_fill(cls, meth, sel);
return method_getImplementation(meth);
} else {
// 如果没找到,则转发该方法去寻找。
// Miss in method list. Cache objc_msgForward.
_cache_addForwardEntry(cls, sel);
return _objc_msgForward_impcache;
}
}
关键数据结构
方法Method
是一个objc_method结构体
SEL
是一个字符串
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
};
在objc2里结构是:
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
// objc-runtime-new.h
typedef uintptr_t SEL;
class_addMethod
class_addMethod(__unsafe_unretained Class cls, SEL name, IMP imp, const char *types);
// 获取其它方法指向method_t的指针
Method otherMethod = class_getInstanceMethod(self, @selector(other));
// method_getImplementation 和 method_getTypeEncoding 获取 imp 和 type