钟晴

当你的实力撑不起你的梦想,就更应该脚踏实地!


  • Home

  • Tags

  • Categories

  • Archives

  • 读书

  • 电影

  • Search

iOS 多线程实现 --GCD使用详解

Posted on 2019-04-01 | Edited on 2019-05-19 | In 多线程

本文来自:https://www.jianshu.com/p/33151a5bac28

介绍

GCD,英文全称是Grand Central Dispatch(功能强悍的中央调度器),基于C语言编写的一套多线程开发机制,因此使用时会以函数形式出现,且大部分函数以dispatch开头,虽然是C语言的但对于苹果其他多线程实现方式,抽象层次更高,使用起来也更加方便。

它是苹果为应对多核的并行运算提出的解决方案,它会自动利用多核并发处理和运算,它能提供系统级别的处理,而不再局限于某个进程、线程,官方声明会更快、更高效、更灵敏,且线程由系统自动管理(调度、运行),无需程序员参与,使用起来非常方便。

任务和队列

GCD有2个核心:任务和队列

  • 任务:要执行的操作或方法函数,
  • 队列:存放任务的集合,而我们要做的就是将任务添加到队列然后执行,GCD会自动将队列中的任务按先进先出的方式提取并交付给对应的线程。注意任务的取出是按照先进先出的方式,这也是队列的特性,但是取出后执行的顺序不一定,下面会详细讨论。

1任务
任务是一个比较抽象的概念,可以简单的认为是一个操作、一个函数、一个方法等等。在实际的开发中大多是以block的形式,使用起来更加灵活。

2队列queue

  • 有2种队列:串行队列和并行队列。串行队列:同步执行,在当前线程执行;并行队列:可由多个线程异步执行,但任务取出还是FIFO的。

队列创建,根据函数的第二个参数来创建串行或并行队列

1
2
3
// 参数1 队列名称
// 参数2 队列类型 DISPATCH_QUEUE_SERIAL/NULL 串行队列,DISPATCH_QUEUE_CONCURRENT代表并行队列
dispatch_queue_t serialQ = dispatch_queue_create("队列名称",NULL);
  • 另外系统还提供了两种队列:全局队列和主队列

全局队列属于并行队列,只不过由系统创建没有名字,且在全局可见(可用)。获取全局队列

1
2
3
4
5
/* 取得全局队列
第一个参数:线程的优先级,设为默认即可,个人习惯写0,等同于默认
第二个参数:标记参数,目前没有用,一般传入0
*/
serialQ = dispatch_get_global_queue(DISPATCH_QUEQE_PRIORITY_DEFAULT, 0);

主队列属于串行队列,也由系统创建,只不过运行在主线程(UI线程)。获取主队列

1
serialQ = dispatch_get_main_queue();

关于内存:queue属于一个对象,也是占用内存的,也会使用引用计数,当queue添加一个任务时就会将这个queue retain一下,引用计数+1,直到所有任务都完成内存才会释放。(我们在声明queue属性时要用strong)。

3执行方式–2种
同步执行和异步执行
同步执行:不会开启新的线程,在当前线程执行。
异步执行:GCD管理的线程池中有空闲线程就会从队列中取出任务执行,会开启线程。

下面为实现同步和异步的函数,函数功能为:将任务添加到队列并执行

1
2
3
4
5
6
7
/* 同步执行
第一个参数:执行任务的队列:串行、并行、全局、主队列
第二个参数:block任务
*/
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
//异步队列
void dispatch_async(dispatch_queue_t queue, dispathc_block_t block);

注意:默认情况下,新线程都没有开启runloop,所有当block任务完成后,线程都会自动被回收,假设我们想在新开的线程中使用NSTimer,就必须启用runloop。可以使用[[NSRunLoop currentRunLoop] run] 开启当前线程,这时就需要自己管理线程的回收等工作。

  • 另外还用2个方法,实际开发中用的并不是太多
    dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);和 dispatch_barrier_async(dispatch_queue_t queue, disaptch_block_t block);

加了一个barrier,意义在于:队列之前的block处理完成之后才开始处理队列中barrier的block,且barrier的block必须处理完成之后,才能处理其他的block。

三、几种类型

