iOS 中AutoreleasePool实现原理下

我们在分析自动释放池底层源码前,我们先来创建一个新工程,查看main函数中系统创建的自动释放池最终转换为底层c++代码的情况

main函数

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
    }
    return 0;
}

我们执行命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.mmain.m文件转换为底层c++文件,核心代码如下:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ {
        __AtAutoreleasePool __autoreleasepool;
    }
    return 0;
}

从上面的转换我们可以看出,将创建自动释放池的代码@autoreleasepool{}转换为了底层代码__AtAutoreleasePool __autoreleasepool;

__AtAutoreleasePool __autoreleasepool;声明了一个__AtAutoreleasePool类型的变量,我们再来看看__AtAutoreleasePool的类型,我们通过在main.cpp文件中搜索,发现__AtAutoreleasePool为一个结构体对象,源码如下:

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

struct __AtAutoreleasePool {
    // 构造函数,在初始化创建结构体的时候调用
  __AtAutoreleasePool() {
      atautoreleasepoolobj = objc_autoreleasePoolPush();
  }
    
    // 析构函数,在结构体对象销毁的时候调用
  ~__AtAutoreleasePool() {
      objc_autoreleasePoolPop(atautoreleasepoolobj);
  }
    
  void * atautoreleasepoolobj;
};

我们发现在__AtAutoreleasePool结构体的构造函数中调用了objc_autoreleasePoolPush()函数,在析构函数中调用了objc_autoreleasePoolPop()函数

我们知道创建自动释放池除了使用@autoreleasepool{}这种方式,我们还可以使用下面的面向对象的语法:

    // 创建自动释放池
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    NSObject *obj = [[NSObject alloc] init];
    
    // 将对象添加到自动释放池
    [obj autorelease];
    
    // 销毁自动释放池
    [pool drain];

这两种创建自动释放池方式的等价操作如图:

image

通过使用NSAutoreleasePool的方式创建的自动释放池和使用@autoreleasepool{}这种方式最终生成的结构体__AtAutoreleasePool进行对比,我们可以知道

@autoreleasepool { // 大括号作用域前调用:objc_autoreleasePoolPush()

    NSObject *obj = [[NSObject alloc] init];
    [obj autorelease];
    
} // 大括号作用域结束前调用:objc_autoreleasePoolPop()

@autoreleasepool {}方式创建的自动释放池,在作用域大括号的开始前会调用objc_autoreleasePoolPush()函数,在作用域大括号的结束前会调用objc_autoreleasePoolPop()函数

然后类比使用NSAutoreleasePool创建自动释放池,在执行NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]时会调用objc_autoreleasePoolPush()函数,在执行[pool drain]时会调用objc_autoreleasePoolPop()函数,等价于下面的代码

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    /* 等同于objc_autoreleasePoolPush() */
    
    NSObject *obj = [[NSObject alloc] init];
    
    [obj autorelease];
    /* 等同于objc_autorelease(obj) */
    
    [pool drain];
    /* 等同于objc_autoreleasePoolPop(pool) */

上面我们一直提到objc_autoreleasePoolPushobjc_autoreleasePoolPop函数,接下来我们再来看看这两个函数的具体作用,源码查看路径:objc4 -> NSObject.mm -> void * objc_autoreleasePoolPush(void)

源码对这两个函数的定义如下:

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

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

通过上面的源码我们看一看到Push和Pop函数最终都是通过AutoreleasePoolPage来调用的,接下来我们再来看看AutoreleasePoolPage的源码,源码查看路径:objc4 -> NSObject.mm -> AutoreleasePoolPage

通过源码查看我们发现AutoreleasePoolPage是一个类,AutoreleasePoolPage类的核心源码如下:

/***********************************************************************
   Autorelease pool implementation

   A thread's autorelease pool is a stack of pointers. 
   Each pointer is either an object to release, or POOL_BOUNDARY which is 
     an autorelease pool boundary.
   A pool token is a pointer to the POOL_BOUNDARY for that pool. When 
     the pool is popped, every object hotter than the sentinel is released.
   The stack is divided into a doubly-linked list of pages. Pages are added 
     and deleted as necessary. 
   Thread-local storage points to the hot page, where newly autoreleased 
     objects are stored. 
**********************************************************************/
class AutoreleasePoolPage 
{
     // ************** AutoreleasePoolPage类的七个成员变量 **************
     
     magic_t const magic;
    
    // 这个指针是指Page可以存放下一个autorelease对象的地址值
    id *next;
    
    // 当前所在的线程
    pthread_t const thread;
    
    // 指向上一个page对象
    AutoreleasePoolPage * const parent;
    
    // 指向下一个page对象
    AutoreleasePoolPage *child;
    
    // 自动释放池Page的深度
    uint32_t const depth;
    uint32_t hiwat;
        
    // ************** AutoreleasePoolPage类的核心函数 **************
    
    // autorelease:对象调用autorelease方法,将对象添加到自动释放池中
public:
    static inline id autorelease(id obj)
    {
        assert(obj);
        assert(!obj->isTaggedPointer());
        
        // 通过autoreleaseFast函数,找到这个对象的内存地址
        id *dest __unused = autoreleaseFast(obj);
        
        assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        
        return obj;
    }
    
