TaggedPointer 指针

  • 指针地址 + 值
  • 怎么区分 taggedPointer ,在第 0 位的标识是1 代表是 taggedPointer 指针
  • 第 1 ~ 3 位的值标识 tagType,
  • 不需要执行 retain & release 方法,存放在常量区,系统控制释放
    1
    2
    3
    4
    5
    6
    7
    8
    9
    OBJC_TAG_NSAtom            = 0,
    OBJC_TAG_1 = 1,
    OBJC_TAG_NSString = 2,
    OBJC_TAG_NSNumber = 3,
    OBJC_TAG_NSIndexPath = 4
    OBJC_TAG_NSManagedObjectID = 5,
    OBJC_TAG_NSDate = 6
    ...

retain

  1. 传入的参数有 isa_t ,判断不是 nonpointer 类型的 isa,直接操作 SideTables
    sidetables 不是一张表,如果只有一张,全局对象都需要多线程访问,效率底下,又不能一个对象一张表

    模拟器是64张,真机是8张,源码中显示的限制

    1
    2
    3
    4
    5
    sturct SideTable {
    spinlock_t lock; // 自旋锁
    RefcountMap refcnts; // 引用计数表
    weak_table_t weak_table; // 弱引用表
    }
  2. 判断是否正在释放

  3. 从 isa 中找到 extra_rc 的位置,引用计算 +1 ,如果 extra_rc 满了不够用了,需要创建 sideTable 并在 extra_rc 标记,

    为什么会吧 extra_rc 的数据一半放入散列表,为了在后续 retain & release 操作的时候尽可能不访问散列表,(开锁解锁耗时)

问题

1
2
3
4
NSObject *obj = [NSObject alloc];
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)obj))
//这个打印值为1

autorelease

  1. autoReleasePool
  2. autoreleasepage 存放指针的栈结构
  3. page 与 page 之间是双向链表结构
  4. 和线程有关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
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 用来验证 autoreleasePoolPage 的结构是否完整
  • next 指向最新添加的 autorelease 对象的下一个位置,初始化时指向 begin()
  • thread 指当前线程
  • parent 指向父结点,第一个 page 的父结点指向 nil
  • child 指向子结点,最后一个 page 的子结点指向 nil
  • depth 表示深度,从 0 开始,往后递增 1
  • hiwat 是 high water mark 最大入栈数量标记

问题

压栈过程(push)

  1. 一个 autoreleasepool 多个 page 只有一个哨兵对象,放在最开始的位置,为了在 pop 的时候存在边界
  2. 在 @autoreleasepool 在 push 的时候,先放入一个哨兵对象,创建一个 page ,并标记当前为 hotPage
  3. 后续再添加对象入栈,如果满了就创建 child page ,继续压栈;

出栈过程(pop)

1.顺序是和出栈相反,do while 循环,从 child 往 parent 的方向,向 page 里面的对象发送 release 消息,直到找到 parent 为空的 page 并且找到哨兵对象停止,其中一次销毁 page 对象

runloop

问题

  1. runloop 是什么?

    __CFRunLoop 是一个结构体
    一个 runloop 对象对应多个 runloopMode 一个 mode 对应多个 modeItem ,item 可以是 timer & source & observer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};

  1. runloop 能干什么?
  2. runloop 和线程之间的关系
  • 从 getMainRunLoop 源码中得出,主线程的 runloop 和主线程对应,是一个全局字典的方式映射存储的,key 是线程,value 是 CFRunloopRef 对象