很明显两种执行方式,两种队列。那么就有4种情况:串行队列同步执行、
串行队列异步执行、并行队列同步执行、并行队列异步执行。哪一种会开启新的线程?开几条?是否并发?记忆起来比较绕,但是只要抓住基本的就可以,为了方便理解,现分析如下:

  1. 串行队列,同步执行——串行队列意味着顺序执行,同步执行意味着不开启线程(在当前线程执行)
  2. 串行队列,异步执行——串行队列意味着任务顺序执行,异步执行说明要开线程。(如果开多个线程的话,不能保证串行队列顺序执行,所以只能开一个线程)
  3. 并行队列,异步执行——并行队列意味着执行顺序不确定,异步执行意味着会开启线程,而并行队列又不允许不按顺序执行,所以系统为了提高性能会开启多个线程,来队列提取任务(队列中的任务取出仍然要顺序取出的,只是线程执行无序)。
  4. 并行队列,同步执行——同步执行意味着不开线程,责肯定是顺序执行的。
  5. 死锁—-程序执行不出来

四、死锁举例

主队列死锁:
这种死锁最常见,问题也最严重,会造成主线程卡住.原因:主队列,如果主线程正在执行代码,就不调度任务;同步执行:一直执行第一个任务直到结束.两者相互等待造成死锁,示例代码如下.

1
2
3
4
5
6
7
8
9
- (void)mainThreadDeadLockTest{
NSLog(@"begin");
dispatch_sync(dispatch_get_main_queue(),^{
// 发生死锁下面的代码不会执行
NSLog(@"middle");
});
// 发生死锁下面的代码不会执行,当然函数也不会返回,后果也最为严重
NSLog(@"end");
}

其他线程死锁,这种不会影响主线程:
原因: serialQueue 为串行队列,当代码执行到block1时正常,执行到dispatch_sync时.dispatch_sync等待block2执行完才能返回,而serialQueue是串行队列,它正在执行block1,只有等block1执行完毕之后才会去执行block2,相互等待造成死锁.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)deadLockTest {
//其他线程死锁
dispatch_queue_t serialQueue = dispatch_queue_create("serial_queue",DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue,^{
//串行队列block1
NSLog(@"begin");
dispatch_sync(serialQueue,^{
//串行队列block2 发生死锁,下面的代码不会执行
NSLog(@"middle");
});
// 不会打印
NSLog(@"end");
});
//函数会返回,不影响主线程
NSLog(@"return");
}

五、常用举例

  • 线程间通讯

比如,为了提高用户体验,我们一般在其他线程(非主线程)下载图片或其它网络资源,下载完成后我们要更新UI,而UI更新必须在主线程执行,所以我们经常会使用

1
2
3
4
5
6
7
8
// 同步执行,不阻塞直到下面block中的代码执行完毕
diapatch_sync(dispatch_get_main_queue,^{
// 主线程,UI更新
});
//异步执行
dispatch_async(dispatc_get_main_queue,^{
//主线程,UI更新
})

信号量的使用
也属于线程间通讯,下面的举例是经常使用到的场景,在网络访问中,NSURLSession类都是异步的(找了好久没有找到同步的方法),而有时我们希望能够像NSURLConnection一样可以同步访问,即在网络block调用完之后做一些操作.那我们可以使用dispatch的信号量来解决.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
///用于线程间通讯,下面是等待一个网络完成
- (void)dispatchSemaphore {
NSString *urlString = [@"https:www.baidu.com" stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
//设置缓存策略为每次都从网络加载 超时时间30秒
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:30];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error){
//处理完之后发,送信号量
NSLog(@"正在处理");
dispatch_semaphore_signal(semaphore);
}] resume];
//等待网络处理完成
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"处理完成");
}

在上面的举例中, dispatch_semaphore_signal 的调用必须是在另一个线程调用,因为当前线程已经 dispatch_semaphore_wait阻塞.另外,dispatch_semaphore_wait最好不要在主线程调用

  • 全局队列,实现并发:

    1
    2
    3
    dispatch_async(dispatch_get_global_queue(0,0), ^{
    //要执行相关代码
    })

六、Dispatch Group

使用调度组,可以轻松实现一下任务完成后,做一些操作.比如具有顺序性要求的生产者消费者等等.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)groupTest {
// 创建一个组
dispatch_group_t group = dispatch_group_create();
NSLog(@"开始执行");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
//任务1
//等等一段时间执行
[NSThread sleepForTimeInterval:1];
NSLog(@"task1 runing in %@",[NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_global_queue(0,0), ^{
//任务2
NSLog(@"task2 runing in %@",[NSThread currentThread]);
});
});
}