    // AutoreleasePoolPage类中的 push 函数
    static inline void *push() 
    {
        id *dest;
        if (DebugPoolAllocation) {
            // Each autorelease pool starts on a new pool page.
            
            // 当没有Page时,创建一个Page,将POOL_BOUNDARY放入到page中入栈,然后返回这个位置的内存地址值
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            // 已经有Page
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        
        // 返回值,也就是Page开始存放对象时POOL_BOUNDARY入栈时对应Page中的内存地址
        return dest;
    }
    
    
    // AutoreleasePoolPage类中的Pop函数,这里的token就是POOL_BOUNDARY
    // pop函数会从栈底开始一直往上释放对象,直到释放到POOL_BOUNDARY的位置截止
    static inline void pop(void *token) 
    {
        
        AutoreleasePoolPage *page;
        id *stop;

        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            
            // 判断是否为当前的自动释放池
            if (hotPage()) {
                // Pool was used. Pop its contents normally.
                // Pool pages remain allocated for re-use as usual.
                pop(coldPage()->begin());
            } else {
                // Pool was never used. Clear the placeholder.
                setHotPage(nil);
            }
            return;
        }

        page = pageForPointer(token);
        
        // 将token(POOL_BOUNDARY)赋值给stop指针
        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 (PrintPoolHiwat) printHiwat();

        // 这里我们可以看到,自动释放池释放对象,是通过一个终止释放的标记来从后往前释放池中的对象
        page->releaseUntil(stop);

        
        // memory: delete empty children
        if (DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top) 
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } 
        else if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }
}

AutoreleasePoolPage类的核心源码可以知道,当执行objc_autoreleasePoolPush()函数时,就是调用AutoreleasePoolPage类的push函数,当执行objc_autoreleasePoolPop()函数就是调用AutoreleasePoolPage类的pop函数,当调用对象的autorelease函数时就是调用AutoreleasePoolPage类的autorelease函数

我们从如下Autorelease pool的底层源码注释可知:

    Autorelease pool implementation

   A thread's autorelease pool is a stack of pointers. 
   Each pointer is either an object to release, or POOL_BOUNDARY which is 
     an autorelease pool boundary.
   A pool token is a pointer to the POOL_BOUNDARY for that pool. When 
     the pool is popped, every object hotter than the sentinel is released.
   The stack is divided into a doubly-linked list of pages. Pages are added 
     and deleted as necessary. 
   Thread-local storage points to the hot page, where newly autoreleased 
     objects are stored. 

自动释放池是由很多个AutoreleasePoolPage组成的一个双向链表的结构,并且每一个AutoreleasePoolPage中存放自动释放的对象都是以栈的形式存储的,如下图所示:

image

这里有一个比较关键的指针POOL_BOUNDARY

例如:当我们创建一个自动释放池,这时就有一个POOL_BOUNDARY指针入栈存储在AutoreleasePoolPage表中,我们可以通过下面的push函数验证:

static inline void *push() 
{
    id *dest;
    if (DebugPoolAllocation) {
        // Each autorelease pool starts on a new pool page.
        
        // 当没有Page时,创建一个Page,将POOL_BOUNDARY放入到page中入栈,然后返回这个位置的内存地址
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
        // 已经有Page
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    
    // 返回值,也就是Page开始存放对象时POOL_BOUNDARY入栈时对应Page中的内存地址
    return dest;
}

然后我们创建10个对象调用autorelease方法逐一添加到自动释放池中,autorelease函数:

public:
    static inline id autorelease(id obj)
    {
        assert(obj);
        assert(!obj->isTaggedPointer());
        
        // 通过调用autoreleaseFast函数,将obj对象添加至池中
        id *dest __unused = autoreleaseFast(obj);
        
        assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        
        return obj;
    }

autoreleaseFast函数:

static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            
            // 将obj对象添加到page中
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }

autoreleaseFast函数中,我们看到,不管是当前page没有满,还是当前page满了或者是还没有page,最终都调用了page->add(obj)语句将对象添加至池中

当需要销毁这10个对象时,这时需要调用pop函数,源码如下:

static inline void pop(void *token) 
    {
        
        AutoreleasePoolPage *page;
        id *stop;

        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            
            // 判断是否为当前的自动释放池
            if (hotPage()) {
                // Pool was used. Pop its contents normally.
                // Pool pages remain allocated for re-use as usual.
                pop(coldPage()->begin());
            } else {
                // Pool was never used. Clear the placeholder.
                setHotPage(nil);
            }
            return;
        }

        page = pageForPointer(token);
        
        // 将token(POOL_BOUNDARY)赋值给stop指针
        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 (PrintPoolHiwat) printHiwat();

        // 这里我们可以看到,自动释放池释放对象,是通过一个终止释放的标记来从后往前释放池中的对象
        page->releaseUntil(stop);

        
        // memory: delete empty children
        if (DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top) 
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } 
        else if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }

从上面的源码我们看到,调用pop函数有传一个参数token,这个token是一个指针,正是开始创建自动释放池时函数的返回值,这个值就是POOL_BOUNDARY,我们从编译出的c++文件源码可知

struct __AtAutoreleasePool {
    // 构造函数,在初始化创建结构体的时候调用
  __AtAutoreleasePool() {
        // 创建自动释放池时调用objc_autoreleasePoolPush函数,并返回atautoreleasepoolobj,这个就是入栈的`POOL_BOUNDARY`的地址
      atautoreleasepoolobj = objc_autoreleasePoolPush();
  }
    
    // 析构函数,在结构体对象销毁的时候调用
  ~__AtAutoreleasePool() {
        // 销毁自动释放池时调用`objc_autoreleasePoolPop`函数,将`POOL_BOUNDARY`地址传入
      objc_autoreleasePoolPop(atautoreleasepoolobj);
  }
   
