ARC

https://blog.sunnyxx.com/2014/10/15/behind-autorelease/

https://juejin.im/post/5a66e28c6fb9a01cbf387da1

https://www.jianshu.com/p/f87f40592023

文档里关于autorelease的描述

Decrements the receiver’s retain count at the end of the current autorelease pool block.

NSAutoreleasePool

NSAutoreleasePool object contains objects that have received an autorelease message and when drained it sends a release message to each of those objects. Thus, sending autorelease instead of release to an object extends the lifetime of that object at least until the pool itself is drained

NSAutoreleasePool对象包含了已经收到autorelease消息的对象,当NSAutoreleasePool对象release时,它会给它包含的每一个对象发送release消息。因此没使用autoreleasee替代release相当于延长了对象的生命周期直到NSAutoreleasePool对象release为止。

The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event.

在主线程里,当每一个runloop事件循环开始的时候,The Application Kit 会自动创建autorelease pool,事件循环结束时会释放这个autorelease pool。也就是说,在没有手加autorelease pool的情况下,autorelease对象是在当前的runloop迭代结束时释放的。

新建一个工程,在终端里cd到main.m所在的目录下,使用下面的命令:

clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk

在目录下会生成一个main.cpp的文件。在这个文件的最下面可以看到:

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

在文件中搜索__AtAutoreleasePool

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

我们使用@autoreleasepool{}来使用一个AutoreleasePool,随后编译器将其改写成下面的样子:

void * context = objc_autoreleasePoolPush();

objc_autoreleasePoolPop(context)

上面两个函数的实现可以在runtime源码的NSObject.mm文件里找到:

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

void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

而这两个函数都是对AutoreleasePoolPage的简单封装,所以自动释放机制的核心就在于这个类。AutoreleasePoolPage是一个C++实现的类。

image
  • AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)
  • AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
  • 上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置
  • 一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入
  • AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)

一个AutoreleasePoolPage对象内存如下图:


image

我们接下来看看 AutoreleasePoolPage 的 push 函数的作用和执行过程, 一个 push 操作其实就是往 AutoreleasePoolPage 中的 next 位置插入一个 POOL_BOUNDARY ,并且返回插入的 POOL_BOUNDARY 的内存地址。这个地址在执行 pop 操作的时候作为函数的入参。

#   define POOL_BOUNDARY nil
static inline void *push() 
    {
        id *dest;
        if (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;
    }

push 函数通过调用 autoreleaseFast 函数来执行具体的插入POOL_BOUNDARY操作。

 static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }

autoreleaseFast 函数在执行一个具体的插入操作时,分别对三种情况进行了不同的处理:

  • 当前 page 存在且没有满时,直接将对象添加到当前 page 中,即 next 指向的位置;
  • 当前 page 存在且已满时,创建一个新的 page ,并将对象添加到新创建的 page 中;
  • 当前 page 不存在时,即还没有 page 时,创建第一个 page ,并将对象添加到新创建的 page 中。

每当进行一次objc_autoreleasePoolPush调用时,runtime向当前的AutoreleasePoolPage中add进一个哨兵对象,值为0(也就是个nil),那么这一个page就变成了下面的样子:


image

objc_autoreleasePoolPush的返回值正是这个哨兵对象的地址,被objc_autoreleasePoolPop(void *context)作为入参,于是:

  • 根据传入的哨兵对象地址找到哨兵对象所处的page
  • 在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次- release消息,并向回移动next指针到正确位置
  • 从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page

通过Object.mm源文件,我们可以找到 -autorelease 方法的实现,它最终也是调用了AutoreleasePoolPage的autorelease函数:

public:
    static inline id autorelease(id obj)
    {
        assert(obj);
        assert(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);
        assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }

它跟 push 操作的实现非常相似。只不过 push 操作插入的是一个 POOL_BOUNDARY ,而 autorelease 操作插入的是一个具体的 autoreleased 对象。

在MRC下:

使用alloc/new/copy/mutable名称开头的方法意味着自己生成并持有对象:

{
   id obj = [[NSObject alloc] init]; 
}

使用alloc/new/copy/mutable以外的方法获取的对象,是非自己生成的对象:

{
    //取得非自己生成的对象,但obj不持有对象
    id obj = [NSArray array];
    //obj持有对象
    [obj retain]
}

ARC下,id类型和对象类型必须附加所有权修饰符:

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleaseing

__strong是id类型和对象类型默认的所有权修饰符,在ARC下

{
   id obj = [[NSObject alloc] init];
}

上面的代码和下面的代码是一样的

{
   id __strong obj = [[NSObject alloc] init];
}

__strong修饰的变量在超出其变量作用域时,会释放其赋予的对象,上面的代码和下面的MRC下的代码是一样的:

{
    id obj = [[NSObject alloc] init];
    ...
    [obj release];
}

对于非自己生成的对象:

{
    id __strong obj = [NSArray array];
}

在MRC下,上面的代码可记述如下:

{
    id obj = [[NSArray array] retain];
    ...
    [obj release];
}

ARC下不能使用autorelease和NSAutoreleasePool,使用__autoreleasing替代autorelease,使用@autoreleasepool{}替代和NSAutoreleasePool。

@autoreleasepool{
    id __autoreleasing obj = [[NSObject alloc] init];
}

对象赋值给__autoreleaseing修饰的变量等价于在ARC无效时调用对象的autorelease方法。
如果使用非自己生成的方法获取对象:

@autoreleasepool{
    id __strong obj = [NSArray array];
}
    

上面的[NSArray array]方法已经将生成的对象放入了自动释放池,因此obj不需要使用__autoreleasing将其放入自送释放池了。

+ (id)
{
    id __strong obj = [NSArray new];
    rerurn obj;
}

上面的代码里,return使得obj变量超出其作用域,所以obj持有的对象会自动释放,但该对象作为函数的返回值,编译器会自动将其注册到autoreleasepool里。
id指针和对象的指针在没有显示指定时会被附加上__autoreleasing修饰符。
假设有一个方法的声明:

- (BOOL)perfomrOperationWithError:(NSError **)error

对象的指针默认修饰符是__autoreleasing,所以它等价于下面的代码:

- (BOOL)perfomrOperationWithError:(NSError *_autoreleasing*)error

把对象赋值给__weak修饰的变量时,会把对象的地址作为键值,把weak修饰的变量的地址作为值注册到weak表中,weak表作为散列表实现。当对象释放时,从weak表中获取以对象地址为键值的记录,通过记录的地址将
__weak修饰的变量置nil。

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

推荐阅读更多精彩内容