点击屏幕后打印如下,可以看到任务1虽然等待了1s,任务2也不执行,只有任务1执行完成之后才去执行任务2.

示例2:其实示例1并不常用,真正用到的是监控多个任务完成之后,回到主线程更新UI,或者做其它事情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-(void)groupTest1 {
dispatch_group_t group = dispatch_group_create();
NSLog(@"开始执行");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
//关联任务1
NSLog(@"task 1 running in %@",[NSThread currentThread]);
});
dispatch_group_async(group,dispatch_get_global_queue(0,0), ^{
//关联任务2
NSLog(@"task2 runing in %@",[NSThread currentThread]);
});
dispatch_group_async(group,dispatch_get_global_queue(0, 0), ^{
//关联任务3
NSLog(@"task3 runing in %@",[NSThread currentThread]);
});
dispatch_group_async(group,dispatch_get_global_queue(0, 0), ^{
//关联任务4
[NSThread sleepForTimeInterval:1];
NSLog(@"task4 runing in %@",[NSThread currentThread]);
});
dispatch_group_notify(group,dispatch_get_main_queue(), ^{
//回到主线程执行
NSLog(@"mainTask running in%@",[NSThread currentThread]);
});
});
}

点击屏幕打印如下,无论其他任务的执行完成顺序,mainTask等待他们执行完成后才执行.

019-03-30 21:15:13.903164+0800 GDC[11358:631441] 开始执行
2019-03-30 21:15:13.903705+0800 GDC[11358:631487] task 1 running in <NSThread: 0x600000b6d240>{number = 4, name = (null)}
2019-03-30 21:15:13.903754+0800 GDC[11358:631488] task2 runing in <NSThread: 0x600000b60340>{number = 3, name = (null)}
2019-03-30 21:15:14.906108+0800 GDC[11358:631490] task4 runing in <NSThread: 0x600000b6d340>{number = 5, name = (null)}
2019-03-30 21:15:15.903843+0800 GDC[11358:631489] task3 runing in <NSThread: 0x600000b6d480>{number = 6, name = (null)}
2019-03-30 21:15:15.904085+0800 GDC[11358:631441] mainTask running in<NSThread: 0x600000b3e900>{number = 1, name = main}

关于GCD的内存管理问题

根据上面的代码,可以看出有关dispatch的对象并不是OC对象,那么,用不用像对待Core Foundation框架的对象一样,使用retain、release来管理它呢?答案是不用的.

如果是ARC环境,我们无需管理,会像对待OC对象一样自动内存管理.如果是MRC环境,不是使用retain、release,而是使用dispath_retain/dispatch_release 来管理.

NSDictionary 的实现原理

Posted on 2019-03-29 | Edited on 2019-05-19 | In 实现原理

NSDictionary使用原理

1、NSDictionary (字典)是使用hash表来实现key和value之间的映射存储的,hash函数设计的好坏影响着数据查找访问效率

2、Objective-C 中的字典NSDictionary底层其实就是一个哈希表,实际上绝大多数语言中字典都是通过哈希表实现,

哈希原理

1
哈希概念:哈希表的本质是一个数组,数组中每一个元素称为一个箱子(bin),箱子中存放的是键值对。

哈希表的存储过程:

  1. 根据key计算出它的哈希值h。
  2. 假设箱子的个数为n,那没这个键值对应该放在第(h % n)个箱子中。
  3. 如果该箱子中已经有了键值对,就使用开放寻址法或者拉链法解决冲突
1
2
3
4
在使用拉链法解决哈希冲突时,每个箱子其实是一个链表,属于同一个箱子的所用键值都会排列在链表中。
哈希表还有一个重要的属性:负载因子(load foctor),它用来衡量哈希表的空、满程度,一定程度上也是可以体现查询效率,计算公式为
负载因子 = 总键值对数/箱子个数
负载因子越大,意味着哈希表越满,越容易导致冲突,性能也就越低。因此,一般来说,当负载因子大于某个常数(可能是1,或者0.75)时,哈希表将自动扩容。

重哈希概念:

  1. 哈希表在自动扩容时,一般会创建两倍于原来个数的箱子,因此即使key的哈希值不变,对箱子个数取余结果也会发生改变,因此所有键值对的存放位置可能发生改变,这个过程称为重哈希(rehash)

  2. 哈希表的扩容并不是总能够有效的解决负载因子过大的问题。假设所有的key的哈希值都一样,那么即使扩容以后他们的位置也不会变化。因为负载因子会降低,但实际存储在每个箱子中的链表长度并不发送改变,因此也就不能提高哈希表的查询性能