   // atautoreleasepoolobj为指针类型
  void * atautoreleasepoolobj;
};

我们在来看pop函数,当把token地址赋值给stop指针后,执行了page->releaseUntil(stop)语句,我们来看看releaseUntil函数,源码如下:

// 自动释放池释放对象,从后往前开始释放,直到遇到stop地址停止释放
void releaseUntil(id *stop) 
{
    // Not recursive: we don't want to blow out the stack 
    // if a thread accumulates a stupendous amount of garbage
    
    // 开始循环释放对象
    while (this->next != stop) {
        // Restart from hotPage() every time, in case -release 
        // autoreleased more objects
        
        // 获取到当前正在使用的Page,也就是热Page
        AutoreleasePoolPage *page = hotPage();

        // fixme I think this `while` can be `if`, but I can't prove it
        while (page->empty()) {
            page = page->parent;
            setHotPage(page);
        }

        page->unprotect();
        
        // 取出`next`指针指向的对象地址
        id obj = *--page->next;
        
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
        page->protect();

        // 一直循环遍历释放对象,知道遇到POOL_BOUNDARY地址
        if (obj != POOL_BOUNDARY) {
            // 这里就是释放对象的最终位置
            objc_release(obj);
        }
    }

    setHotPage(this);

#if DEBUG
    // we expect any children to be completely empty
    for (AutoreleasePoolPage *page = child; page; page = page->child) {
        assert(page->empty());
    }
#endif
}

releaseUntil()函数中,我们看到了核心代码:

    // 开始循环释放对象
    while (this->next != stop) {
        // 一直循环遍历释放对象,知道遇到POOL_BOUNDARY地址
        if (obj != POOL_BOUNDARY) {
            // 这里就是释放对象的最终位置
            objc_release(obj);
        }
    }

在while循环中判断,如果地址不等于POOL_BOUNDARY,就执行objc_release()释放对象,直到地址为POOL_BOUNDARY停止释放,到这我们应该就明白POOL_BOUNDARY的作用了

上面Autorelease pool源码注释我们知道,自动释放池是由很多个AutoreleasePoolPage组成的双向链表结构,那每一个AutoreleasePoolPage又分配了多大的存储空间尼

我们通过前面的源码static size_t const COUNT = SIZE / sizeof(id);中的SIZE的定义

#define I386_PGBYTES            4096            /* bytes per 80386 page */

从宏定义中可以看出每页page有4096字节的大小,由于AutoreleasePoolPage类中声明了7个成员变量,每个成员变量占8字节,所以说每页page还有4040(4096-56)个字节大小用来存放对象,如果这一页数据存满了,那么就接着存放在下一页page中,以此类推

在上面的源码分析过程我们也有看到hotPagecoldPage函数,这两个很好理解,当前正在使用的page就是hotPage,前面已经存放满对象的page就是coldPage,这个后面代码打印当前自动释放池的情况,看输出结果就一目了然了

上面是通过Autorelease Pool底层源码进行的分析,下面我们再来通过测试代码来看看自动释放池的使用情况,这里由于ARCMRC使用Autorelease Pool的情况有点不同,这里我们就分开来看测试效果

我们先来看看MRC的测试情况

我们先新建一个工程,然后将Automatic Reference Counting改为NO,然后创建一个Person类,一个Dog类,测试代码如下:

Person

@interface Person : NSObject
{
    Dog *_dog;
}

- (void)setDog:(Dog *)dog;

- (Dog *)dog;
@end


@implementation Person

- (void)setDog:(Dog *)dog {
    if (dog != _dog) {
        [_dog release];
        dog = [dog retain];
        _dog = dog;
    }
}

- (Dog *)dog {
    return _dog;
}

- (void)dealloc {
    
    NSLog(@"%s", __func__);
    self.dog = nil;
    
    [super dealloc];
}
@end

Dog

@interface Dog : NSObject

@end


@implementation Dog

- (void)dealloc {
    [super dealloc];
    
    NSLog(@"%s", __func__);
}
@end

main函数

// _objc_autoreleasePoolPrint()函数可以打印出当前自动释放池的使用情况,在ARC和MRC下都可用
extern void _objc_autoreleasePoolPrint(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        // 注意:当前是MRC环境
                
        NSLog(@"%@", [NSThread currentThread]);
        
        _objc_autoreleasePoolPrint();
    }
    return 0;
}

main函数中,我们先来看看最简单的情况,就是没有任何对象添加到自动释放池时,我们使用_objc_autoreleasePoolPrint()函数来打印池中的情况,自动释放池打印如下:

objc[72048]: ##############
objc[72048]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[72048]: 1 releases pending.
objc[72048]: [0x103802000]  ................  PAGE  (hot) (cold)
objc[72048]: [0x103802038]  ################  POOL 0x103802038
objc[72048]: ##############

从上面的打印我们可以看出,1 releases pending,表示当前池中就一个对象,这个对象就是POOL 0x103802038,也就是上面所讲的POOL_BOUNDARY指针

接下来我们修改下main函数,我们创建对象调用autorelease添加到池中,然后创建对象不调用autorelease,对比看下差别,测试代码如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        // 注意:当前是MRC环境
                
        NSLog(@"%@", [NSThread currentThread]);
        
        _objc_autoreleasePoolPrint();
        
        NSLog(@"--------------------------------");
        
        Person *p1 = [[Person alloc] init];
        [p1 autorelease];
        
        _objc_autoreleasePoolPrint();
        
        NSLog(@"--------------------------------");
        
        Dog *d1 = [[Dog alloc] init];
        
        _objc_autoreleasePoolPrint();
    }
    return 0;
}

