?
运行期的成员列表只读那块
感觉用property就可以了, 但是好像三种都有用
synthesize就是给property生成的成员变量换了个名字
dynamic就是取消了property的实现set&get&生成成员变量的功能
参考文章
面向对象编程三大特性------封装、继承、多态
__unsafe_unretained 的理解和使用
OC对象内存大小及分配原理详解
探索子线程autorelease对象的释放时机
一、OC语言特性
OC和C:
其实OC实现的东西C也可以实现, 结构体也可以封装数据,区别在于C语言面向过程, OC面向对象,通过runtime库, 把复杂的过程封装了起来,可以简约方便的实现动态特性。
OC语言本质上是对C语言的扩展, 相对于C语言, 增加了动态特性和smallTalk式的消息传递机制,这一扩展的本质就是runtime库。
1.1 动态特性
- 动态类型(Dynamic typing):
在OC语言中就是id类型。静态类型在编译的时候就能被识别出来。所以,若程序发生了类型不对应,编译器就会发出警告。而动态类型就编译器编译的时候是不能被识别的,要等到运行时(run time),即程序运行的时候才会根据语境来识别。
我用最多的就是代理
代理需要用到很多不同的VC里,需要赋不同的VC值, id类型的指针,被赋值什么类型就是什么类型 - 动态绑定(Dynamic binding)
动态绑定是指在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。程序运行过程中,把函数(或过程)调用与响应调用所需要的代码相结合的过程称为动态绑定。 - 动态加载(Dynamic loading)
程序启动时动态加载可执行代码和资源。
1.2 smallTalk式的消息传递机制
在这个机制中,我们认为所有的东西都是对象,或者应该被当作对象处理。
这一机制的核心是msg_send()
1.3 面向对象特性
- 封装
指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。系统的其他对象只能通过包裹在数据外面的已经授权的操作来与这个封装的对象进行交流和交互。也就是说用户是无需知道对象内部的细节,但可以通过该对象对外的提供的接口来访问该对象。 - 继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码,能够大大的提高开发的效率。 - 多态
多态性就是相同的消息使得不同的类做出不同的响应。
二、类与对象
类本质上是对数据和行为的封装产物
对象是类的实例化
2.1 类:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
2.1.1成员变量列表
常见的生成成员变量的方法:
- 直接写在interFace的{}中
- 通过@property
@property会自动生成一个成员变量和声明set&get方法
@synthesize:根据@property声明的属性生成实例变量,并自动实现setter和getter方法 - @dynamic:告诉编译器不生成实例变量、getter和setter方法
分类中只有属性列表, 没有成员变量列表, 只声明, 不实现, 所以没办法合入到主类, 这就是为什么分类不能添加成员变量的原因。
2.1.2 方法列表
OC中的方法构成, 把方法名(SEL)和具体(IMP)实现分开
像method-swizzling就是交换两个sel的IMP
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
2.1.3 方法缓存列表
哈希存储,一般用到的方法不会只用一次,所以会把用到的方法放到方法缓存中, 方法查找时, 先从缓存中找, 找不到在按照层级找类和superClass。
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method _Nullable buckets[1] OBJC2_UNAVAILABLE;
};
2.1.4 协议列表
链表存储
struct objc_protocol_list {
struct objc_protocol_list * _Nullable next;
long count;
__unsafe_unretained Protocol * _Nullable list[1];
};
2.2 对象:
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
三、isa指针
面向对象中每一个对象都必须依赖一个类来创建,因此对象的isa指针就指向对象所属的类根据这个类模板能够创建出实例变量、实例方法等。
类的isa指向元类, 元类中存放了类方法
根据不同的条件, 初始值可能不同
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; //是否是优化的isa
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 unused : 1;
uintptr_t has_sidetable_rc : 1; 是否要用sidetable存储引用计数
uintptr_t extra_rc : 19 //引用计数值
isa占8字节, 所有对象都有isa指针(除了taggedPointer)
在对象创建时初始化, 管控这对象的引用计数, 生命周期。
四、对象的生成
alloc的函数调用栈
关键方法:_class_createInstanceFromZone
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
//计算内存
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
//通过calloc申请内存。
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
//将内存地址与Isa绑定的过程
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
做的事情
- 内存计算
- 内存申请
- 将内存地址与Isa绑定以及isa的初始化
tips:内存计算规则
设计到内存对齐原则而且必须为16的倍数。
size_t instanceSize(size_t extraBytes) {
//alignedInstanceSize()遵循内存对齐
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
五、 对象的使用
对象创建之后, 存放在堆区, 通过引用计数管理
栈区存放对对象的引用
引进ARC之后,对象的引用类型通过属性关键字来管理
5.1 属性关键字
5.1.1 强引用:
strong
相当于MRC下的retain方法调用
会给所引用的对象的引用计数加1
copy
分为浅复制和深复制
浅复制复制地址 ,被引用对象的引用计数加1。
深复制复制内容, 创建了一个新的对象, 并强持有该对象。
5.1.2 弱引用
weak
只引用, 不持有, 对象的引用计数无变化
对象的引用计数为0时, 对象被释放, 会给弱引用该对象的指针置nil
常用于修饰对象类型以及容易引起循环引用的对象(eg:代理, block)
assign
与weak相似, 但不会在对象被释放后置nil
常用于修饰基本数据类型
unsafe_unretained
可修饰对象类型
__weak对性能会有一定的消耗,使用__weak,需要检查对象是否被释放,在追踪是否被释放的时候当然需要追踪一些信息,那么此时__unsafe_unretained比__weak快,而且一个对象有大量的__weak引用对象的时候,当对象被废弃,那么此时就要遍历weak表,把表里所有的指针置空,消耗cpu资源。
当明确对象的生命周期的时候,可以使用__unsafe_unretained替代__weak,可以稍微提高一些性能,虽然这点性能微乎其微。
5.2 引用存储
5.2.1 非优化的isa
(通过nonPointer判断):引用计数和弱引用存放在sideTable中
5.2.2 优化的isa:
- 引用计数:
存放在isa.retainCount中
如果存不下了, isa的has_sidetable_rc置为1
然后分一半到sideTable中, 继续往retainCount中存
再次满了之后, 再分一半出去…… - 弱引用
isad的weakly_referenced 置为1
存放在sideTable中
5.2.3 sideTable
有一个全局的哈希表sideTables里面存放sideTable
👇
sideTable是一个结构体
struct SideTable {
spinlock_t slock;
RefcountMap refcnts; //referanceCount:引用计数表
weak_table_t weak_table;//弱引用表:存放的对象的弱引用指针
};
其中:
RefcountMap refcnts :就是引用计数表
weak_table_t weak_table:就是弱引用表
涉及到weak置nil, 所以看一下弱引用表
弱引用表:weak_table_t weak_table
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
被弱引用的对象对应一个weak_entry_t
weak_entry_t
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers; // 弱引用该对象的对象指针地址的hash数组
uintptr_t out_of_line_ness : 2; // 是否使用动态hash数组标记位
uintptr_t num_refs : PTR_MINUS_2; // hash数组中的元素个数
uintptr_t mask; // hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)素个数)。
uintptr_t max_hash_displacement; // 可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值)
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
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;
}
}
};
- 每个弱引用该对象的对象都是一个weak_referrer_t
- weak_referrer_t的存放形式是一个union类型, 通过out_of_line判断
如果不超过某个值, 存放在定长数组中
如果大于某个值, 就从定长数组中取出, 放入动态数组中, 后续的也放在动态数组
六、 对象的销毁
对象的生存过程中, 通过retain持有, 通过release释放, 通过delloc销毁。
ARC环境下, 自动管理引用计数, 这些过程都对应到相应的ARC机制。
retain -> 属性关键字
release -> autoreleasePool
delloc -> 引用计数为0时被调用
6.1 关于autoreleasePool
包括主线程在内的所有线程都维护有它自己的自动释放池的堆栈结构。
当一个线程线程停止,它会自动释放掉与其关联的所有自动释放池。
6.1.1 autoReleasePool结构
struct __AtAutoreleasePool {
//构造函数,在创建该结构体的时候调用
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
//析构函数,在销毁该结构体的时候调用
//将objc_autoreleasePoolPush()返回的结果传入到objc_autoreleasePoolPop()中
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
调用了objc_autoreleasePoolPush() & objc_autoreleasePoolPop(atautoreleasepoolobj)
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
可以看到, 本质上由autoReleasePoolPage管理引用计数
autoReleasePoolPage
内存布局
- 每个AutoreleasePoolPage对象占用4096个字节的内存
- 所有的AutoreleasePoolPage对象都是通过双向链表的形式连接在一起
autoReleasePoolPage类结构部分截图如下
继承于AutoreleasePoolPageData
包含struct thread_data_t成员
AutoreleasePoolPageData
struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
struct AutoreleasePoolEntry {
uintptr_t ptr: 48;
uintptr_t count: 16;
static const uintptr_t maxCount = 65535; // 2^16 - 1
};
static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endif
magic_t const magic;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
: magic(), next(_next), thread(_thread),
parent(_parent), child(nil),
depth(_depth), hiwat(_hiwat)
{
}
};
成员参数介绍
- magic
magic:用来校验 AutoreleasePoolPage 的结构是否完整; - next
指向下一个能存放autorelease对象地址的区域 - thread
AutoReleasePoolPage和线程是对应关系 - parent&child
当前page的上一个page和下一个page - depth
表示链表的深度,也就是链表节点的个数 - hiwat
表示high water mark(最高水位标记) - POOL_BOUNDARY
标识了当前autoreleasePool池的起始位置(哨兵对象);
thread_data_t
struct thread_data_t
{
#ifdef __LP64__
pthread_t const thread;
uint32_t const hiwat;
uint32_t const depth;
#else
pthread_t const thread;
uint32_t const hiwat;
uint32_t const depth;
uint32_t padding;
#endif
};
6.1.2 autoreleasePoolPage实现原理
6.1.2.1 push
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
分为三种:
- 当前还没有page
- 当前page已满
- 直接加入到当前的page
根据不同的情况调用不同的方法, 比较特别的是传入的参数POOL_BOUNDARY
6.1.2.2 pop
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
page = hotPage();
if (!page) {
// Pool was never used. Clear the placeholder.
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
page = coldPage();
token = page->begin();
} else {
page = pageForPointer(token);
}
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
return popPage<false>(token, page, stop);
}
- 根绝传入的哨兵对象找到对应的位置
- 对上次执行push操作添加的所有对象依次发送release消息
- 回退next指针到正确的位置(到另一个哨兵对象的位置,一个page对象只有一个next指针,但是哨兵对象可以有多个)
6.2 对象的销毁——dealloc
销毁主要是析构&解除关联对象&释放对象引用计数和弱引用
完整过程如下:
其中weak置nil具体步骤如下:
👇
通过上述可知, 弱引用对象的所有元素, 通过out_of_line判断存放在定长数组/动态数组
所以weak置nil就是把所有的元素取出来, for循环遍历这些元素, 置nil。
然后把被弱引用的对象的weak_entry_t从弱引用表的weakEntries中删除
实现的源码如下:
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// zero out references
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
weak_entry_remove(weak_table, entry);
}
七、零零碎碎
- NS开头:表示代码所属公司, NextStep
- objc_class_old.mm应该是不用了
/***********************************************************************
* objc-class-old.m
* Support for old-ABI classes, methods, and categories.
**********************************************************************/
- sizeof和class_getInstanceSize只需要考虑结构体的内存对齐
而内存分配中, 增加了一条规则——大小必须是16的倍数
- sizeof是运算符,编译的时候就替换为常数.返回的是一个类型所占内存的大小.
- class_getInstanceSize传入一个类对象,返回一个对象的实例至少需要多少内存,它等价于sizeof.需要导入#import <objc/runtime.h>
- malloc_size返回系统实际分配的内存大小,需要导入#import <malloc/malloc.h>
- lldb打印的几个方法
p/print打印指针
po打印对象
memory read读取内存