总结

1、如果哈希表中本来箱子就比较多,扩容时需要重新设计哈希并移动数据,性能影响较大。
2、如果哈希函数设计不合理,哈希表在极端情况下会变成线性表。性能极低。

iOS 内存的几大区域

Posted on 2019-03-29 | Edited on 2019-05-19 | In 原理

本文参考:https://www.jianshu.com/p/6fe8bf22acfb

堆栈(stack):

由系统管理分配和释放。一般存放函数的参数值,和局部变量(函数中的基本数据类型)。栈区的操作方式类似数据结构中的栈(先进后出)

堆区(heap):

由程序员管理(分配释放),若程序员不释放,程序结束时可能由系统回收。一般由程序员 alloc、new 出来的对象。堆的操作方式与数据结构中的堆不同,操作方式类似于链表

全局区(又称静态去)(static)

由编译器管理(分配和释放),程序结束后由系统释放。存放全局变量和static修饰的静态变量。静态区由2块区域组成,一块是存放未初始化的全局变量和静态变量,另一块是初始化已经完成的全局变量和静态变量,这两块是相邻的

文字常量区:

由编译器管理(分配释放),程序结束后由系统存放。存放常量字符串。

程序代码区

存放函数的二进制代码

几大区域详解

  • 申请方式
    栈区:系统自动分配。例如,在函数申明中一个局部变量(int a;),系统会自动在栈区开辟空间

堆区:需要程序员自己申请,并指明大小,例如(char p1 = (char *)malloc(10); char p2 = new char[10];)注意p1、p2、本身是栈区,他俩指向地址是在堆区

申请后系统的响应

  • 栈区:只要栈区的剩余空间大于所申请的空间,系统将为程序提供内存,否则将报异常提示栈溢出
  • 堆区:操作系统会有一个记录空闲内存地址的链表,当系统收到程序申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆节点。然后将该节点重空闲链表中删除,并将改节点的空间分配给程序。另外,对于大多数系统,会在这块内存空间中的首地处记录本次分配的空间的大小,这样,后续代码中的delete语句就可以正确的释放该内存空间。还有找到改堆节点的大小不一定正好等于申请的大小,系统会自动将多余的内存空间重新放入空闲链表中。

申请大小的限制

  • 栈区:栈区是向低地址扩展的数据结构,是一块连续的内存。栈顶的地址和栈的最大容量是系统预先规定好的,是一个在编译是就确定的常数,如果申请的空间超过栈的剩余空间时,会有错误提示。因此,能从栈获得空间较小。
  • 堆区:堆区是搞地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储空内存地址的,自然不是连续的。而链表的遍历反向是由低地址向高地址。堆区的大小受限于计算机系统的虚拟内存,由此可见,堆区获得的空间较灵活,空间较大。

存取效率比较

  • 堆区:堆区是运行时刻赋值的
  • 栈区:栈区也是运行时刻赋值的,栈区的读写速度比堆区块
  • 全局区:编译的时候就确定
  • 其余的现在不确定

申请效率的比较
栈区:栈区由系统自动分配,速度快,但程序员无法控制
堆区:堆区是alloc(c++中使用,即创建新对象)分配的内存,一般速度较慢,而且容易产生内存碎片,不过使用起来方便。

结语:

  • 栈区是点菜(只管吃,其他都不用管),堆区是自助餐(都自己来)

数据持久化的方案

Posted on 2019-03-27 | Edited on 2019-05-19 | In Objective-C

本文来自: https://casatwy.com/iosying-yong-jia-gou-tan-ben-di-chi-jiu-hua-fang-an-ji-dong-tai-bu-shu.html

1 NSUserDefault

一般来说,小规模数据,弱业务相关数据,都可以放到NSUserDefault里面,内容比较多的数据,强业务相关的数据就不太适合NSUserDefault了。

2 keychain

KeyChain 是苹果提供的带有可逆加密的存储机制,普遍用在各种存密码的需求上。另外,由于App卸载只要系统不重装,KeyChain中的数据依旧能够的到保留,依旧可被iCloud同步特性,大家都会在这里存储用户唯一标识串。所有有需要加密,需要存储iCloud的敏感小数据,一般都会放在Keychain。