自动释放池打印如下:

objc[72119]: ##############
objc[72119]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[72119]: 1 releases pending.
objc[72119]: [0x100801000]  ................  PAGE  (hot) (cold)
objc[72119]: [0x100801038]  ################  POOL 0x100801038
objc[72119]: ##############
--------------------------------
objc[72119]: ##############
objc[72119]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[72119]: 2 releases pending.
objc[72119]: [0x100801000]  ................  PAGE  (hot) (cold)
objc[72119]: [0x100801038]  ################  POOL 0x100801038
objc[72119]: [0x100801040]       0x10060f850  Person
objc[72119]: ##############
--------------------------------
objc[72119]: ##############
objc[72119]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[72119]: 2 releases pending.
objc[72119]: [0x100801000]  ................  PAGE  (hot) (cold)
objc[72119]: [0x100801038]  ################  POOL 0x100801038
objc[72119]: [0x100801040]       0x10060f850  Person
objc[72119]: ##############

从上面的打印,我们可以看到,当我们不添加任何对象到池中时,池中就一个对象POOL_BOUNDARY,当我们创建一个Person对象添加到池中后,可以看到池中有了2个对象,除了最开始的POOL_BOUNDARY,还新增了0x10060f850 Person,然而当我们创建一个Dog对象,但是没有调用autorelease,我们发现Dog对象的地址没有添加到池中。从这个对比我们知道了,要想让对象添加到自动释放池,对象就需要调用autorelease方法

接下来我们来看下自动释放池嵌套使用的情况,测试代码如下:

extern void _objc_autoreleasePoolPrint(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        // 注意:当前是MRC环境
                
        NSLog(@"%@", [NSThread currentThread]);
        
        _objc_autoreleasePoolPrint();
        
        NSLog(@"--------------------------------");
        
        Person *p1 = [[Person alloc] init];
        [p1 autorelease];
        
        _objc_autoreleasePoolPrint();
        
        NSLog(@"--------------------------------");
        
        @autoreleasepool {
            Person *p2 = [[Person alloc] init];
            [p2 autorelease];
            
            Dog *d2 = [[Dog alloc] init];
            [d2 autorelease];
            
            _objc_autoreleasePoolPrint();
            
            NSLog(@"--------------------------------");
            
            @autoreleasepool {
                Person *p3 = [[Person alloc] init];
                [p3 autorelease];
                
                Cat *cat3 = [[Cat alloc] init];
                [cat3 autorelease];
                
                NSLog(@"--------------------------------");
                
                _objc_autoreleasePoolPrint();
            }
        }
    }
    return 0;
}

_objc_autoreleasePoolPrint()函数打印如下:

objc[72275]: ##############
objc[72275]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[72275]: 1 releases pending.
objc[72275]: [0x105800000]  ................  PAGE  (hot) (cold)
objc[72275]: [0x105800038]  ################  POOL 0x105800038
objc[72275]: ##############
--------------------------------
objc[72275]: ##############
objc[72275]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[72275]: 2 releases pending.
objc[72275]: [0x105800000]  ................  PAGE  (hot) (cold)
objc[72275]: [0x105800038]  ################  POOL 0x105800038
objc[72275]: [0x105800040]       0x1023057d0  Person
objc[72275]: ##############
--------------------------------
objc[72275]: ##############
objc[72275]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[72275]: 5 releases pending.
objc[72275]: [0x105800000]  ................  PAGE  (hot) (cold)
objc[72275]: [0x105800038]  ################  POOL 0x105800038
objc[72275]: [0x105800040]       0x1023057d0  Person
objc[72275]: [0x105800048]  ################  POOL 0x105800048
objc[72275]: [0x105800050]       0x10073af00  Person
objc[72275]: [0x105800058]       0x10073d3b0  Dog
objc[72275]: ##############
--------------------------------
objc[72275]: ##############
objc[72275]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[72275]: 8 releases pending.
objc[72275]: [0x105800000]  ................  PAGE  (hot) (cold)
objc[72275]: [0x105800038]  ################  POOL 0x105800038
objc[72275]: [0x105800040]       0x1023057d0  Person
objc[72275]: [0x105800048]  ################  POOL 0x105800048
objc[72275]: [0x105800050]       0x10073af00  Person
objc[72275]: [0x105800058]       0x10073d3b0  Dog
objc[72275]: [0x105800060]  ################  POOL 0x105800060
objc[72275]: [0x105800068]       0x10244f2f0  Person
objc[72275]: [0x105800070]       0x10244ee70  Cat
objc[72275]: ##############
--------------------------------
2020-02-19 10:48:27.081520+0800 aaaaaaa[72275:5367840] -[Cat dealloc]
2020-02-19 10:48:27.081564+0800 aaaaaaa[72275:5367840] -[Person dealloc]
2020-02-19 10:48:27.081606+0800 aaaaaaa[72275:5367840] -[Dog dealloc]
2020-02-19 10:48:27.081641+0800 aaaaaaa[72275:5367840] -[Person dealloc]
2020-02-19 10:48:27.081675+0800 aaaaaaa[72275:5367840] -[Person dealloc]

