iOS 内存管理

1 面试:全局变量和局部变量在内存中是否有区别?如果有,是什么区别?

局部变量定义在局部的空间,全局变量定义在全局的区域,存储的区域不一样

2 block 是否可以直接修改全局变量?

可以,全局变量作用空间特别大,是公共的

3静态区安全测试

全局静态变量可以修改 只对文件有效,不是对类有效
新建一个LGPerson的类

//.h文件
#import <Foundation/Foundation.h>

static int personNum = 100;

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson : NSObject

- (void)run;
+ (void)eat;
@end


//.m文件
@implementation LGPerson
- (void)run{
    personNum ++;
    NSLog(@"LGPerson内部:%@-%p--%d",self,&personNum,personNum);
}

+ (void)eat{
    personNum ++;
    NSLog(@"LGPerson内部:%@-%p--%d",self,&personNum,personNum);
}

- (NSString *)description{
    return @"";
}

@end

再新建一个LGPerson的一个分类

#import "LGPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson (LG)
- (void)cate_method;
@end


//.m文件
@implementation LGPerson (LG)
- (void)cate_method{
    NSLog(@"LGPerson内部:%@-%p--%d",self,&personNum,personNum);
}

@end

调用

// 100 可以修改
    // 只针对文件有效 -
    NSLog(@"vc:%p--%d",&personNum,personNum); // 100
    personNum = 10000;
    NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
    [[LGPerson new] run]; // 100 + 1 = 101
    NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
    [LGPerson eat]; // 102
    NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
   
    [[LGPerson alloc] cate_method];

打印结果为

2020-11-07 12:10:39.026774+0800 001---五大区Demo[5285:436872] vc:0x101bc23cc--100
2020-11-07 12:10:39.027201+0800 001---五大区Demo[5285:436872] vc:0x101bc23cc--10000
2020-11-07 12:10:39.027532+0800 001---五大区Demo[5285:436872] LGPerson内部:-0x101bc23b0--101
2020-11-07 12:10:39.027790+0800 001---五大区Demo[5285:436872] vc:0x101bc23cc--10000
2020-11-07 12:10:39.028165+0800 001---五大区Demo[5285:436872] LGPerson内部:LGPerson-0x101bc23b0--102
2020-11-07 12:10:39.028627+0800 001---五大区Demo[5285:436872] vc:0x101bc23cc--10000
2020-11-07 12:10:39.029702+0800 001---五大区Demo[5285:436872] LGPerson内部:-0x101bc23d0--100

Tagged Pointer

先看一个面试题

- (void)taggedPointerDemo {
  
    self.queue = dispatch_queue_create("com.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"cooci"];
             NSLog(@"%@",self.nameStr);
        });
    }
}



- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    // 多线程
    // setter getter
    /**
     retian newvalue
     realase oldvalue
     taggedpointer 影响
     */
    
    NSLog(@"来了");
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"cooci_和谐学习不急不躁"];
            NSLog(@"%@",self.nameStr);
        });
    }
}

运行taggedPointerDemo,代码运行没问题,点击运行另一块代码,项目崩溃,之前多线程部分讲到新值替换旧值的一瞬间可能会为空,导致程序崩溃,那么为什么上面的没有问题,下面的崩溃呢?添加断点,打印得知:
当self.nameStr为cooci时,类型为NSTaggedPointerString


屏幕快照 2020-11-07 下午10.46.08.png

当self.nameStr为cooci_和谐学习不急不躁时,类型为NSCFString,

屏幕快照 2020-11-07 下午10.50.42.png

打开源码,搜索release方法

__attribute__((aligned(16), flatten, noinline))
id 
objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}


__attribute__((aligned(16), flatten, noinline))
void 
objc_release(id obj)
{
    if (!obj) return;
    if (obj->isTaggedPointer()) return;
    return obj->release();
}

可以发现不会对TaggedPointer类型进行release,retain操作,一般是在8到10位左右长度的小对象,编译读取的时候更加直接,释放是由系统直接回收

总结:

1:Tagged Pointer专⻔用来存储小的对象,例如NSNumber和NSDate
2:Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再 是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储 在堆中,也不需要malloc和free
3.在内存读取上有着3倍的效率,创建时比以前快106倍。

NONPOINTER_ISA

