Objective-C的动态性体现在四个方面:动态类型(Dynamic typing)、动态绑定(Dynamic binding 找到真的的实现)、动态方法决议(Dynamic Method Resolution 添加新的实现)、动态加载(Dynamic loading)。
动态类型(Dynamic typing)
Dynamic typing,使用任意类型id,在运行时确定对象类型(在编译时确定对象的类型是静态类型),由对象类型决定指针的类型,也可以通过isKindofClass方法,动态判断对象的最后的类型。 在Dynamic typing作用下,实例对象的类别可延迟到运行时确定,是数据动态性。
动态绑定(Dynamic binding 找到真的的实现)
Objective-C是面向对象的编程语言,方法是第二个维度,Dynamic binding便是方法在运行时确定的机制,即消息传递机制。为此Objective-C构造了选择器,把C中的单维度的函数指针二维化,运行时动态的查找选择器对应的函数指针,可以灵活的构造选择器到多个函数指针的映射。
void objc_msgSend (id self, SEL cmd, ...)
⚠️动态绑定使用了动态类型,但动态类型不是它的基础,离开它一样可以实现动态绑定,但没有丰富的现实意义。
如果动态绑定失败(没有找到实现的方法),走动态方法决议流程。
动态方法决议(Dynamic Method Resolution )
_objc_msgForward在进行消息转发的过程中会涉及以下这几个方法:
0、查找类体系中的实现
1、resolveInstanceMethod:方法 (或 resolveClassMethod:)。添加新的实现
2、forwardingTargetForSelector:方法 快速转发-备用接受者
3、methodSignatureForSelector:方法 完整转发-生成方法签名
4、forwardInvocation:方法 完整转发-调用
5、doesNotRecognizeSelector: 方法 异常