上面我们进行了3层自动释放池的嵌套操作,我们从最后一次执行_objc_autoreleasePoolPrint()打印可以看出,每当我们使用@autoreleasepool {}创建一个自动释放池,就会有一个POOL_BOUNDARY入栈,我们从下面打印可以看出

objc[72275]: ##############
objc[72275]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[72275]: 8 releases pending.
objc[72275]: [0x105800000]  ................  PAGE  (hot) (cold)
objc[72275]: [0x105800038]  ################  POOL 0x105800038
objc[72275]: [0x105800040]       0x1023057d0  Person
objc[72275]: [0x105800048]  ################  POOL 0x105800048
objc[72275]: [0x105800050]       0x10073af00  Person
objc[72275]: [0x105800058]       0x10073d3b0  Dog
objc[72275]: [0x105800060]  ################  POOL 0x105800060
objc[72275]: [0x105800068]       0x10244f2f0  Person
objc[72275]: [0x105800070]       0x10244ee70  Cat
objc[72275]: ##############

接下来我们再来看看当嵌套的自动释放池超出了作用域会有什么结果,测试代码如下:

extern void _objc_autoreleasePoolPrint(void);

int main(int argc, const char * argv[]) {
    
    @autoreleasepool { // 第一个自动释放池
        // insert code here...
        
        // 注意:当前是MRC环境
                
        NSLog(@"%@", [NSThread currentThread]);
        
        Person *p1 = [[Person alloc] init];
        [p1 autorelease];
    
        @autoreleasepool { // 第二个自动释放池
            Person *p2 = [[Person alloc] init];
            [p2 autorelease];
            
            Dog *d2 = [[Dog alloc] init];
            [d2 autorelease];
            
            @autoreleasepool { // 第三个自动释放池
                Person *p3 = [[Person alloc] init];
                [p3 autorelease];
                
                Cat *cat3 = [[Cat alloc] init];
                [cat3 autorelease];
                                
                _objc_autoreleasePoolPrint();
                
                NSLog(@"--------------------------------");
            }
            
            NSLog(@"第三个自动释放池超出了作用域");
            
            _objc_autoreleasePoolPrint();
            
            NSLog(@"--------------------------------");
        }
        
         NSLog(@"第二个自动释放池超出了作用域");
        
        _objc_autoreleasePoolPrint();
        
        NSLog(@"--------------------------------");
    }
    
    NSLog(@"第一个自动释放池超出了作用域");
    
    _objc_autoreleasePoolPrint();
    
    NSLog(@"--------------------------------");
    
    return 0;
}

终端打印结果如下:

objc[72410]: ##############
objc[72410]: AUTORELEASE POOLS for thread 0x1000ab5c0
objc[72410]: 8 releases pending.
objc[72410]: [0x105803000]  ................  PAGE  (hot) (cold)
objc[72410]: [0x105803038]  ################  POOL 0x105803038
objc[72410]: [0x105803040]       0x1007045c0  Person
objc[72410]: [0x105803048]  ################  POOL 0x105803048
objc[72410]: [0x105803050]       0x100704170  Person
objc[72410]: [0x105803058]       0x100700be0  Dog
objc[72410]: [0x105803060]  ################  POOL 0x105803060
objc[72410]: [0x105803068]       0x100702760  Person
objc[72410]: [0x105803070]       0x100700dc0  Cat
objc[72410]: ##############
--------------------------------
aaaaaaa[72410:5388081] -[Cat dealloc]
aaaaaaa[72410:5388081] -[Person dealloc]
aaaaaaa[72410:5388081] 第三个自动释放池超出了作用域
objc[72410]: ##############
objc[72410]: AUTORELEASE POOLS for thread 0x1000ab5c0
objc[72410]: 5 releases pending.
objc[72410]: [0x105803000]  ................  PAGE  (hot) (cold)
objc[72410]: [0x105803038]  ################  POOL 0x105803038
objc[72410]: [0x105803040]       0x1007045c0  Person
objc[72410]: [0x105803048]  ################  POOL 0x105803048
objc[72410]: [0x105803050]       0x100704170  Person
objc[72410]: [0x105803058]       0x100700be0  Dog
objc[72410]: ##############
--------------------------------
aaaaaaa[72410:5388081] -[Dog dealloc]
aaaaaaa[72410:5388081] -[Person dealloc]
aaaaaaa[72410:5388081] 第二个自动释放池超出了作用域
objc[72410]: ##############
objc[72410]: AUTORELEASE POOLS for thread 0x1000ab5c0
objc[72410]: 2 releases pending.
objc[72410]: [0x105803000]  ................  PAGE  (hot) (cold)
objc[72410]: [0x105803038]  ################  POOL 0x105803038
objc[72410]: [0x105803040]       0x1007045c0  Person
objc[72410]: ##############
--------------------------------
aaaaaaa[72410:5388081] -[Person dealloc]
aaaaaaa[72410:5388081] 第一个自动释放池超出了作用域
objc[72410]: ##############
objc[72410]: AUTORELEASE POOLS for thread 0x1000ab5c0
objc[72410]: 0 releases pending.
objc[72410]: [0x105803000]  ................  PAGE  (hot) (cold)
objc[72410]: ##############

