1、AutoreleasePool分析整理
为了分析AutoreleasePool,下面分四种场景进行分析
Person类用于打印对象的释放时机
#import
NS_ASSUME_NONNULL_BEGIN
@(iOS开发学习)[温故而知新]interface Person : NSObject
@property (nonatomic, strong) NSString* name;
@end
NS_ASSUME_NONNULL_END
@implementation Person
- (void)dealloc {
NSLog(@"func = %s, name = %@", __func__, self.name);
}
@end
复制代码
场景一:对象没有被加入到AutoreleasePool中
#import
#import "Person.h"
NS_ASSUME_NONNULL_BEGIN
@interface AutoreleasePoolWithOutVC : UIViewController
@end
NS_ASSUME_NONNULL_END
@interface AutoreleasePoolWithOutVC ()
@property (nonatomic, strong) Person* zhangSanStrong;
@property (nonatomic, weak) Person* zhangSanWeak;
@end
@implementation AutoreleasePoolWithOutVC
- (void)viewDidLoad {
[super viewDidLoad];
Person *xiaoMing = [[Person alloc] init];
xiaoMing.name = @"xiaoMing";
_zhangSanStrong = [[Person alloc] init];
_zhangSanStrong.name = @"zhangSanStrong";
Person *zhangSanWeak = [[Person alloc] init];
zhangSanWeak.name = @"zhangSanWeak";
_zhangSanWeak = zhangSanWeak;
NSLog(@"func = %s, xiaoMing = %@", __func__, xiaoMing);
}
- (void)viewWillAppear:(BOOL)animated {
NSLog(@"func = %s", __func__);
}
- (void)viewDidAppear:(BOOL)animated {
NSLog(@"func = %s", __func__);
}
@end
复制代码
运行结果: 栈中创建的临时对象xiaoMing和weak属性修饰的对象 _zhangSanWeak ,在viewDidLoad
结束后就被释放了。
场景二:对象被加入到手动创建的AutoreleasePool中
#import
NS_ASSUME_NONNULL_BEGIN
@interface AutoreleasePoolManualWithVC : UIViewController
@end
NS_ASSUME_NONNULL_END
#import "AutoreleasePoolManualWithVC.h"
#import "Person.h"
@interface AutoreleasePoolManualWithVC ()
@property (nonatomic, strong) Person* zhangSanStrong;
@property (nonatomic, weak) Person* zhangSanWeak;
@end
@implementation AutoreleasePoolManualWithVC
- (void)viewDidLoad {
[super viewDidLoad];
@autoreleasepool {
Person *xiaoMing = [[Person alloc] init];
xiaoMing.name = @"xiaoMing";
_zhangSanStrong = [[Person alloc] init];
_zhangSanStrong.name = @"zhangSanStrong";
Person *zhangSanWeak = [[Person alloc] init];
zhangSanWeak.name = @"zhangSanWeak";
_zhangSanWeak = zhangSanWeak;
}
NSLog(@"func = %s", __func__);
}
- (void)viewWillAppear:(BOOL)animated {
NSLog(@"func = %s", __func__);
}
- (void)viewDidAppear:(BOOL)animated {
NSLog(@"func = %s", __func__);
}
@end
复制代码
运行结果: 栈中创建的临时对象xiaoMing和weak属性修饰的对象 _zhangSanWeak,在viewDidLoad
结束之前就被释放了。
场景三:对象被加入到系统的AutoreleasePool中
#import
NS_ASSUME_NONNULL_BEGIN
@interface AutoreleasePoolSystermWithVC : UIViewController
@end
NS_ASSUME_NONNULL_END
#import "AutoreleasePoolSystermWithVC.h"
@interface AutoreleasePoolSystermWithVC ()
@property (nonatomic, strong) NSString* zhangSanStrong;
@property (nonatomic, weak) NSString* zhangSanWeak;
@end
@implementation AutoreleasePoolSystermWithVC
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"func = %s start", __func__);
_zhangSanStrong = [NSString stringWithFormat:@"zhangSanStrong"];
NSString* zhangSanWeak = [NSString stringWithFormat:@"zhangSanStrong"];
_zhangSanWeak = zhangSanWeak;
[self printInfo];
NSLog(@"func = %s end", __func__);
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"func = %s start", __func__);
[self printInfo];
NSLog(@"func = %s end", __func__);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"func = %s start", __func__);
[self printInfo];
NSLog(@"func = %s end", __func__);
}
- (void)printInfo {
NSLog(@"self.zhangSanStrong = %@", _zhangSanStrong);
NSLog(@"self.zhangSanWeak = %@", _zhangSanWeak);
}
@end
复制代码
运行结果: 系统在每个Runloop
迭代中都加入了AutoreleasePool
,Runloop
开始后创建AutoreleasePool并Autorelease对象
加入到pool中,Runloop
结束后或者休眠的时候Autorelease对象
被释放掉。
场景四:(Tagged Pointer
)对象被加入到系统的AutoreleasePool中
看了别人的博客后,决定手动验证一下,又不想完全copy别人的代码,自己仿写初始化的时候又懒得写太多内容,索性写了@“1”,所以导致结果与博客不一致,因此更加怀疑人生。我想不止我一个要这种情况?。
#import
NS_ASSUME_NONNULL_BEGIN
@interface AutoreleasePoolSystermWithTaggedPointerVC : UIViewController
@end
NS_ASSUME_NONNULL_END
#import "AutoreleasePoolSystermWithTaggedPointerVC.h"
@interface AutoreleasePoolSystermWithTaggedPointerVC ()
@property (nonatomic, weak) NSString* tagged_yes_1; // 是Tagged Pointer
@property (nonatomic, weak) NSString* tagged_yes_2; // 是Tagged Pointer
@property (nonatomic, weak) NSString* tagged_yes_3; // 是Tagged Pointer
@property (nonatomic, weak) NSString* tagged_no_1; // 非Tagged Pointer
@property (nonatomic, weak) NSString* tagged_no_2; // 非Tagged Pointer
@property (nonatomic, weak) NSString* tagged_no_3; // 非Tagged Pointer
@end
@implementation AutoreleasePoolSystermWithTaggedPointerVC
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"func = %s start", __func__);
NSString* tagged_yes_1_str = [NSString stringWithFormat:@"1"];
_tagged_yes_1 = tagged_yes_1_str;
NSString* tagged_yes_2_str = [NSString stringWithFormat:@"123456789"];
_tagged_yes_2 = tagged_yes_2_str;
NSString* tagged_yes_3_str = [NSString stringWithFormat:@"abcdefghi"];
_tagged_yes_3 = tagged_yes_3_str;
NSString* tagged_no_1_str = [NSString stringWithFormat:@"0123456789"];
_tagged_no_1 = tagged_no_1_str;
NSString* tagged_no_2_str = [NSString stringWithFormat:@"abcdefghij"];
_tagged_no_2 = tagged_no_2_str;
NSString* tagged_no_3_str = [NSString stringWithFormat:@"汉字"];
_tagged_no_3 = tagged_no_3_str;
[self printInfo];
NSLog(@"func = %s end", __func__);
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"func = %s start", __func__);
[self printInfo];
NSLog(@"func = %s end", __func__);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"func = %s start", __func__);
[self printInfo];
NSLog(@"func = %s end", __func__);
}
- (void)printInfo {
NSLog(@"self.tagged_yes_1 = %@", _tagged_yes_1);
NSLog(@"self.tagged_yes_2 = %@", _tagged_yes_2);
NSLog(@"self.tagged_yes_3 = %@", _tagged_yes_3);
NSLog(@"self.tagged_no_1 = %@", _tagged_no_1);
NSLog(@"self.tagged_no_2 = %@", _tagged_no_2);
NSLog(@"self.tagged_no_3 = %@", _tagged_no_3);
}
@end
复制代码
运行结果:
Tagged Pointer
类型的Autorelease对象
,系统不会释放- 非
Tagged Pointer
类型的Autorelease对象
,系统会在当前Runloop
结束后释放
AutoreleasePool定义
- 自动释放池是由 AutoreleasePoolPage 以双向链表的方式实现的
- 当对象调用 Autorelease 方法时,会将对象加入 AutoreleasePoolPage 的栈中
- 调用 AutoreleasePoolPage::pop 方法会向栈中的对象发送 release 消息
在ARC环境下,以alloc
/new
/copy
/mutableCopy
开头的方法返回值取得的对象是自己生成并且持有的,其他情况是非自己持有的对象,此时对象的持有者就是AutoreleasePool
。
当我们使用@autoreleasepool{}
时,编译器会将其转换成以下形式
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
复制代码
__AtAutoreleasePool
定义如下:
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
.
.
// 将中间对象压入栈中(atautoreleasepoolobj也是一个对象,相当于哨兵,代表一个 autoreleasepool 的边界,
// 与当前的AutoreleasePool对应,pop的时候用来标记终点位置,被当前的AutoreleasePool第一个压入栈中,
// 出栈的时候最后一个被弹出)
.
.
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
复制代码
创建时调用了objc_autoreleasePoolPush()
方法,而释放时调用objc_autoreleasePoolPop()
方法,只是一层简单的封装。
AutoreleasePool
并没有单独的结构,本质上是一个双向链表,结点是AutoreleasePoolPage
对象。每个结点的大小是4KB
(4*1024=4096字节),除去实例变量的大小,剩余的空间用来存储Autorelease对象的地址
。
class AutoreleasePoolPage {
magic_t const magic; // 用于校验AutoreleasePage的完整性
id *next; // 指向栈顶最后push进来的Autorelease对象的下一个位置
pthread_t const thread; // 保存了当前页所在的线程,每一个 autoreleasepool 只对应一个线程
AutoreleasePoolPage * const parent; // 双向链表中指向上一个节点,第一个结点的 parent 值为 nil
AutoreleasePoolPage *child; // 双向链表中指向下一个节点,最后一个结点的 child 值为 nil
uint32_t const depth; // 深度,从0开始,往后递增1
uint32_t hiwat; // high water mark
};
复制代码
next指针指向将要添加新对象(Autorelease对象、哨兵对象)的空闲位置。
objc_autoreleasePoolPush 执行过程
当调用AutoreleasePoolPage::push()
方法时,首先向当前的page(hotPage)结点next指针指向的位置添加一个哨兵对象(POOL_SENTINEL
,值为nil)。如果后面嵌套着AutoreleasePool
则继续添加哨兵对象,否则将Autorelease对象压入哨兵对象的上面。向高地址移动next指针,直到 next == end()
时,表示当前page已满。当 next == begin()
时,表示 AutoreleasePoolPage 为空;当 next == end()
时,表示 AutoreleasePoolPage 已满。
- 1、有
hotPage
并且当前page
不满。调用page->add(obj)
方法将对象添加至AutoreleasePoolPage
的栈中 - 2、有
hotPage
并且当前page
已满。调用autoreleaseFullPage
初始化一个新的页,调用page->add(obj)
方法将对象添加至AutoreleasePoolPage
的栈中 - 3、无
hotPage
。调用autoreleaseNoPage
创建一个hotPage
,调用page->add(obj)
方法将对象添加至AutoreleasePoolPage
的栈中。
objc_autoreleasePoolPop
执行过程
当调用AutoreleasePoolPage::pop()
的方法时,pop 函数的入参就是 push 函数的返回值,也就是 POOL_SENTINEL
的内存地址,即 pool token
。当执行 pop 操作时,根据传入的哨兵对象地址找到哨兵对象所处的page,将晚于(上面的)哨兵对象压入的Autorelease
对象进行release。即内存地址在 pool token 之后的所有 Autoreleased 对象
都会被 release 。直到 pool token 所在 page 的 next 指向 pool token
为止。并向回移动next指针到正确位置。
AutoreleasePool对象什么时候释放?
没有手动加AutoreleasePool
的情况下,Autorelease对象
是在当前的Runloop
迭代结束的时候释放的。手动添加的Autorelease对象
也是自动计数的,当引用计数为0的时候,被释放掉。
实际验证之前,首先了解几个私有API,查看自动释放池的状态,在ARC下查看对象的引用计数
//先声明私有的API
extern void _objc_autoreleasePoolPrint(void);
extern uintptr_t _objc_rootRetainCount(id obj);
_objc_autoreleasePoolPrint(); //调用 打印自动释放池里的对象
_objc_rootRetainCount(obj); //调用 查看对象的引用计数
复制代码
NSThread、NSRunLoop 和 NSAutoreleasePool
根据苹果官方文档中对 NSRunLoop 的描述,我们可以知道每一个线程,包括主线程,都会拥有一个专属的 NSRunLoop 对象,并且会在有需要的时候自动创建。同样的,根据苹果官方文档中对 NSAutoreleasePool 的描述,我们可知,在主线程的 NSRunLoop 对象(在系统级别的其他线程中应该也是如此,比如通过 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 获取到的线程)的每个 event loop 开始前,系统会自动创建一个 autoreleasepool ,并在 event loop 结束时 drain 。
添加打印Runloop的代码:NSLog(@"[NSRunLoop currentRunLoop] = %@", [NSRunLoop currentRunLoop]);
打印出的日志部分截图如下。
_wrapRunLoopWithAutoreleasePoolHandler()
。
第一个 Observer 监视的事件是 Entry
(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush()
创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件: BeforeWaiting
(准备进入休眠) 时调用_objc_autoreleasePoolPop()
和 _objc_autoreleasePoolPush()
释放旧的池并创建新池;Exit
(即将退出Loop) 时调用 _objc_autoreleasePoolPop()
来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
什么时候用@autoreleasepool
根据 Apple的文档 ,使用场景如下:
- 写基于命令行的的程序时,就是没有UI框架,如AppKit等Cocoa框架时。
- 写循环,循环里面包含了大量临时创建的对象。(本文的例子)
- 创建了新的线程。(非Cocoa程序创建线程时才需要)
- 长时间在后台运行的任务。
Tagged Pointer
Tagged Pointer是一个能够提升性能、节省内存的有趣的技术。在OS X 10.10中,NSString就采用了这项技术,现在让我们来看看该技术的实现过程。
- 1、Tagged Pointer专门用来存储小的对象,例如NSNumber、NSDate、NSString
- 2、Tagged Pointer指针的值不再是地址了,而是真正的值。不再是一个对象,内存中的位置不在堆中,不需要malloc和free。避免在代码中直接访问对象的isa变量,而使用方法isKindOfClass和objc_getClass。
- 3、在内存读取上有着3倍的效率,创建时比以前快了106倍。
参考博客
深入理解RunLoop
深入理解AutoreleasePool
黑幕背后的Autorelease
自动释放池的前世今生 ---- 深入解析 autoreleasepool
iOS开发 自动释放池(Autorelease Pool)和RunLoop
Objective-C Autorelease Pool 的实现原理
深入理解Tagged Pointer
【译】采用Tagged Pointer的字符串
二、Block学习整理
2.0、什么是Block
Block
表面上是一个带自动变量(局部变量)的匿名函数。本质上是对闭包的对象实现,简单来说Block
就是一个结构体对象。在ARC
下,大多数情况下Block
从栈上复制到堆上的代码是由编译器实现的(Blcok
作为返回值或者参数)
2.1、block编译转换结构
2.1.1、新建一个macOS项目,编写一个最简单的Block。
#import
typedef void(^blockVoid)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
blockVoid block = ^() {
NSLog(@"block");
};
block();
}
return 0;
}
复制代码
2.1.2、使用clang命令处理成cpp文件从而初步认识Block。
由于命令过长,我们起一个别名genCpp_mac。 alias genCpp_mac='clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk'
2.1.3、在终端进入.m所在的目录下面执行genCpp_mac main.m,当前目录下会生成同名.cpp文件
2.1.4、打开.cpp文件查看与Block相关的代码
struct __block_impl {
void *isa; // 指向所属类的指针,也就是block的类型(包含isa指针的皆为对象)
int Flags; // 标志变量,在实现block的内部操作时会用到
int Reserved; // 保留变量
void *FuncPtr; // block执行时调用的函数指针
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 显式的构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_x0_1sgmhpyx6535p2pfkfsbfvww0000gn_T_main_94a22e_mi_0);
}
// 纪录了block结构体大小等信息
static struct __main_block_desc_0 {
size_t reserved; // 保留字段
size_t Block_size; // block大小(sizeof(struct __main_block_impl_0))结构体大小需要保存是因为,每个 block 因为会 capture 一些变量,这些变量会加到 __main_block_impl_0 这个结构体中,使其体积变大
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
/*
把多余的转换去掉,看起来就比较清楚了:
第一部分:block的初始化
参数一:__main_block_func_0(是block语法转换的C语言函数指针)
参数二:__main_block_desc_0_DATA(作为静态全局变量初始化的 __main_block_desc_0 结构体实例指针)
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
第二部分:
block的执行: blk()
去掉转化部分:
(*blk -> imp.FuncPtr)(blk);
这就是简单地使用函数指针调用函数。由Block语法转换的 __main_block_func_0 函数的指针被赋值成员变量FuncPtr中,另外 __main_block_func_0的函数的参数 __cself 指向Block的值,通过源码可以看出 Block 正式作为参数进行传递的。
*/
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
blockVoid block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
复制代码
2.2、block实际结构Block_private.h
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
struct Block_layout {
void *isa; // 所有对象都有该指针,用于实现对象相关的功能。
volatile int32_t flags; // contains ref count(block copy 的会对该变量操作)
int32_t reserved; // 保留变量
void (*invoke)(void *, ...); // 函数指针,指向具体的 block 实现的函数调用地址。
struct Block_descriptor_1 *descriptor; // 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。
// imported variables // capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。
};
复制代码
2.3、block的种类
Block种类 | 存储区 | 拷贝效果 | 生命周期 |
---|---|---|---|
_NSConcreteStatckBlock | 栈 | 从栈拷贝到堆,往flags中并入BLOCK_NEEDS_FREE这个标志表明block需要释放,在release以及再次拷贝时会用到);如果有辅助拷贝函数,就调用,将捕获的变量从栈拷贝到堆中 | 出了作用域 |
_NSConcreteMallocBlock | 堆 | 单纯的引用计数加一 | 引用计数为0,runloop结束后释放 |
_NSConcreteGlobalBlock | 全局数据区 | 什么都不做,直接返回Blcok | app整个生命周期 |
- 1、只要不访问外部变量就是
__NSGlobalBlock__
类型的Block,不管是__strong
还是__weak
修饰(不加默认__strong
)。 - 2、如果访问外部变量,被
__strong
修饰的是__NSMallocBlock__
类型,被__weak
修饰的是__NSStackBlock__
类型。
注意: 访问外部变量的Block,在编译完成后其实都是
__NSStackBlock__
类型的,只是在ARC中被__strong
修饰的会在运行时被自动拷贝一份,最终调用_Block_copy_internal
函数,将isa由_NSConcreteStatckBlock
指向_NSConcreteMallocBlock
。
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
...
aBlock = (struct Block_layout *)arg;
...
// Its a stack block. Make a copy.
if (!isGC) {
// 申请block的堆内存
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
// 拷贝栈中block到刚申请的堆内存中
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
// 改变isa指向_NSConcreteMallocBlock,即堆block类型
result->isa = _NSConcreteMallocBlock;
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
//printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
else {
...
}
}
复制代码
2.3.1、__NSStackBlock__与__NSMallocBlock__的区别:修饰类型是__strong
还是__weak
/**
__NSMallocBlock__:__strong修饰
__NSStackBlock__:__weak修饰
*/
-(void)blockStatckVsGloable {
NSInteger i = 10;
__strong void (^mallocBlock)(void) = ^{
NSLog(@"i = %ld", (long)i);
};
__weak void (^stackBlock)(void) = ^{
NSLog(@"self.number = %@", self.number);
};
}
复制代码
2.3.2、 __NSStackBlock__与__NSGlobalBlock__的区别:是否访问外部变量
/**
__NSStackBlock__:访问外部变量
__NSGlobalBlock__:没有访问外部变量
*/
-(void)blockStatckVsGloable {
NSInteger i = 10;
__weak void (^stackBlock)(void) = ^{
NSLog(@"i = %ld", (long)i);
};
__weak void (^globalBlock2)(void) = ^{
};
}
复制代码
2.4、访问修改变量对block结构的影响
2.4.1、全局变量
对于访问和修改全局变量,Block的结构不会发生变化。
2.4.2、全局静态变量
对于访问和修改全局静态变量,同全局变量,Block的结构不会发生变化。
2.4.3、局部变量
ARC下NSConcreteStackBlock如果引入了局部变量,会被NSConcreteMallocBlock 类型的 block 替代。
访问局部变量
修改局部变量(局部变量需要使用__Block修饰)
函数__main_block_copy_0
用于在将Block拷贝到堆中的时候,将包装局部变量(
localVariable
)的对象(
__Block_byref_localVariable_0
)从栈中拷贝到堆中,所以即使局部变量在栈中被销毁,Block依然能对堆中的局部变量进行修改操作。结构体
__Block_byref_localVariable_0
中的
__forwarding
变量用来指向局部变量在堆中的拷贝。目的是为了保证操作的值始终是堆中的拷贝,而不是栈中的值。
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
struct Block_byref **destp = (struct Block_byref **)dest;
struct Block_byref *src = (struct Block_byref *)arg;
...
// 堆中拷贝的forwarding指向它自己
copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
// 栈中的forwarding指向堆中的拷贝
src->forwarding = copy; // patch stack to point to heap copy
...
}
复制代码
2.4.4、局部静态变量(修改不需要__Block修饰)
对于访问和修改局部静态变量,Block需要截获静态变量的指针,改变的时候直接通过指针改变值
2.4.5、self隐式循环引用
发生循环引用的时候,self强持有Block,从下面可以看出Block也是强持有self的。
2.6、block的辅助函数
详情请参考Block技巧与底层解析
参考博客
《Objective-C高级编程》Blocks
Block技巧与底层解析
Block源码解析和深入理解
谈Objective-C block的实现
block没那么难(二):block和变量的内存管理
源码解析之从Block说开去
iOS 中的 block 是如何持有对象的
三、Runloop学习整理(持续更新纠正)
[TOC]
@(iOS开发学习)[温故而知新]
问题
1、什么是RunLoop
RunLoop
实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop
的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 “接受消息->等待->处理
” 的循环中,直到这个循环结束(比如传入 quit
的消息),函数返回。一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要RunLoop
,让线程能随时处理事件但并不退出。RunLoop
核心是一个有条件的循环。
Runloop组成
RunLoop的结构需要涉及到以下4个概念:
Run Loop Mode
、Input Source
、Timer Source
和Run Loop Observer
。
1、一个 Runloop 包含若干个
mode
,每个mode
又包含若干个source
(InputSource
、TimerSource
、Observers
) 2、Runloop 启动只能指定一个mode
,若要切换mode
只能重新启动Runloop
指定另外一个mode
。这样做的目的是为了处理优先级不同的Source
、Timer
、Observer
。
Runloop的各种mode
NSUserDefaultRunloopMode
// 默认mode,通常主线程在这个mode下面运行
UITrackingRunloopMode
// 界面追踪mode,用于ScrollView追踪界面滑动,保证界面滑动时不受其他mode的影响
UIInitializationRunloopMode
// 刚启动App时进入的第一个mode,启动完成后就不在使用
GSEventReceiveRunloopMode
// 接受系统事件的内部mode,通常用不到。
NSRunloopCommonModes
// 并不是一种真正的mode,系统把NSUserDefaultRunloopMode和UITrackingRunloopMode共同标记为NSRunloopCommonModes
复制代码
Runloop的各种mode的作用
指定事件在运行循环中的优先级。线程的运行需要不同的模式,去响应各种不同的事件,去处理不同情境模式。(比如可以优化tableview
的时候可以设置UITrackingRunLoopMode
下不进行一些操作,比如设置图片等。)
1、InputSource:
官方文档分为三类,
基于端口的
、自定义的
、基于perform selector
。但是也可通过函数调用栈对Source分为两类,source0和source1。source1是基于端口的,包含一个match_port和一个回调(函数指针),能主动唤醒Runloop的线程;source0是基于非端口的,只包含一个回调(函数指针),不能主动触发事件。也就是用户触发事件,包括自定义source和perfom selector
注意: 按钮点击事件从函数调用栈来看是Source0
事件。实际上是点击屏幕产生event
事件,传递给Source1
,然后Source1
派发给Source0
的。
2、TimerSource:
即
CFRunloopTimerRef
,也就是NSTimer
。NSTimer
创建时必须添加到Runloop
中,否则无法执行,在添加到Runloop
时,必须指定mode
,决定NSTimer
在哪个mode
下运行。
3、observer:
监听
Runloop
的状态
Runloop
的各种状态
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Runloop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 即将从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将推出Runloop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
复制代码
Runloop生命周期
生命周期 | 主线程 | 子线程 |
---|---|---|
创建 | 默认系统创建 | 苹果不允许直接创建 RunLoop ,它只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent() 。 在当前子线程调用[NSRunLoop currentRunLoop] ,如果有就获取,没有就创建 |
启动 | 默认启动 | 手动启动 |
获取 | [NSRunLoop mainRunLoop] 或者CFRunLoopGetMain() |
[NSRunLoop currentRunLoop] 或者CFRunLoopGetCurrent() |
销毁 | app结束时 | 超时时间到了或者手动结束CFRunLoopStop() |
CFRunLoopStop()
方法只会结束当前的runMode:beforeDate:
调用,而不会结束后续的调用。
启动注意:
- - 使用run启动后,Runloop会一直运行处理输入源的数据,在defaultMode模式下重复调用runMode:beforeDate:
- - 使用runUntilDate:启动后与run启动的区别是,在设定时间到后会停止Runloop
- - 使用runMode:beforeDate:启动,Runloop只运行一次,设定时间到达或者第一个input source被处理,Runloop会停止
运行:
Runloop会一直循环检测事件源CFRunloopSourceRef执行处理函数,首先会产生通知,CoreFundation向线程添加RunloopObserves来监听事件,并控制Runloop里面线程的执行和休眠,在有事情做得时候使NSRunloop控制的线程处理事情,空闲的时候让NSRunloop控制的线程休眠。先处理TimerSource,再处理Source0,然后Source1。
停止:
- 1、线程结束
- 2、因为没有任何事件源而退出( Runloop 只会检查 Source 和 Timer ,没有就关闭,不会检查Observer )
- 3、手动结束 Runloop
Runloop启动方式
启动方式 | 调用次数 | 描述 |
---|---|---|
run | 循环调用 | 无条件进入是最简单的做法,但也最不推荐。这会使线程进入死循环,从而不利于控制 runloop,结束 runloop 的唯一方式是 kill 它。它的本质就是无限调用 runMode:beforeDate: 方法。 |
runUntilDate | 循环调用 | 如果我们设置了超时时间,那么 runloop 会在处理完事件或超时后结束,此时我们可以选择重新开启 runloop。也会重复调用 runMode:beforeDate:,区别在于它超时后就不会再调用。这种方式要优于前一种。 |
runMode:beforeDate: | 单次调用 | 这是相对来说最优秀的方式,相比于第二种启动方式,我们可以指定 runloop 以哪种模式运行。 |
通过run开启 runloop 会导致内存泄漏,也就是 thread 对象无法释放。
Runloop的作用:
- 1、保持程序的持续运行(比如主运行循环)接收用户的输入
- 2、处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
- 3、任务调度(主调方产生很多事件,不用等到被调方执行完毕事件,采取执行其他操作)
- 4、节省CPU资源,提高程序性能:该做事时做事,该休息时休息
RunLoop处理逻辑
摘自
2、Runloop和线程的关系
Runloop
与线程是一一对应的,主线程的Runloop
默认已经创建好了,子线程的需要自己手动创建(主线程是一一对应的,子线程可以没有,也可以最多有一个Runloop
)
3、Runloop与NSTimer的关系
- 1、Timer Source会重复在预设的时间点(创建定时器时指定的时间间隔)向Runloop发送消息,执行任务回调函数。
- 2、主线程由于默认创建启动了Runloop所以定时器可以正常运行,但是子线程要想定时器可以正常运行,需要手动启动Runloop。
- 3、另外Timer添加到Runloop指定的默认mode是NSUserDefaultRunloopMode,当UIScrollView滚动的时候Runloop会自动切换到UITrackingRunloopMode,此时定时器是不能正常运行的,如果想正常运行,需要改变Timer添加到Runloop的mode为NSRunloopCommonMode
NSTimer
其实就是 CFRunLoopTimerRef
,他们之间是 toll-free bridged
的。一个 NSTimer
注册到 RunLoop
后,RunLoop
会为其重复的时间点注册好事件。例如 10:00, 10:10, 10:20 这几个时间点。RunLoop
为了节省资源,并不会在非常准确的时间点回调这个 Timer
。Timer
有个属性叫做 Tolerance
(宽容度),标示了当时间点到后,容许有多少最大误差。
如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延后执行。就比如等公交,如果 10:10 时我忙着玩手机错过了那个点的公交,那我只能等 10:20 这一趟了。
创建定时器的时候,会以NSUerDefaultRunloopMode
的mode自动加入到当前线程中。因此下面两种效果是等价的。
GCD定时器不受Runloop的mode的影响。GCD 本身与 RunLoop 是属于平级的关系。 他们谁也不包含谁,但是他们之间存在着协作的关系。当调用 dispatch_async(dispatch_get_main_queue(), block) 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并在回调 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() 里执行这个 block。但这个逻辑仅限于 dispatch 到主线程,dispatch 到其他线程仍然是由 libDispatch 处理的。
4、RunLoop应用
4.1、TableView中实现平滑滚动延迟加载图片
利用CFRunLoopMode的特性,可以将图片的加载放到NSDefaultRunLoopMode
的mode里,这样在滚动UITrackingRunLoopMode
这个mode时不会被加载而影响到。参考:RunLoopWorkDistribution
4.2、解决NSTime在ScrollView滚动时无效
如果利用scrollView类型的做自动广告滚动条 需要把定时器加入当前runloop的模式NSRunLoopCommonModes
4.3、检测UI卡顿
第一种方法通过子线程监测主线程的 runLoop,判断两个状态区域之间的耗时是否达到一定阈值。
ANREye
就是在子线程设置flag 标记为YES, 然后在主线程中将flag设置为NO。利用子线程时阙值时长,判断标志位是否成功设置成NO。NSRunLoop调用方法主要就是在kCFRunLoopBeforeSources
和kCFRunLoopBeforeWaiting
之间,还有kCFRunLoopAfterWaiting
之后,也就是如果我们发现这两个时间内耗时太长,那么就可以判定出此时主线程卡顿
第二种方式就是FPS监控,App 刷新率应该当努力保持在 60fps,通过
CADisplayLink
记录两次刷新时间间隔,就可以计算出当前的 FPS。CADisplayLink
是一个和屏幕刷新率一致的定时器(但实际实现原理更复杂,和 NSTimer 并不一样,其内部实际是操作了一个 Source)。如果在两次屏幕刷新之间执行了一个长任务,那其中就会有一帧被跳过去(和 NSTimer 相似),造成界面卡顿的感觉。在快速滑动TableView
时,即使一帧的卡顿也会让用户有所察觉。
4.4、利用空闲时间缓存数据或者做其他的性能优化相关的任务
可以添加Observer监听RunLoop的状态。比如监听点击事件的处理(在所有点击事件之前做一些事情)
4.5、子线程常驻
某些操作,需要重复开辟子线程,重复开辟内存过于消耗性能,可以设定子线程常驻
如果子线程的NSRunLoop没有设置source or timer, 那么子线程的NSRunLoop会立刻关闭
1、添加Source
// 无含义,设置子线程为常住线程,让子线程不关闭
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
复制代码
2、添加Timer
NSTimer *timer = [NSTimer timerWithTimeInterval:5.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
// 如果不改变mode,下面这行代码去掉后效果一样
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
复制代码
4.6、UI界面更新,监听UICollectionView刷新reloadData完毕的时机
当在操作 UI 时,比如改变了
Frame
、更新了UIView/CALayer
的层次时,或者手动调用了UIView/CALayer
的 >setNeedsLayout
/setNeedsDisplay
方法后,这个UIView/CALayer
就被标记为待处理,并被提交到一个全局>的容器去。苹果注册了一个
Observer
监听BeforeWaiting
(即将进入休眠) 和 Exit (即将退出Loop
) 事件,回调去执行一个>很长的函数:_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()
。这个函数里会遍历所有待处理的 >UIView/CAlayer
以执行实际的绘制和调整,并更新 UI 界面。如果主线程忙于大量的业务逻辑运算,此时去更新UI可能会卡顿。异步绘制框架
ASDK(Texture)
就是为了解决这个问题诞生的,根本原理就是将UI排版和绘制运算尽可能的放到后台,将UI最终的更新操作放到主线程中,同时提供了一套类似UIView
和CAlayer
的相关属性,尽可能保证开发者的开发习惯。监听主线程Runloop Observer
的即将进入休眠和退出两种状态,收到回调是遍历队列中待处理的任务一一执行。
4.7、事件响应
苹果注册了一个 Source1
(基于 mach port
的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()
。
当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework
生成一个 IOHIDEvent
事件并由 SpringBoard
接收。 SpringBoard
只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event
,随后用 mach port
转发给需要的 App 进程。随后苹果注册的那个 Source1
就会触发__IOHIDEventSystemClientQueueCallback()
回调,并调用 _UIApplicationHandleEventQueue()
进行应用内部的分发。
_UIApplicationHandleEventQueue()
会把 IOHIDEvent
处理并包装成 UIEvent
进行处理或分发,其中包括识别 UIGesture
/处理屏幕旋转
/发送给 UIWindow
等。通常事件比如 UIButton
点击、touchesBegin
/Move
/End
/Cancel
事件都是在这个回调中完成的。
SpringBoard
是什么?
SpringBoard
其实是一个标准的应用程序,这个应用程序用来管理IOS的主屏幕,除此之外像启动WindowSever(窗口服务器)
,bootstrapping(引导应用程序)
,以及在启动时候系统的一些初始化设置
都是由这个特定的应用程序负责的。它是我们IOS程序中,事件的第一个接受者
。它只能接受少数的事件比如:按键(锁屏/静音等),触摸,加速,接近传感器等几种Event,随后使用macport
转发给需要的App进程。
4.8、手势识别
当上面的 _UIApplicationHandleEventQueue()
识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin
/Move
/End
系列回调打断。随后系统将对应的 UIGestureRecognizer
标记为待处理。
苹果注册了一个 Observer
监测 BeforeWaiting
(Loop 即将进入休眠) 事件,这个 Observer 的回调函数是 _UIGestureRecognizerUpdateObserver()
,其内部会获取所有刚被标记为待处理的 GestureRecognizer
,并执行 GestureRecognizer
的回调。
当有 UIGestureRecognizer
的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。
5、Runloop与PerformSelecter
performSelecter:after
函数依赖Timer Source
和Runloop
的启动;Runloop
依赖Source
(不限于Timer Source
),没有Sources
就会退出
参考:关于 performSelector 的一些小探讨
import UIKit
class RunloopVc: UIViewController {
@objc func performSeletor() {
debugPrint("performSeletor \(Thread.current)")
}
func case0() {
// 结果:1 → 2 → 1.1 → performSeletor → 1.2
debugPrint("1")
DispatchQueue.global().async {
debugPrint("1.1 \(Thread.current)")
// 当前子线程 异步执行,1.1 → 1.2 → performSeletor
//self.performSelector(inBackground: #selector(self.performSeletor), with: nil)
// 当前子线程 同步执行,1.1 → performSeletor → 1.2
self.perform(#selector(self.performSeletor))
debugPrint("1.2 \(Thread.current)")
}
debugPrint("2")
}
func case1() {
// 结果:1 → 2 → performSeletor(子线程异步执行)
debugPrint("1")
self.performSelector(inBackground: #selector(performSeletor), with: nil)
debugPrint("2")
}
func case2() {
// 结果:1 → 2 → performSeletor(主线程异步执行)
debugPrint("1")
self.perform(#selector(performSeletor), afterDelay: 1)
debugPrint("2")
}
func case3() {
// 结果:1 → 2 → performSeletor(主线程异步执行)
debugPrint("1")
self.perform(#selector(performSeletor), with: nil, afterDelay: 1, inModes: [.default])
debugPrint("2")
}
func case4() {
// 结果:1 → 2 → performSeletor不会执行
debugPrint("1")
DispatchQueue.global(qos: .default).async {
debugPrint("1.1 \(Thread.current)")
self.perform(#selector(self.performSeletor), afterDelay: 1)
debugPrint("1.2 \(Thread.current)")
}
debugPrint("2")
}
func case5() {
// 结果:1 → 2 → 1.1 → 1.2 → 1.3
debugPrint("1")
DispatchQueue.global(qos: .default).async {
debugPrint("1.1 \(Thread.current)")
// Runloop中没有source是会自动退出
RunLoop.current.run()
debugPrint("1.2 \(Thread.current)")
self.perform(#selector(self.performSeletor), afterDelay: 10)
debugPrint("1.3 \(Thread.current)")
}
debugPrint("2")
}
func case6() {
// 结果:1 → 2 → 1.1 → 1.2 → performSeletor → 1.3
debugPrint("1")
DispatchQueue.global(qos: .default).async {
debugPrint("1.1 \(Thread.current)")
self.perform(#selector(self.performSeletor), afterDelay: 1)
debugPrint("1.2 \(Thread.current)")
// perform后runloop唤醒
RunLoop.current.run()
// 1.3 能够执行,是因为定时器执行完毕后已经无效,导致Runloop中没有source,所以线程执行完毕后退出
debugPrint("1.3 \(Thread.current)")
}
debugPrint("2")
}
func case7() {
// 结果:1 → 2 → 1.1 → performSeletor → 1.2 → 1.3
debugPrint("1")
DispatchQueue.global(qos: .default).async {
debugPrint("1.1 \(Thread.current)")
self.perform(#selector(self.performSeletor),
on: Thread.current,
with: nil,
waitUntilDone: true)
debugPrint("1.2 \(Thread.current)")
RunLoop.current.run()
// 1.3 不执行,是因为定时器source无效,Runloop结束了
debugPrint("1.3 \(Thread.current)")
}
debugPrint("2")
}
func case8() {
// 结果:1 → 2 → 1.1 → 1.2 → performSeletor → 1.3不执行
debugPrint("1")
DispatchQueue.global(qos: .default).async {
debugPrint("1.1 \(Thread.current)")
self.perform(#selector(self.performSeletor),
on: Thread.current,
with: nil,
waitUntilDone: false)
debugPrint("1.2 \(Thread.current)")
RunLoop.current.run()
// 1.3 不执行,是因为定时器source还存在,Runloop没有结束
debugPrint("1.3 \(Thread.current)")
}
debugPrint("2")
}
func case9() {
// 结果:1 → 2 → 1.1 → 1.2 → performSeletor → 1.3
debugPrint("1")
DispatchQueue.global(qos: .default).async {
debugPrint("1.1 \(Thread.current)")
self.perform(#selector(self.performSeletor),
on: Thread.current,
with: nil,
waitUntilDone: false)
debugPrint("1.2 \(Thread.current)")
// 演示 1s 后结束runloop
RunLoop.current.run(until: NSDate.init(timeIntervalSince1970: NSDate.init().timeIntervalSince1970 + 1) as Date)
debugPrint("1.3 \(Thread.current)")
}
debugPrint("2")
}
override func viewDidLoad() {
super.viewDidLoad()
case5()
}
}
复制代码
- - 当调用
NSObject
的performSelecter:afterDelay:
后,实际上其内部会创建一个Timer Source
并添加到当前线程的RunLoop
中。所以如果当前线程没有RunLoop
,则这个方法会失效。 - - 当调用
performSelector:onThread:
时,实际上其会创建一个Timer Source
加到对应的线程去,同样的,如果对应线程没有RunLoop
该方法也会失效。
是事件源的performSelector方法有:
// 主线程
performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:
/// 指定线程
performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:
/// 针对当前线程
performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:
/// 取消,在当前线程,和上面两个方法对应
cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:
复制代码
不是事件源的performSelector方法有:
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
复制代码
6、Runloop与AutoreleasePool
一个线程可以包含多个AutoReleasePool,但是一个AutoReleasePool只能对应一个唯一的线程 添加打印Runloop的代码:NSLog(@"[NSRunLoop currentRunLoop] = %@", [NSRunLoop currentRunLoop]);
打印出的日志部分截图如下:
可以发现App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调callout都是
_wrapRunLoopWithAutoreleasePoolHandler()
。
第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件:
BeforeWaiting
(准备进入休眠) 时调用_objc_autoreleasePoolPop()
和_objc_autoreleasePoolPush()
释放旧的池并创建新池;Exit(即将退出Loop) 时调用_objc_autoreleasePoolPop()
来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被
RunLoop
创建好的AutoreleasePool
环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
疑问:子线程默认不会开启 Runloop,那出现 Autorelease 对象如何处理?不手动处理会内存泄漏吗?
解释:
在子线程你创建了 Pool 的话,产生的
Autorelease 对象
就会交给 pool 去管理。 如果你没有创建 Pool ,但是产生了Autorelease 对象
,就会调用autoreleaseNoPage
方法。在这个方法中,会自动帮你创建一个 hotpage(hotPage 可以理解为当前正在使用的AutoreleasePoolPage
,如果你还是不理解,可以先看看 Autoreleasepool 的源代码,再来看这个问题 ),并调用page->add(obj)
将对象添加到AutoreleasePoolPage
的栈中,也就是说你不进行手动的内存管理,也不会内存泄漏啦!StackOverFlow 的作者也说道,这个是 OS X 10.9+和 iOS 7+ 才加入的特性。并且苹果没有对应的官方文档阐述此事,但是你可以通过源码了解。这里张贴部分源代码:
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
// No pool in place.
// hotPage 可以理解为当前正在使用的 AutoreleasePoolPage。
assert(!hotPage());
// POOL_SENTINEL 只是 nil 的别名
if (obj != POOL_SENTINEL && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
(void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
// Install the first page.
// 帮你创建一个 hotpage(hotPage 可以理解为当前正在使用的 AutoreleasePoolPage
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push an autorelease pool boundary if it wasn't already requested.
// POOL_SENTINEL 只是 nil 的别名,哨兵对象
if (obj != POOL_SENTINEL) {
page->add(POOL_SENTINEL);
}
// Push the requested object.
// 把对象添加到 自动释放池 进行管理
return page->add(obj);
}
复制代码
7、Runloop与GCD
Runloop
和GCD
并没有直接的关系,当调用了DispatchQueue.main.async
从子线程到主线程进行通信刷新UI的时候,libDispatch
会向主线程Runloop
发送消息唤醒Runloop
,Runloop
从消息中获取Block
,并且在__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
回调里执行block
操作。dispatch
到非主线程的操作全部是由libDispatch
驱动的。
参考博客
Run Loops 官方文档
RunLoop在iOS开发中的应用
格而知之5:我所理解的Run Loop
深入理解RunLoop
深入研究 Runloop 与线程保活
解密-神秘的 RunLoop
视频学习
runLoop学习笔记
iOS学习之深入理解RunLoop
RunLoop与Timer以及常用Mode
NSTimer需要注意的地方