void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
Dynamic loading
类和类别运行时动态加载,这里暂不讨论。
Dynamic typing
Objective-C 是面向对象的编程语言,具有封装、继承和多态三大特性,同时具有category这一伟大的特性,与继承相媲美。Dynamic typing不仅仅提供运行时确定对象类型,也提供了运行时修改类型的封装的能力,这才是Objective-C所独具的特色与核心。
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();//非标号指针
// getIsa() allows this to be a tagged pointer object
Class getIsa();
....
}
/* Use `Class` instead of `struct objc_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
class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
......
}
typedef struct objc_class *Class;
typedef struct objc_object *id;
Class是定义为objc_class结构体的指针,id定义为objc_object结构体的指针,objc_class继承objc_object,在Objective-C中万物皆对象。Class承载了对象的数据结构信息与操作,注意到从class_data_bits_t到class_rw_t的转换,class_rw_t便是那个承载了诸多动态性的结构。
struct class_rw_t {// 8 bytes
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;//
method_array_t methods; //动态添加
property_array_t properties;//动态添加
protocol_array_t protocols;//动态添加
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
#if SUPPORT_INDEXED_ISA
uint32_t index;
#endif
void setFlags(uint32_t set){
OSAtomicOr32Barrier(set, &flags);
}
void clearFlags(uint32_t clear){
OSAtomicXor32Barrier(clear, &flags);
}
// set and clear must not overlap
void changeFlags(uint32_t set, uint32_t clear){
assert((set & clear) == 0);
uint32_t oldf, newf;
do {
oldf = flags;
newf = (oldf | set) & ~clear;
} while (!OSAtomicCompareAndSwap32Barrier(oldf,
newf,
(volatile int32_t *)&flags));
}
};
class_rw_t结构提供了可扩展的方法列表、属性列表和协议列表,可通过类别与协议向类中添加方法与属性,不必使用继承。class_rw_t结构提供了类结构变化控制的接口,而class_ro_t封装了继承体系中不变的成分,如实例变量,和基类中的方法和协议(在加载时会放到RW对应结构的对应列表中)。class_ro_t一但编译完成,便不可继续添加实例变量,如通过类别或是协议添加属性会引起崩溃,可通过关联存储实现(内部通二级哈希表实现:以指针非操作结果(隐藏指针机制)为key,在全局哈希表中找到对象对应的哈希表,通过对象属性定位属性)。
struct class_ro_t {
uint32_t flags; ////////////////////////
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved; //not set directly
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList; //not set directly
protocol_list_t * baseProtocols; //not set directly
const ivar_list_t * ivars;//not set directly, create by compiler, read from image
const uint8_t * weakIvarLayout;//point to the memory that stores the value of ivar wjf
property_list_t *baseProperties; //not set directly
method_list_t *baseMethods() const {
return baseMethodList;
}
};
Objective-C指向类指针,即Class,占用5个bytes,而内部最低对其是4bytes,所以isa占用8bytes,其后是实例变量空间,在类等级中同样遵守4bytes对其原则。如Test实例占用8+4=12bytes空间,Test1实例占用8+4+4=16bytes空间,Test2实例占用8+4=12bytes空间(flag、flag2、flag3、flag4占据连续的四个byte),Test3占用8+4=12bytes空间(在flag与flag2之间有一个空闲byte)。
@interface Test : NSObject
@property BOOL flag;
@end
@interface Test1 : Test
@property BOOL flag1;
@end
@interface Test2 : NSObject
@property BOOL flag;
@property BOOL flag2;
@property BOOL flag3;
@property BOOL flag4;
@end
@interface Test3 : NSObject
@property BOOL flag;
@property short flag2;
@end
Test * test = [Test new];
test.flag = 1;
test.flag3 = 4;

- 父类与子类不共用4bytes对其规则
- 父类的实例变量更靠近isa
- 对齐规则为4bytes规则+类型对其规则
isa_t联合
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {//__arm64__
uintptr_t nonpointer : 1; ////true tagged指针,值直接存储在 地址中
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;//45
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
}
在__arm64__与__x86_64__两个架构上struct具体的字段相同,但是分布不同。 __ARM_ARCH_7K__架构上的struct具体的字段有所不同。
__ARM_ARCH_7K__具有所谓的索引类别指针,即indexcls,类对象存储在一个数组中,indexcls的值为类对象在数组中的下标。而__arm64__与__x86_64__直接存储类对象的地址。
struct {//__ARM_ARCH_7K__
uintptr_t nonpointer : 1; //true tagged指针,值直接存储在 地址中
uintptr_t has_assoc : 1;
uintptr_t indexcls : 15; //类对象索引地址,类对象存储在一个数组中,通过索引查找
uintptr_t magic : 4;
uintptr_t has_cxx_dtor : 1;//up
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 7;//25
# define RC_ONE (1ULL<<25)
# define RC_HALF (1ULL<<6)
};
- nonpointer:该实例对象的指针是否是标号指针
- has_assoc:该实例对象是否有关联存储的对象,true在析构时需要移除关联的对象
- has_cxx_dtor:是否有C++析构函数
- weakly_referenced:实例对象是否被其它对象若引用,如果为true析构时需要把若表中指针置为nil
- has_sidetable_rc:是否在边表中存储引用计数值
void *objc_destructInstance(id obj)
{//移除关联的对象
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);//边表移除若引用
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
标号指针
inline Class objc_object::ISA() {
assert(!isTaggedPointer()); //标号指针
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) { //不是指针,是下标值
uintptr_t slot = isa.indexcls;// index
return classForIndex((unsigned)slot);//looking table
}
return (Class)isa.bits; //类对象的指针,错误情况
#else
return (Class)(isa.bits & ISA_MASK);//bit operation
#endif
}
目前常见的支持标号指针的有5个类:NSString、NSNumber、NSIndexPath、NSManagedObjectID、NSDate。
内存管理-边表
objective-c runtime对内存的管理是基于几个十分重要的数据结构与概念展开的。
- 带化映射模版类StripedMap,与此对应的概念是带化锁。
- 指针伪装模版类DisguisedPtr<T>,与此对应的概念是指针伪装。
- 数据结构弱表weak_table_t和弱实体weak_entry_t,与它们对应的概念是自动释放池。
- 结构体边表SideTable,与其对应的概念是引用计数映射。
SideTableBuf
- SideTableBuf { StripedMap { SideTable *8 {
- spinlock_t slock;
- RefcountMap refcnts; //简单的二次探测哈希表,擅长支持小的键和值, id -> rcount
- weak_table_t weak_table;
- } } }
- weak_table_t { weak_entry_t* //数组,使用循环查找 }
- //通过不精确定位+循环查找特定对象对应的weak_entry_t
SideTableBuf是一个静态内存区域,new StripedMap<SideTable>在其上创建了对象,巧妙的避开了初始化顺序问题。代码如下:
alignas(StripedMap<SideTable>) static uint8_t
SideTableBuf[sizeof(StripedMap<SideTable>)];//placement new
static void SideTableInit() {
new (SideTableBuf) StripedMap<SideTable>();
}
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
SideTableBuf是一个外部不可见的静态内存区块,存储StripedMap<SideTable>对象。它是内存管理的基础,其它的功能与特性都是基于这个插槽而展开的。为了理解内存管理机制的运作,我们首先药介绍StripedMap<T>和SideTable。
带化映射模版类StripedMap
StripedMap<T> 实现void* 到T的映射,objective-c中为id到SideTable的映射(含义是这个对象的生命周期由哪个SideTable管理),即各种结构体指针(id)到SideTable的映射。 在嵌入式架构中,StripedMap<T>一个典型的应用是8个内旋锁的映射查找,以提高并发访问的速度。有如下特化:
StripedMap<spinlock_t>{
enum { CacheLineSize = 64 };
enum { StripeCount = 8 };
struct PaddedT {
T value alignas(CacheLineSize);
};
PaddedT array[StripeCount];
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
public:
T& operator[] (const void *p) { //cast void * to T&
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const { //cast void * to const T&
return const_cast<StripedMap<T>>(this)[p];
}
..........
}
array是一个长度为8的数组,T类型强制为64位对齐。在运行时中已知应用实例:StripedMap<spinlock_t>、StripedMap<SyncList>和StripedMap<SideTable> 。。
StripedMap中没有用到锁,对内存块SideTableBuf中对象是只读的,对StripedMap封装的8个全局表的操作在多线程环境下仍是线程安全的。
SideTable
辅助表,或是边表,它是objective-c内存管理的核心类。所有对象共享8个运行时维护的全局边表,特定的对象只对应于单个边表。运行时通过边表维护的唯一的一个自旋锁,保证多线程环境下对象操作的有效性。
// Template parameters.
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;///
weak_table_t weak_table;
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
DenseMap是llvm库中的类,是一个简单的二次探测哈希表,擅长支持小的键和值。DisguisedPtr<T>通过运算使指针隐藏于系统工具(如LEAK工具),同时保持指针的能力,其作用是通过计算把保存的T的指针隐藏起来,实现指针到整数的全射。其中RefcountMap定义为:
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
typedef objc::DenseMap<对象指针运算得到的整数,对象的引用计算值,true> RefcountMap,边表就是通过RefcountMap保存隐藏的且可恢复的对象指针与引用计数值,当引用计数变为0时,通过weak_table_t置空所有的weak引用,这就是weak置空的大体触发机制。
template <typename T>
class DisguisedPtr {
uintptr_t value; // unsigned long
static uintptr_t disguise(T* ptr) { //指针隐藏
return -(uintptr_t)ptr;
}
static T* undisguise(uintptr_t val) { //指针显露
return (T*)-val;
}
.......
};
DenseMap存储了对象指针与引用计数的键值对,运行时一共维护8个SideTable,对应一共有8个DenseMap。SideTable通过一个自旋锁控制对DenseMap集合的访问。所以对象如何在这8个SideTable之间(DenseMap之间)分布存储是提高系统并发效率的关键。
weak_table_t
运行时一共维护8个SideTable,对应一共有8个DenseMap,同样也是8个weak_table_t表。
#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers;//若引用者的列表
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;//62 or 30
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];//4
};
};
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}
weak_entry_t(objc_object *newReferent,
objc_object **newReferrer)
: referent(newReferent){
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
};
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
assert(referent);
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
return &weak_table->weak_entries[index];
}
weak_table_t维护一个weak_entry_t *,指向一个weak_entry_t一维数组,通过不精确定位+循环查找特定对象对应的weak_entry_t。
autoreleasepool
AutoreleasePool底层实现原理www.jianshu.com
使用双向链表承载延迟释放信息,使用池边界(POOL_BOUNDARY)分割不同的嵌套池。
Dynamic binding 动态绑定
动态绑定,即从编码时的选择器,到运行时通过方法查找,找到对应的函数指针的过程。
方法缓存前查找过程
在类的特定方法列表中查找方法
struct old_method {
SEL method_name;//sel 是方法名字
char *method_types;
IMP method_imp;
};
static inline old_method *_findMethodInList(old_method_list * mlist, SEL sel) {
int i;
if (!mlist) return nil;
for (i = 0; i < mlist->method_count; i++) {
old_method *m = &mlist->method_list[i];
if (m->method_name == sel) {//sel 是方法名字
return m;
}
}
return nil;
}
类可能有多个,一个或是没有方法列表
static inline old_method * _findMethodInClass(Class cls, SEL sel) {
// Flattened version of nextMethodList(). The optimizer doesn't
// do a good job with hoisting the conditionals out of the loop.
// Conceptually, this looks like:
// while ((mlist = nextMethodList(cls, &iterator))) {
// old_method *m = _findMethodInList(mlist, sel);
// if (m) return m;
// }
if (!cls->methodLists) {
// No method lists.
return nil;
}
else if (cls->info & CLS_NO_METHOD_ARRAY) {
// One method list. 单个方法列表
old_method_list **mlistp;
mlistp = (old_method_list **)&cls->methodLists;
*mlistp = fixupSelectorsInMethodList(cls, *mlistp);
return _findMethodInList(*mlistp, sel);
}
else {
// Multiple method lists. 多个方法列表
old_method_list **mlistp;
for (mlistp = cls->methodLists;
*mlistp != nil && *mlistp != END_OF_METHODS_LIST;
mlistp++)
{
old_method *m;
*mlistp = fixupSelectorsInMethodList(cls, *mlistp);
m = _findMethodInList(*mlistp, sel);
if (m) return m;
}
return nil;
}
}
static Method _class_getMethodNoSuper_nolock(Class cls, SEL sel)
{//对外接口
methodListLock.assertLocked();
return (Method)_findMethodInClass(cls, sel);
}
查找到方法后,获取实现指针
// called by a debugging check in _objc_insertMethods
IMP findIMPInClass(Class cls, SEL sel)
{
old_method *m = _findMethodInClass(cls, sel);
if (m) return m->method_imp;
else return nil;
}
方法查找有两个过程,一个是不查找父类的方法列表lookupMethodInClassAndLoadCache,在析构中使用object_cxxDestructFromClass。
一个是查找父类的方法列表lookUpImpOrForward,在消息发送时使用。它是标准的IMP查找函数。其步骤如下:
- 查找类自己的缓存,找到返回实现,否则:
- 查找自己的方法列表(分类与协议中的方法,在加载的时候已经加入到类的方法列表中),找到加入类的缓存列表,否则:
- 查找父类,同样1 2 的过程,只不过这次操作是父类的缓存列表。
- 如果在整个类链上都没有找到,则进入方法决议_class_resolveMethod。
- 如果方法决议也没有得到有效的实现,进入方法转发_objc_msgForward_impcache。
/***********************************************************************
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known.
* If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use
* must be converted to _objc_msgForward or _objc_msgForward_stret.
* If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel,
id inst,
bool initialize, bool cache, bool resolver)
{
Class curClass;
IMP methodPC = nil;
Method meth;
bool triedResolver = NO;
methodListLock.assertUnlocked();
// Optimistic cache lookup
if (cache) {
methodPC = _cache_getImp(cls, sel);
if (methodPC) return methodPC;
}
// Check for freed class
if (cls == _class_getFreedObjectClass())
return (IMP) _freedHandler;
// Check for +initialize
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
// The lock is held to make method-lookup + cache-fill atomic
// with respect to method addition. Otherwise, a category could
// be added but ignored indefinitely because the cache was re-filled
// with the old value after the cache flush on behalf of the category.
retry:
methodListLock.lock();
// Try this class's cache.
methodPC = _cache_getImp(cls, sel);
if (methodPC) goto done;
// Try this class's method lists.
meth = _class_getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, cls, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
// Try superclass caches and method lists.
curClass = cls;
while ((curClass = curClass->superclass)) {
// Superclass cache.
meth = _cache_getMethod(curClass, sel, _objc_msgForward_impcache);
if (meth) {
if (meth != (Method)1) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, curClass, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
meth = _class_getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, curClass, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
}
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
methodListLock.unlock();
_class_resolveMethod(cls, sel, inst);
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
_cache_addForwardEntry(cls, sel);
methodPC = _objc_msgForward_impcache;
done:
methodListLock.unlock();
return methodPC;
}
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/DynamicTyping.html
https://www.tutorialspoint.com/objective_c/objective_c_dynamic_binding.htm
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtDynamicResolution.html#//apple_ref/doc/uid/TP40008048-CH102-SW1