我们从上面的打印可以看到,当第三个自动释放池超出了作用域(也就是执行完@autoreleasepool {}的结束大括号)后,在第三个自动释放池中创建的PersonCat对象就已经销毁了,我们从_objc_autoreleasePoolPrint()打印也可以看出,此时池中已经没有了第三个自动释放池的POOL_BOUNDARY地址,和PersonCat的地址了,也就是说第三个自动释放池也销毁了。当第二个自动释放池超出作用域,原理和第三个自动释放池一样。我们在看下main函数中系统创建的第一个自动释放池超出作用域后的情况,从打印0 releases pending.可以看出,此时池中已经没有任何对象了,第一个自动释放池超出作用域也销毁了。这也正好验证了上面咱们总结的结论:

当自动释放池超出作用域,则这个自动释放池就会销毁,当自动释放池销毁时,便会向池中的每一个对象发送release消息来销毁池中的对象。

接下来我们再来看看当添加到自动释放池中的对象引用计数大于1时,当自动释放池销毁时,引用计数大于1的对象是进行引用计数-1还是说直接释放对象,测试代码如下:

extern void _objc_autoreleasePoolPrint(void);

int main(int argc, const char * argv[]) {
    
    @autoreleasepool { // 第一个自动释放池
        // insert code here...
        
        // 注意:当前是MRC环境
                
        NSLog(@"%@", [NSThread currentThread]);
        
        @autoreleasepool { // 第二个自动释放池
            Person *p1 = [[[Person alloc] init] autorelease];
            Person *p2 = [[[Person alloc] init] autorelease];
            
            Dog *dog = [[[Dog alloc] init] autorelease]; // dog引用计数=1
            
            NSLog(@"%zd", [dog retainCount]); // 1
            
            [p1 setDog:dog]; // dog引用计数=2
            [p2 setDog:dog]; // dog引用计数=3
                    
            NSLog(@"%zd", [dog retainCount]); // 3
            
            _objc_autoreleasePoolPrint();
            
            NSLog(@"-------------------");
        }
        
        NSLog(@"---------第二个自动释放池超出作用域---------");
        
        _objc_autoreleasePoolPrint();
    }
    
    return 0;
}

终端打印数据如下:

13:44:18.661853+0800 aaaaaaa[72892:5442548] 1
13:44:18.661890+0800 aaaaaaa[72892:5442548] 3
objc[72892]: ##############
objc[72892]: AUTORELEASE POOLS for thread 0x1000ab5c0
objc[72892]: 5 releases pending.
objc[72892]: [0x100803000]  ................  PAGE  (hot) (cold)
objc[72892]: [0x100803038]  ################  POOL 0x100803038
objc[72892]: [0x100803040]  ################  POOL 0x100803040
objc[72892]: [0x100803048]       0x100622450  Person
objc[72892]: [0x100803050]       0x100620f20  Person
objc[72892]: [0x100803058]       0x100624990  Dog
objc[72892]: ##############
13:44:18.662157+0800 aaaaaaa[72892:5442548] -------------------
13:44:18.662209+0800 aaaaaaa[72892:5442548] -[Person dealloc]
13:44:18.662241+0800 aaaaaaa[72892:5442548] -[Person dealloc]
13:44:18.662280+0800 aaaaaaa[72892:5442548] -[Dog dealloc]
13:44:18.662342+0800 aaaaaaa[72892:5442548] -第二个自动释放池超出作用域-
objc[72892]: ##############
objc[72892]: AUTORELEASE POOLS for thread 0x1000ab5c0
objc[72892]: 1 releases pending.
objc[72892]: [0x100803000]  ................  PAGE  (hot) (cold)
objc[72892]: [0x100803038]  ################  POOL 0x100803038
objc[72892]: ##############

上面的测试代码,我们创建了一个Dog对象,然后分别让p1p2对象持有这个dog对象,这时dog对象的引用计数为3,当第二个自动释放池超出作用域销毁的时,我们通过打印可以看出dog对象也销毁了,此时再打印自动释放池的情况,发现池中就剩下第一个自动释放池的POOL_BOUNDARY地址了,第二个自动释放池中的所有对象全部都已释放了

上面咱们代码测试自动释放池的情况一直都是使用@autoreleasepool {}方式创建的自动释放池,并没有使用NSAutoreleasePool类来创建自动释放池,原理都是一样的,这里就不在重复验证了。

接下来我们再来看看ARC环境下的自动释放池的使用情况:

由于ARC环境中我们不能使用autoreleaseNSAutoreleasePool,所以在ARC环境中,如果想创建一个自动释放池我们只能选择使用@autoreleasepool {}这种方式创建,如果想将对象添加到自动释放池中,我们只能选择使用__autoreleasing权限修饰符来修饰这个对象,测试代码如下:

extern void _objc_autoreleasePoolPrint(void);

int main(int argc, const char * argv[]) {
    
    @autoreleasepool { // 第一个自动释放池
        // insert code here...
        
        // 注意:当前是ARC环境
                
        NSLog(@"%@", [NSThread currentThread]);
        
        _objc_autoreleasePoolPrint();
        
        NSLog(@"---------------------------");
        
        // 没有使用__autoreleasing修饰的对象,不会添加到自动释放池中
        Person *p1 = [[Person alloc] init];
        
        _objc_autoreleasePoolPrint();
        
        // 使用了__autoreleasing的会添加到自动释放池中
        __autoreleasing Person *p2 = [[Person alloc] init];
        
        _objc_autoreleasePoolPrint();
        
    }
    
    NSLog(@"-第一个自动释放池销毁了-");
    
    // 第一个自动释放池超出了作用域,自动释放池销毁,池中的对象也全部释放
    _objc_autoreleasePoolPrint();
    return 0;
}