3 文件存储

文件存储包括了Plist,archive,Stream等方式。一般结构化的数据或者需要方便查询的数据,都会以Plist的方式去持久化。Archive方式适合存储平时不太经常使用但很大量的数据,或者读取之后希望直接对对象的数据,因为Archive会将对数据及其对象关系序列化,以至于读取数据的时候需要Decode很花时间,Decode的过程是解压,也可以是序列化,这个可以根据具体中的实现来决定。Stream就是一般的文件存储了。一般用来存存图片啥的。适用于经常使用,然而数据量又不算非常大的那种。

4 数据库存储

数据库存储,样式就比较多了。苹果自带了一个CoreData,当然业界也有无数代替可选的方案,不过真正用在iOS领域的除了CoreData外,就是FMDB比较多了。数据库方案主要是为了便于增删改查,当数据有状态和类别的时候最好还是采用数据库方案比较好,而且尤其是这些状态和类别都是强业务相关的时候,就更加采用数据库方案了。
因为你不可能通过文件系统遍历文件去甄别你需要获取某个状态或类别的数据,这么做成本太大了。当然,特别大量的数据也不合适直接存储数据库,比如图片或者文章这样的数据,一般来说,都是数据库存一个文件名,然后这个文件名指向的是某个图片或者文章的文件。如果真的要做全文索引这种需求,建议最好还是挂个API丢到服务端去做。

内存管理

Posted on 2019-03-26 | Edited on 2019-05-19 | In 原理

MRC 与 ARC

Objective-C中提供了两种内存管理机制:MRC(MannulReference Counting)和 ARC(Automatic Reference Counting),分别提供对内存的手动和自动管理,来满足不同的需求。现在苹果推荐使用 ARC 来进行内存管理。

MRC

对象操作的四个类别

操作对象 OC中对应的方法 对应的retainCount变化
持有并生成对象 alloc/new/mutableCopy等 +1
持有对象 retain +1
释放对象 release. -1
废弃对象 dealloc

注意
1 这些对象操作的方法并不包含在OC中,而是包含在cocoa框架下的Foundation框架中。从iOS7开始,这些方法被移到Runtime当中,可以objc4-680 NSObject.h 找到
2 对象的 reatinCount 属性并没有实际上的参考价值,参考苹果官方文档《Practical Memory Management》.

四个法则

  1. 自己生成的对象,自己持有
  2. 非自己生成的对象,自己也能持有
  3. 不在需要自己持有的对象的时候,释放
  4. 非自己持有的对象无需释放

autorelease使得对象在超出生命周期后能正确的释放(通过调用release方法)调用release后对象会被立即释放,而调用outorelease后对象不会立即释放,而是注册到autoreleasepool中,经过一段时间后pool结束,此时调用release方法,对象被释放。

在MRC的内存管理模式下,对变量的相关的方法有:reatin ,release和 autorelease。reatin和release方法操作是引用计数,当引用计数为0时,便自动释放内存。并且可以用NSAutoreleasePool对象,对加入自动释放池(autorelease调用)的变量进行管理,当drain时回收内存

ARC

ARC是苹果引入的自动内存管理机制。会根据引用计数自动监测对象的生命周期,实现方式是编译时期自动在已有代码中合适的内存管理代码以以及在Runtime做一些优化。

变量标识符

  1. __strong
  2. __weak
  3. ___unsafe_unretained
  4. __autoreleasing

__strong是默认使用标识符 只要还有一个强指针指向某个对象,这个对象就会一直存活

__weak 声明这个引用不会保存被引用对象的存活,如果对象没有强引用了,弱引用会被置为nil

__unsafe_unretained声明这个引用不会保存引用对象的存活,如果对象没有强引用了,它不会被置为nil。如果它引用的对象被回收掉了,该指针就变成野指针。

__autoreleasing 用于标识使用引用传值的参数(id*),在函数返回时会被自动释放掉。

变量标识用法如下

1
Number* __strong num = [[Number alloc] init];

注意 __strong 的位置应该放到 * 和变量名中间,放到其它位置严格上说不正确的,但编译不会报错

属性标识符

assign 表明setter仅仅是一个简单的赋值操作,通常用于基本数据类型。

strong表明属性定义一个拥有者关系。当给属性设定一个新值的时候,首先这个值进行retain,旧值进行release,然后进行赋值操作