NONPOINTER_ISA同样是苹果公司对于内存优化的一种方案。用 64 bit 存储一个内存地址显然是种浪费,毕竟很少有那么大内存的设备。于是可以优化存储方案,用一部分额外空间存储其他内容。isa 指针第一位为 1 即表示使用优化的 isa 指针,这里列出在x86_64架构下的 64 位环境中 isa 指针结构,arm64的架构会有所差别。

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    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
    };
#endif
};


nonpointer:示是否对isa开启指针优化。0代表是纯isa指针,1代表除了地址外,还包含了类的一些信息、对象的引用计数等
has_assoc:关联对象标志位,0没有,1存在。
has_cxx_dtor:该对象是否有C++或Objc的析构器,如果有析构函数,则需要做一些析构的逻辑处理,如果没有,则可以更快的释放对象。
shiftcls:存在类指针的值,开启指针优化的情况下,arm64位中有33位来存储类的指针
magic:判断当前对象是真的对象还是一段没有初始化的空间
weakly_referenced:是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象释放的更快。
deallocating:标志是否正在释放内存。
has_sidetable_rc:是否有辅助的引用计数散列表。当对象引⽤技术⼤于 10 时,则需要借⽤该变量存储进位。
extra_rc:表示该对象的引⽤计数值,实际上是引⽤计数值减 1,例如,如果对象的引⽤计数为 10,那么 extra_rc 为 9。如果引⽤计数⼤于 10,则需要使⽤到下⾯的 has_sidetable_rc。

内存管理

retain是如何处理的

查看源码
retain方法会调用方法rootRetain,

objc_object::retain()
{
    ASSERT(!isTaggedPointer());

    if (fastpath(!ISA()->hasCustomRR())) {
        return rootRetain();
    }

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}

继续查看方法rootRetain

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    // retain 引用计数处理
    // 
    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        // 散列表的引用计数表 进行处理 ++
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return (id)this;
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

    if (slowpath(transcribeToSideTable)) {
        // Copy the other half of the retain counts to the side table.
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}

首先判断是不是nonpointer isa ,如果不是,引用计数存储在散列表里面
这个散列表包含哪些东西呢?我们进源码看一下:

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);
};

可以看到散列表包含一把锁 lock,引用计数表,弱引用表。
在代码中我们看到有SideTables,可以知道里面有多张散列表,出于安全和性能问题,里面有多张表。数量不得知,大概64张。
通过哈希结构存储多张散列表。

retain是对引用计数进行处理,也是对散列表的引用计数表进行处理 ++ --。
如果是nonpointer isa,调用addc,对bits里面的位数进行操作

newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);

这个地方如果超负荷的话,就会对散列表进行操作,会把原来的一半存储在extra_rc里面,另一半存储在散列表里面

if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }

这里优先isa,是因为extra_rc比散列表的操作要快

release操作

我们查看源码,发现release方法会调用rootRelease方法。

objc_object::release()
{
    ASSERT(!isTaggedPointer());

    if (fastpath(!ISA()->hasCustomRR())) {
        rootRelease();
        return;
    }

    ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
}

继续查看rootRelease方法,其原理与retain一致,这里需要注意一点,就是dealloc方法是在哪里调用的呢?
是在release调用的时候,当引用计数为0的时候,会通过objc_msgSend方式调用

objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return false;
            if (sideTableLocked) sidetable_unlock();
            return sidetable_release(performDealloc);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        if (slowpath(carry)) {
            // don't ClearExclusive()
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits)));

    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;

 underflow:
    // newisa.extra_rc-- underflowed: borrow from side table or deallocate

    // abandon newisa to undo the decrement
    newisa = oldisa;

    if (slowpath(newisa.has_sidetable_rc)) {
        if (!handleUnderflow) {
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }

        // Transfer retain count from side table to inline storage.

        if (!sideTableLocked) {
            ClearExclusive(&isa.bits);
            sidetable_lock();
            sideTableLocked = true;
            // Need to start over to avoid a race against 
            // the nonpointer -> raw pointer transition.
            goto retry;
        }

        // Try to remove some retain counts from the side table.        
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        // To avoid races, has_sidetable_rc must remain set 
        // even if the side table count is now zero.

        if (borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            if (!stored) {
                // Inline update failed. 
                // Try it again right now. This prevents livelock on LL/SC 
                // architectures where the side table access itself may have 
                // dropped the reservation.
                isa_t oldisa2 = LoadExclusive(&isa.bits);
                isa_t newisa2 = oldisa2;
                if (newisa2.nonpointer) {
                    uintptr_t overflow;
                    newisa2.bits = 
                        addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
                    if (!overflow) {
                        stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, 
                                                       newisa2.bits);
                    }
                }
            }

            if (!stored) {
                // Inline update failed.
                // Put the retains back in the side table.
                sidetable_addExtraRC_nolock(borrowed);
                goto retry;
            }

            // Decrement successful after borrowing from side table.
            // This decrement cannot be the deallocating decrement - the side 
            // table lock and has_sidetable_rc bit ensure that if everyone 
            // else tried to -release while we worked, the last one would block.
            sidetable_unlock();
            return false;
        }
        else {
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }

    // Really deallocate.

    if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
        // does not actually return
    }
    newisa.deallocating = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);

    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}