对应的终端打印如下:

objc[73081]: ##############
objc[73081]: AUTORELEASE POOLS for thread 0x1000ab5c0
objc[73081]: 1 releases pending.
objc[73081]: [0x105002000]  ................  PAGE  (hot) (cold)
objc[73081]: [0x105002038]  ################  POOL 0x105002038
objc[73081]: ##############
---------------------------
objc[73081]: ##############
objc[73081]: AUTORELEASE POOLS for thread 0x1000ab5c0
objc[73081]: 1 releases pending.
objc[73081]: [0x105002000]  ................  PAGE  (hot) (cold)
objc[73081]: [0x105002038]  ################  POOL 0x105002038
objc[73081]: ##############
---------------------------
objc[73081]: ##############
objc[73081]: AUTORELEASE POOLS for thread 0x1000ab5c0
objc[73081]: 2 releases pending.
objc[73081]: [0x105002000]  ................  PAGE  (hot) (cold)
objc[73081]: [0x105002038]  ################  POOL 0x105002038
objc[73081]: [0x105002040]       0x103a225e0  Person
objc[73081]: ##############
---------------------------
14:18:48.329583+0800 aaaaaaa[73081:5459344] -[Person dealloc]
14:18:48.329638+0800 aaaaaaa[73081:5459344] -[Person dealloc]
14:18:48.349366+0800 aaaaaaa[73081:5459344] -第一个自动释放池销毁-
---------------------------
objc[73081]: ##############
objc[73081]: AUTORELEASE POOLS for thread 0x1000ab5c0
objc[73081]: 0 releases pending.
objc[73081]: [0x105002000]  ................  PAGE  (hot) (cold)
objc[73081]: ##############

从上面的打印我们可以看到,在ARC环境中,如果对象没有被__autoreleasing修饰是不会被添加到自动释放池中的,这个和MRCautorelease等价,当自动释放池超出作用域,自动释放池会销毁,池中的所有对象也会释放,这个和MRC是完全一样的

接下来我们再来看看ARC环境下的自动释放池循环嵌套情况,测试代码如下:

extern void _objc_autoreleasePoolPrint(void);

int main(int argc, const char * argv[]) {
    
    @autoreleasepool { // 第一个自动释放池
        // insert code here...
        
        // 注意:当前是ARC环境
                
        NSLog(@"%@", [NSThread currentThread]);
        
        Person *p1 = [[Person alloc] init];
        
        __autoreleasing Person *p2 = [[Person alloc] init];
                
        @autoreleasepool { // 第二个自动释放池
            
            __autoreleasing Dog *dog1 = [[Dog alloc] init];
            __autoreleasing Cat *cat1 = [[Cat alloc] init];
            
            _objc_autoreleasePoolPrint();
            
            NSLog(@"---------------------");
        }
        
        NSLog(@"-第二个自动释放池销毁了-");
        
        _objc_autoreleasePoolPrint();
        
        NSLog(@"---------------------");
    }
    
    NSLog(@"-第一个自动释放池销毁了-");
    
    _objc_autoreleasePoolPrint();
    return 0;
}

对应的终端打印如下:

objc[73141]: ##############
objc[73141]: AUTORELEASE POOLS for thread 0x1000ab5c0
objc[73141]: 5 releases pending.
objc[73141]: [0x101804000]  ................  PAGE  (hot) (cold)
objc[73141]: [0x101804038]  ################  POOL 0x101804038
objc[73141]: [0x101804040]       0x10071cd70  Person
objc[73141]: [0x101804048]  ################  POOL 0x101804048
objc[73141]: [0x101804050]       0x10071c030  Dog
objc[73141]: [0x101804058]       0x10071d150  Cat
objc[73141]: ##############
---------------------
14:34:05.417828+0800 aaaaaaa[73141:5467454] -[Cat dealloc]
14:34:05.417890+0800 aaaaaaa[73141:5467454] -[Dog dealloc]
14:34:05.417976+0800 aaaaaaa[73141:5467454] -第二个自动释放池销毁了-
---------------------
objc[73141]: ##############
objc[73141]: AUTORELEASE POOLS for thread 0x1000ab5c0
objc[73141]: 2 releases pending.
objc[73141]: [0x101804000]  ................  PAGE  (hot) (cold)
objc[73141]: [0x101804038]  ################  POOL 0x101804038
objc[73141]: [0x101804040]       0x10071cd70  Person
objc[73141]: ##############
---------------------
14:34:05.421754+0800 aaaaaaa[73141:5467454] -[Person dealloc]
14:34:05.421802+0800 aaaaaaa[73141:5467454] -[Person dealloc]
14:34:05.421872+0800 aaaaaaa[73141:5467454] -第一个自动释放池销毁了-
objc[73141]: ##############
objc[73141]: AUTORELEASE POOLS for thread 0x1000ab5c0
objc[73141]: 0 releases pending.
objc[73141]: [0x101804000]  ................  PAGE  (hot) (cold)
objc[73141]: ##############

我们从上面的打印可以看到,在ARC环境中自动释放池嵌套使用,当自动释放池超出作用域时便会销毁,池中的所有对象也都会被释放,这和MRC是一样的原理。


接下来我们再来探究下Autorelease Poolrunloop之间的关系,我们创建一个新的iOS工程,打印当前的runloop,测试代码如下:

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    NSLog(@"%@", [NSRunLoop currentRunLoop]);
}
@end