weak表明属性定义一个非拥有者关系,当给属性定义一个新值的时候,这个值不会进行retain,旧值也不会进行release,而是进行类似assign的操作。不过当属性指向的对象被销毁时,该属性会被置为nil

unsafe_unreatained 的语义和assign类似,不过是用于对象类型。表示一个非拥有(unretained)的,同时也不会在对象销毁时置nil的(unsafe)关系

copy类似strong,不过赋值是进行copy操作不是retain操作。通常在需要保留某个可变对象(NSString最常见),并且防止它被意外的串改。

unsafe_unretained 的用处
unsafe_unretained 差不多是实际使用最少的一个标识符了,在使用中它的用处主要有下面几点:
1 兼容性考虑。iOS4以及之前还没有引入weak,这种情况下表达弱引用的语义只能使用unsafe_unretained。现在这种情况很少使用了。
2 性能考虑。使用weak对性能有一些影响,因此对性能要求高的话可以考虑使用unsafe_unretained 替换weak。

引用循环

当2个对象相互持有对方的强引用,并且这2个对象的引用计数都不是0的时候,便造成了引用循环。要想破除引用循环,可以从以下几点入手

  1. 注意变量作用域,使用autorelease 让编译器来处理引用。
  2. 使用弱引用weak
  3. 当实例变量完成工作后,将其置为nil

AutoRelease Pool

AutoRelease Pool 提供一种可以允许你向一个对象延迟发送release操作的机制。当你想放弃一个对象的所有权,同时又不希望这个对象立马被释放掉(例如在一个方法中返回一个对象时),AutoRelease Pool的作用就显现出来了

所谓延迟发送 release 消息是指,当我们把一个对象标记为autorelease时:

1
NSString* str = [[[NSString alloc] initWithString:@"hello"] autorelease];

这个对象的retainCount会+1,但并不会发送release。当这段语句所处的autoreleasepool 进行drain操作时,所标记了autorelease的对象的retainCount会被-1。即release消息发送被延迟到pool释放的时候。

在ARC环境下,苹果引入了@autoreleasepool 语法,不再需要手动调用autorelease 和 drain 等方法。

AutoReleasePool的用处

在ARC下,我们并不需要手动调用autorelease有关的方法,甚至可以完全不知道autorelease的存在,就可以真确的管理好内存。因为Cocos Touch的RunLoop中,每个runloop circle中系统都自动加入AutoReleasePool的创建和释放。

当我们创建和销毁大量对象时,使用手动创建AutoReleasePool 可以有效的避免内存峰值的出现。在这种情况不手动创建的话,外层系统创建的pool会在整个runloop circel结束之后drain,手动创建的话,会在block结束之后进行drain操作。下面是一个普遍的例子:

1
2
3
4
5
6
7
8
 for (int i = 0; i < 100000000; i++)
{
@autoreleasepool
{
NSString* string = @"abc";
NSArray* array = [string componentsSeparatedByString:string];
}
}

如果不使用autoreleasepool,需要在循环结束之后释放100000000个字符串,如果使用的话,则会在每次循环结束的时候都进行release操作。

AutoRelease Pool drain的时机

如上面所说,系统在runloop 中创建的autoreleasepool 会在runloop一个event结束时进行释放操作。我们手动创建的autoreleasepool会在block执行完成之后进行drain操作。需要注意的是:

  • 当block以异常(exception)结束时,pool不会被drain,
  • pool的drain操作会把所用标记为autorelase的对象进行引用计数-1,但并不意味着这个对象一定会被释放掉,我们可以在autorelease pool中手动retain 对象,以延长它的生命周期(在MRC中)。

苹果证书如何转pem文件

Posted on 2019-03-22 | Edited on 2019-05-19

openssl x509 -in apns_miaobozhibo.cer -inform der -out apns_miaobozhibo.pem
注:
apns_miaobozhibo.cer:推送证书
apns_miaobozhibo.pem:要生的pem文件

p12转pem文件
openssl pkcs12 -nocerts -out apns_miaobozhiboKey.pem -in apns_miaobozhibo.p12
注:
apns_miaobozhibo.p12文件
apns_miaobozhiboKey.pem 要生成的pem文件

Hello World

Posted on 2019-03-22

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

1…34
ZQing

ZQing

平时学习的小笔记。
37 posts
13 categories
14 tags
GitHub E-Mail
© 2020 ZQing
Powered by Hexo v3.9.0
|
Theme – NexT.Gemini v7.1.1
|
0%