retainCount

先看一个面试题:

        NSObject *objc = [NSObject alloc]; // 0
        NSLog(@"%ld",objc.retainCount); // 1
        NSLog(@"%ld",objc.retainCount); // 1

两次打印结果都为1,那么这里请问alloc出来的对象引用计数为1 是否正确?答案错误,alloc出来的对象引用计数为0.

因为alloc 源码里没有对引用计数进行操作,那我们看下retainCount的源码,bits.extra_rc为0,可以发现alloc出来的对象引用计数为0,打印出来为1是因为这里会默认给他一个1。再次打印一次retainCount还是为1,是因为这里的bits.extra_rc并没有存值,还是为0,所以打印出来的还是1。

inline uintptr_t 
objc_object::rootRetainCount() // 1
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {
        // bits.extra_rc = 0;
        // 
        uintptr_t rc = 1 + bits.extra_rc; // isa
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock(); // 散列表
        }
        sidetable_unlock();
        return rc; // 1
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

dealloc

下面我们分析dealloc操作源码,这里对调用方法rootDealloc,然后是object_dispose方法

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

object_dispose方法

id 
object_dispose(id obj)
{
    if (!obj) return nil;
    // weak
    // cxx
    // 关联对象
    // ISA 64
    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

在free方法之前走方法objc_destructInstance

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;
}

然后走方法clearDeallocating,在这里进行散列表清空,如果含有弱引用计数,调用方法clearDeallocating_slow,继续清理引用计数表

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}
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();
}

总结一下上面的流程可以如下所示:


LG_Cooci_dealloc.png

强引用

我们知道打破block强引用的话,可以用weakSelf解决,self指向block,block指向weakSelf,来打破循环引用,注意一点,这个block捕获的是weakSelf这个临时变量的指针地址,不是这个对象,weakSelf和self是两个不同的指针地址,指向了同一个对象,所以打破了这里的循环引用。
self -> block -> weakSelf(临时变量的指针地址)

下面我们再看一个例子,这里会形成一个强引用,self持有timer,timer持有self

self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];

如果我们也像上面那样操作用weakSelf是否能解决呢?改成下面这样

 __weak typeof(self) weakSelf = self; // weak
     self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];

答案是不能,因为这里timer强持有的事weakSelf指针指向的对象,也就是self指针指向的对象。

自动释放池

自动释放池始于MRC时代,主要是用于 自动 对 释放池内 对象 进行引用计数-1的操作,即自动执行release方法,在MRC中使用autoreleasepool必须在代码块内部手动为对象调用autorelease把对象加入到的自动释放池,系统会自动在代码块结束后,对加入自动释放池中的对象发送一个release消息.无需手动调用release.

通过clang生成cpp文件,查看

int main(int argc, const char * argv[]) {
    @autoreleasepool {
      }
}

在cpp文件显示为这个结构体的调用,也就是用走方法__AtAutoreleasePool和~__AtAutoreleasePool()

 struct __AtAutoreleasePool {
   __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
   ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
   void * atautoreleasepoolobj;
 };

先看方法objc_autoreleasePoolPush,搜索源码

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

继续往下点击

屏幕快照 2020-11-09 下午11.20.27.png

这里看到继承自AutoreleasePoolPageData,我们看下AutoreleasePoolPageData是什么

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    magic_t const magic; // 16
    __unsafe_unretained id *next; //8
    pthread_t const thread; // 8
    AutoreleasePoolPage * const parent; //8
    AutoreleasePoolPage *child; //8
    uint32_t const depth; // 4
    uint32_t hiwat; // 4

    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)
    {
    }
};
struct magic_t {
    static const uint32_t M0 = 0xA1A1A1A1; // 静态变量不在这里存储
#   define M1 "AUTORELEASE!"
    static const size_t M1_len = 12;
    uint32_t m[4];    //4*4 = 16