这里打印的信息太多,我们主要关心runloopObserver的信息,核心打印如下:

observers = (
    "<CFRunLoopObserver 0x6000003143c0 [0x7fff805eff70]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff47571f14), context = <CFArray 0x600003c4f360 [0x7fff805eff70]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fcb45001038>\n)}}",
    "<CFRunLoopObserver 0x6000003100a0 [0x7fff805eff70]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x7fff4712091e), context = <CFRunLoopObserver context 0x600001900fc0>}",
    "<CFRunLoopObserver 0x600000314280 [0x7fff805eff70]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x7fff475a22b6), context = <CFRunLoopObserver context 0x7fcb40c00d90>}",
    "<CFRunLoopObserver 0x60000031c320 [0x7fff805eff70]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x7fff2affcc4a), context = <CFRunLoopObserver context 0x0>}",
    "<CFRunLoopObserver 0x600000314320 [0x7fff805eff70]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x7fff475a231f), context = <CFRunLoopObserver context 0x7fcb40c00d90>}",
    "<CFRunLoopObserver 0x600000314460 [0x7fff805eff70]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff47571f14), context = <CFArray 0x600003c4f360 [0x7fff805eff70]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fcb45001038>\n)}}"

上面的observers中,我们可以看到,程序一启动系统在主线程中就注册了如下两个Observer

// 第一个Observer
"<CFRunLoopObserver 0x6000003143c0 [0x7fff805eff70]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff47571f14), context = <CFArray 0x600003c4f360 [0x7fff805eff70]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fcb45001038>\n)}}",

// 第二个Observer
"<CFRunLoopObserver 0x600000314460 [0x7fff805eff70]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff47571f14), context = <CFArray 0x600003c4f360 [0x7fff805eff70]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fcb45001038>\n)}}"

我们发现第一个Observer是用来监听activities = 0x1的事件,第二个Observer是用来监听activities = 0xa0的事件

我们再来了解下runloop对于监听事件状态的枚举:

// Run Loop Observer Activities
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),            // 1:代表是进入runloop的状态
    kCFRunLoopBeforeTimers = (1UL << 1),     // 2:即将处理timer的状态
    kCFRunLoopBeforeSources = (1UL << 2),    // 4:即将处理source的状态
    kCFRunLoopBeforeWaiting = (1UL << 5),    // 32:即将进入休眠的状态
    kCFRunLoopAfterWaiting = (1UL << 6),     // 64:已休眠,等待唤醒的状态
    kCFRunLoopExit = (1UL << 7),             // 128:退出runloop的状态
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

从枚举中对应的状态值我们便知道了,第一个Observer是用来监听runloop进入事件,也就是kCFRunLoopEntry,第二个Observer对应的activities值为0xa0=160,160正好是状态值32加上状态值128,也就是说第二个Observer是用来监听runloop即将进入休眠的状态kCFRunLoopBeforeWaitingrunloop退出的状态kCFRunLoopExit

我们通过上面的打印可以看到,这两个Observer当监听到事件触发,都会回调执行callout = _wrapRunLoopWithAutoreleasePoolHandler这个handler,从回调函数的名字_wrapRunLoopWithAutoreleasePoolHandler我们可以知道这个回调是用来处理Autorelease Pool相关操作的,那么这两个Observer监听到回调后会触发自动释放池的什么操作尼?

第一个Observer当监听到kCFRunLoopEntry时,这时会调用自动释放池的objc_autoreleasePoolPush函数,也就是创建一个自动释放池,并且将POOL_BOUNDARY添加到自动释放池中

第二个Observer当监听到kCFRunLoopBeforeWaiting时,这时会调用自动释放池的objc_autoreleasePoolPop函数,来销毁自动释放池,然后再调用objc_autoreleasePoolPush函数创建一个自动释放池。当监听到kCFRunLoopExit时,这时会调用自动释放池的objc_autoreleasePoolPop函数,销毁自动释放池

runloop即将进入循环之前,会创建自动释放池,runloop退出循环会销毁自动释放池。系统之所以这样设计,其实也很好理解。因为在runloop即将进入循环前,系统创建了大量的事件和对象,创建的这些事件和对象最好能够由自动释放池来管理以保证得到有效的释放,然而当runloop退出循环,这时肯定也需要释放掉之前创建的对象,所以必然会销毁自动释放池

runloop即将进入休眠状态时,这时整个应用程序都进入了休眠状态,等待其它事件来唤醒runloop,所以在这时也会调用objc_autoreleasePoolPop函数来销毁自动释放池,以保证在休眠状态下释放掉无用的对象。为了保证在下一个runloop循环过程中创建的事件和对象都能够及时的释放,所以在销毁完自动释放池后系统又创建了一个新的自动释放池。

我们从Autorelease Pool的官方文档说明中也可以看出上面runloop注册两个Observer的作用:

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. If 
 you use the Application Kit, you therefore typically don’t have to create 
 your own pools. If your application creates a lot of temporary autoreleased 
 objects within the event loop, however, it may be beneficial to create 
 “local” autorelease pools to help to minimize the peak memory footprint.

runloopAutorelease Pool的关系如图:

image

讲解示例Demo地址:

https://github.com/guangqiang-liu/11-AutoreleasePool

https://github.com/guangqiang-liu/11.1-AutoreleasePool

https://github.com/guangqiang-liu/11.2-AutoreleasePool

https://github.com/guangqiang-liu/11.3-AutoreleasePool

更多文章

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