    magic_t() {
        ASSERT(M1_len == strlen(M1));
        ASSERT(M1_len == 3 * sizeof(m[1]));

        m[0] = M0;
        strncpy((char *)&m[1], M1, M1_len);
    }

    ~magic_t() {
        // Clear magic before deallocation.
        // This prevents some false positives in memory debugging tools.
        // fixme semantically this should be memset_s(), but the
        // compiler doesn't optimize that at all (rdar://44856676).
        volatile uint64_t *p = (volatile uint64_t *)m;
        p[0] = 0; p[1] = 0;
    }

AutoreleasePoolPageData是个结构体,里面有自己的一些成员变量,里面的字段的意义如下

magic 用来校验 AutoreleasePoolPage 的结构是否完整;
• next 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向
begin() ;
• thread 指向当前线程;
• parent 指向父结点,第一个结点的 parent 值为 nil ;
• child 指向子结点,最后一个结点的 child 值为 nil ;
• depth 代表深度,从 0 开始,往后递增 1;
• hiwat 代表 high water mark 最大入栈数量标记

总结一下:

AutoreleasePoolPage其实就是一个双向链表结构,AutoreleasePoolPage(自动释放池页) 用来存放 autorelease 的对象,但是每一页的大小是有限制的,假如某个AutoreleasePoolPage页中需要存放的autorelease 的对象过多,一页存放不完,所以它就需要指向父结点点,在指向父结点里的AutoreleasePoolPage页中继续存放.
那么每一页大小是多少呢?

class AutoreleasePoolPage : private AutoreleasePoolPageData
{
    friend struct thread_data_t;

public:
    static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MIN_SIZE;  // size and alignment, power of 2
.
.
.
#endif
}

有个PAGE_MAX_SIZE,点击进去是4096.原来每一页AutoreleasePoolPage可以存放4096个字节.一共4096个字节, (4096 - AutoreleasePoolPage 中自己成员变量所占的字节)/每个对象中所占的字节. (4096 - 56)/8 = 505. 好的,每一AutoreleasePoolPage可以存放505个对象。


image

一个autoreleasePoolPage最多能添加504个8字节对象是否正确?

第一页是504+1个对象,这一个不是你添加进去的,是一个标记,是边界,也就是第一页最多能添加504个对象,其他页最多能添加505个对象。

总结

在APP中,整个主线程是运行在一个自动释放池中的。

main函数中的自动释放池的作用:这个池块给出了一个pop点来显式的告诉我们这里有一个释放点,如果你的main在初始化的过程中有别的内容可以放在这里。

使用@autoreleasepool标记,调用push()方法。

没有hotpage,调用(),设置EMPTY_POOL_PLACEHOLDER。

因为设置了EMPTY_POOL_PLACEHOLDER,所以会设置本页为hotpage,添加边界标记POOL_BOUNDARY,最后添加obj。

继续有对象调用autorelease,此时已经有了page,调用page->add(obj)。

如果page满了,调用autoreleaseFullPage()创建新page,重复第6点。

到达autoreleasePool边界,调用pop方法,通常情况下会释放掉POOL_BOUNDARY之后的所有对象

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容

  • 本文中的源代码来源:需要下载Runtime的源码,官方的工程需要经过大量调试才能使用。这里有处理好的objc4-7...
    shen888阅读 827评论 0 3
  • 一 iOS程序内存布局 二 Tagged Pointer内存地址优化 三 MRC概念讲解 四 引用计数的存储 五 ...
    当前明月阅读 941评论 0 2
  • 学习了好久的iOS内存管理,一直是断断续续的,现在有时间找了个机会总结了一下,有时候时间久了好多知识点就会遗忘,希...
    YYFast阅读 2,075评论 1 9
  • 1、内存布局 stack:方法调用 heap:通过alloc等分配对象 bss:未初始化的全局变量等。 data:...
    AKyS佐毅阅读 1,583评论 0 19
  • 1. 对象与类 1.1 对象 对象(Class或id)内部只有一个isa_t联合体指针。isa_t联合体内部只有两...
    我才是臭吉吉阅读 706评论 0 2