多线程网络(二)
1.GCD常用的代码
#import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self gcdDemo4]; } #pragma mark - 最常用的组合 - 线程间通讯 // MARK: 最常用的代码! - (void)gcdDemo4 { dispatch_async(dispatch_get_global_queue(0, 0), ^{ // 执行耗时操作! NSLog(@"耗时操作 %@", [NSThread currentThread]); // 更新 UI dispatch_get_main_queue 主队列,专门用来调度任务在主线程上执行的 dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"更新UI %@", [NSThread currentThread]); }); }); } #pragma mark - GCD演练 // MARK: 精简版 /** 与 NSThread 的对比 1. 所有的代码写在一起的,让代码更加简单,易于阅读和维护 NSThread 是通过 selector 来调用指定的方法 2. 使用 GCD 不需要管理线程的创建/销毁/复用的过程! 如果用 NSThread 显然不好搞! */ - (void)gcdDemo3 { for (int i = 0; i < 10; ++i) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"%@", [NSThread currentThread]); }); } } // MARK: 基本演练 - 同步执行任务不开线程 - (void)gcdDemo2 { // 队列 dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // 任务 void (^task)() = ^ { NSLog(@"%@", [NSThread currentThread]); }; // 指定执行任务的函数 dispatch_sync(queue, task); } // MARK: 基本演练 - 异步执行任务,开线程 - (void)gcdDemo1 { // 1. 队列-全局队列 dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // 2. 任务-用 block 定义一个任务 void (^task)() = ^ { NSLog(@"%@", [NSThread currentThread]); }; // 3. 指定任务的"执行函数" -> 同步执行/异步执行 // 同步 - 这一条指令不执行完,"不会"执行下一条语句,顺序执行,不需要开启线程 // 异步 - 这一条指令不执行完,"就可以"执行下一条语句,会开启线程 // "异步"是多线程的代名词 dispatch_async(queue, task); } @end
2.GCD加载网络图片
#import "ViewController.h" @interface ViewController () <UIScrollViewDelegate> @property (nonatomic, strong) UIScrollView *scrollView; @property (nonatomic, weak) UIImageView *imageView; @end @implementation ViewController #pragma mark - UIScrollViewDelegate // "告诉" scrollView 缩放哪一个视图,就应该有"返回值" - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { return self.imageView; } /** 加载视图,和 storyboard & xib 等价的,一旦写了这个方法 storyboard & xib 会实效 创建界面上所有子视图的层次结构! */ - (void)loadView { _scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds]; // 设置缩放比例 _scrollView.minimumZoomScale = 0.5; _scrollView.maximumZoomScale = 2; // 设置代理 _scrollView.delegate = self; self.view = _scrollView; UIImageView *iv = [[UIImageView alloc] init]; _imageView = iv; [self.view addSubview:iv]; } /** 视图加载完毕后执行,加载数据 */ - (void)viewDidLoad { [super viewDidLoad]; // 在后台执行耗时的操作 dispatch_async(dispatch_get_global_queue(0, 0), ^{ // 确定网络上唯一的资源 NSURL *url = [NSURL URLWithString:@"http://d.hiphotos.baidu.com/image/pic/item/0df3d7ca7bcb0a46e18d93706863f6246a60afcf.jpg"]; // 加载图片数据->所有网络上传输的都是"二进制数据" NSData *data = [NSData dataWithContentsOfURL:url]; // 将二进制数据转换成图像 UIImage *image = [UIImage imageWithData:data]; // 耗时操作执行完成后 - 更新UI - 主线程 dispatch_async(dispatch_get_main_queue(), ^{ // 设置图像视图 self.imageView.image = image; // 调整图像视图大小和image一样 [self.imageView sizeToFit]; // 指定滚动视图的范围 self.scrollView.contentSize = image.size; }); }); } @end
3.GCD队列
#import "ViewController.h" @interface ViewController () @end @implementation ViewController /** GCD 核心概念 - 队列-负责"调度"任务 * "串行"队列 - 一个接一个 - 一个任务不调度完,"不会"调度下一个 * "并发"队列 - 可以同时的 - 一个任务不调度完,"可以"调度下一个 * 主队列 - 负责在主线程上调度任务,实现线程间通讯 注意:主队列不是主线程 * 全局队列 - 为了方便程序员使用,提供的一个全局队列,可以直接 get 全局队列就是并发队列,执行效果和自己创建的并发队列完全一样! * 问题:全局队列&并发队列 - 全局队列没有名字 无论 MRC & ARC 都不需要考虑释放 - 并发队列有名字,和 NSThread 的 name 属性很像 如果在 MRC 开发时,需要使用 dispatch_release(q); 释放相应的对象 - 如果我们前期日常开发中,建议使用"全局队列" - 并发队列,是在专业的应用程序或者第三方框架中使用的 - 任务(名词) - 是用 block 封装的一个代码块 block 是一组预先转被好的代码,在需要的时候执行! - 指定任务执行的函数(动词) dispatch_async - 异步执行 * 这一句话不执行完,就可以执行下一句 * 不是阻塞式的 * 异步是多线程的代名词 dispatch_sync - 同步执行 * 这一句不执行完,不能执行下一句 * 是阻塞式的 * 不会开启线程 - 小结 - 开不开线程由执行任务的函数决定 * 异步开,多线程的代名词 * 同步不开 - 异步执行的函数,开几条线程由队列决定 * 串行队列 开一条线程 * 并发队列 开多条线程 - 队列的选择 * 多线程的目的:将耗时的操作放在后台执行! * 串行队列(斯坦福大学的视频),只开一条线程,所有任务顺序执行 * 如果任务有先后执行顺序的要求 * 效率低 -> 执行慢,"省电" * 有的时候,用户其实不希望太快!使用 3G 流量,"省钱" * 并发队列,会开启多条线程,所有任务不按照顺序执行 * 如果任务没有先后执行顺序的要求 * 效率高 -> 执行快,"费电" * WIFI,包月 - 在实际开发中,线程数量如何决定? * WIFI 线程数 6 条 * 3G / 4G 移动开发的时候,2~3条,再多会费电费钱! - 同步任务的作用 - "依赖"唯一 在多线程开发的时候,有的时候,需要使用同步任务,挡住后面的异步任务, 让所有后续的任务,都等待同步任务执行完成之后,才能继续 * 所有后续的任务,都"依赖"最前面的同步任务 "异步是多线程的代名词!" */ - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self gcdDemo3]; } #pragma mark - 全局队列 /** 执行效果:开多个线程,并发执行 队列的效果:全局队列就是并发队列,执行效果和自己创建的并发队列完全一样! */ - (void)gcdDemo8 { /** 参数 1. 服务质量(队列对任务调度的优先级)/iOS 7.0 之前,就是优先级 - iOS 8.0 QOS_CLASS_USER_INTERACTIVE 0x21, 用户交互(希望最快完成-不能用太耗时的操作) QOS_CLASS_USER_INITIATED 0x19, 用户期望(希望快,也不能太耗时) QOS_CLASS_DEFAULT 0x15, 默认(用来底层重置队列使用的,不是给程序员用的) *** QOS_CLASS_UTILITY 0x11, 实用工具(专门用来处理耗时操作!) QOS_CLASS_BACKGROUND 0x09, 后台 QOS_CLASS_UNSPECIFIED 0x00, 未指定,可以和iOS 7.0 适配 - iOS 7.0 DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级 DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级 DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级 DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级 结论:如果要适配 iOS 7.0 & 8.0,使用以下代码: dispatch_get_global_queue(0, 0); 提示:不要选择"后台"的优先级,苹果认为,后台优先级的任务,用户不需要关心什么时候执行完! ** 任务执行会慢得令人发指!不利于调试! 2. 为未来保留使用的,应该永远传入0 */ dispatch_queue_t q = dispatch_get_global_queue(0, 0); for (int i = 0; i < 10; ++i) { dispatch_async(q, ^{ NSLog(@"%@ %d", [NSThread currentThread], i); }); } NSLog(@"come here"); } #pragma mark - 同步任务作用! - (void)gcdDemo7 { /** 在有的时候,是会希望任务执行过程中有一定的先后顺序 -> 依赖关系 举个例子:登录,下载小说 A,下载小说 B... 实际目标:下载过程是并发的,但是登录一定要在最前面! */ dispatch_queue_t q = dispatch_queue_create("ios", DISPATCH_QUEUE_CONCURRENT); // 登录必须第一个执行完,才能执行后序的任务 dispatch_sync(q, ^{ NSLog(@"登录 %@", [NSThread currentThread]); }); dispatch_async(q, ^{ NSLog(@"download A %@", [NSThread currentThread]); }); dispatch_async(q, ^{ NSLog(@"download B %@", [NSThread currentThread]); }); } #pragma mark - 主队列 // MARK: 主队列,同步执行 /** 提问:come here? 猜测:最后/没有! 答案:死锁! */ - (void)gcdDemo6 { dispatch_queue_t q = dispatch_get_main_queue(); NSLog(@"!!!"); dispatch_sync(q, ^{ NSLog(@"%@", [NSThread currentThread]); }); NSLog(@"come here"); } // MARK: 主队列,异步执行 /** 提问:执行顺序,会开启线程吗?come here? 猜测:顺序,不会,最后! 答案:顺序,不会,最前 */ - (void)gcdDemo5 { // 1. 队列,主队列->主队列是随着程序启动就存在的->主队列不需要创建,可以直接获取 dispatch_queue_t q = dispatch_get_main_queue(); // 2. "异步"执行 for (int i = 0; i < 10; ++i) { dispatch_async(q, ^{ NSLog(@"%@ %d", [NSThread currentThread], i); }); } NSLog(@"睡会"); [NSThread sleepForTimeInterval:2.0]; NSLog(@"come here"); } #pragma mark - GCD 演练 // MARK: 并发队列,"同步"任务 /** 提问:执行顺序,会开启线程吗?come here? 猜测:顺序,开"一条",不是最后? 结果:顺序,不开线程,最后 并发队列:可以同时调度多个任务 同步执行:不会开启线程 */ - (void)gcdDemo4 { // 1. 队列 dispatch_queue_t q = dispatch_queue_create("ios", DISPATCH_QUEUE_CONCURRENT); // 2. 任务 for (int i = 0; i < 10; ++i) { dispatch_sync(q, ^{ NSLog(@"%@ %d", [NSThread currentThread], i); }); } NSLog(@"come here"); } // MARK: 并发队列,"异步"任务 /** 提问:执行顺序,会开启线程吗?come here? 答案:不是顺序,开"多条",不是最后 并发队列:同时调度多个任务 异步执行:会开启线程 */ - (void)gcdDemo3 { // 1. 队列 dispatch_queue_t q = dispatch_queue_create("ios", DISPATCH_QUEUE_CONCURRENT); // 2. 执行任务 for (int i = 0; i < 10; ++i) { dispatch_async(q, ^{ NSLog(@"%@ %d", [NSThread currentThread], i); }); } NSLog(@"come here"); } // MARK: 串行队列,异步任务 /** 提问:执行顺序,会开启线程吗?come here? 答案:顺序,开"一条",不是最后 原因: 队列:一个接一个,最多只需要开一个就可以 任务:异步,会开启线程 */ - (void)gcdDemo2 { // 1. 队列 dispatch_queue_t q = dispatch_queue_create("ios", NULL); // 2. 执行任务的函数 for (int i = 0; i < 10; ++i) { NSLog(@"%d------", i); dispatch_async(q, ^{ NSLog(@"%@ %d", [NSThread currentThread], i); }); } NSLog(@"come here"); } // MARK: 串行队列,同步任务(不用!) /** 提问:执行顺序,会开启线程吗?come here? 答案:顺序,不开,最后! 原因:串行队列一个接一个的调度任务 -> 顺序的 同步执行,本身不需要开线程 */ - (void)gcdDemo1 { // 1. 队列 /** 参数: 1. 队列的名称 2. 队列的属性 - 指定是什么类型的队列 DISPATCH_QUEUE_SERIAL 串行 - NULL DISPATCH_QUEUE_CONCURRENT 并发 - 太长,使用 \ 拼接 */ // 以下两句代码是等价的! // dispatch_queue_t q = dispatch_queue_create("ios", DISPATCH_QUEUE_SERIAL); dispatch_queue_t q = dispatch_queue_create("ios", NULL); // 2. 指定任务执行的函数 for (int i = 0; i < 10; ++i) { NSLog(@"%d------", i); dispatch_sync(q, ^{ NSLog(@"%@ %d", [NSThread currentThread], i); }); } NSLog(@"come here"); } @end
4.GCD增强
#import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self gcdDemo4]; } /** 通过 block 的嵌套,可以组合出非常复杂的任务关系! 很难用一些表格或者规则表述出所有的情况! 要理解队列和任务之间的关系,一定要理清"队列"的特点&"执行任务"函数的特点! 提示:多线程开发要"尽量简单"!不要给自己"埋的太深"! */ // MARK: 同步任务增强 - 依赖关系 - (void)gcdDemo4 { dispatch_queue_t q = dispatch_get_global_queue(0, 0); void (^task)() = ^ { // 1. 异步执行,在其他线程执行 NSLog(@"===> %@", [NSThread currentThread]); // 2. 同步任务不开启线程,就在当前线程立即执行 // 同步任务不执行完,就不能执行下一条指令 // 同步任务执行完毕,线程就空闲出来,后面的并发完全可以利用! dispatch_sync(q, ^{ NSLog(@"login %@", [NSThread currentThread]); }); // 3. 同步执行完毕后,并发执行后续两个任务 dispatch_async(q, ^{ NSLog(@"download A %@", [NSThread currentThread]); }); dispatch_async(q, ^{ NSLog(@"download B %@", [NSThread currentThread]); }); }; dispatch_async(q, task); } - (void)gcdDemo3 { dispatch_queue_t q = dispatch_get_global_queue(0, 0); // 问题: login 在主线程,用户登录也是耗时操作! // 因为同步任务不会开启线程 => 同步任务在那一个线程都不会开启线程 dispatch_sync(q, ^{ NSLog(@"login %@", [NSThread currentThread]); }); dispatch_async(q, ^{ NSLog(@"download A %@", [NSThread currentThread]); }); dispatch_async(q, ^{ NSLog(@"download B %@", [NSThread currentThread]); }); } // MARK:同步任务不死锁 /** 主队列在调度任务的时候,如果主线程上有任务,就暂时不调度 造成主线程和主队列相互等待,造成死锁 */ - (void)gcdDemo2 { dispatch_queue_t q = dispatch_get_global_queue(0, 0); void (^task)() = ^ { NSLog(@"hehe"); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"%@", [NSThread currentThread]); }); }; dispatch_async(q, task); NSLog(@"come here"); } - (void)gcdDemo1 { dispatch_queue_t q = dispatch_get_main_queue(); NSLog(@"hehe"); dispatch_sync(q, ^{ NSLog(@"%@", [NSThread currentThread]); }); } @end
5.GCD延时
#import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self delay]; } // MARK: 延时功能 - (void)delay { /** 从现在开始,经过多少纳秒,由"队列"调度异步执行 block 中的代码 参数 1. when 从现在开始,经过多少纳秒 2. queue 队列 3. block 异步执行的任务 */ dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)); NSLog(@"haha"); // dispatch_after(when, dispatch_get_main_queue(), ^{ // NSLog(@"%@", [NSThread currentThread]); // }); // 全局队列 延时多长时间后,异步执行某些方法! // dispatch_after(when, dispatch_get_global_queue(0, 0), ^{ // NSLog(@"%@", [NSThread currentThread]); // }); // 串行队列 - 也会开线程! dispatch_after(when, dispatch_queue_create("ios", NULL), ^{ NSLog(@"%@", [NSThread currentThread]); }); NSLog(@"come here"); } @end
6.只执行一次的代码
#import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self demoOnce]; } // MARK: 多线程测试 dispatch_once - (void)demoOnce { for (int i = 0; i < 10; ++i) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ [self once]; }); } } /** 有的时候,在程序开发中,有些代码只想从程序启动就只执行一次 典型的应用场景就是“单例” 有的人会以为单例内部使用的是"互斥锁"! */ - (void)once { // token 令牌,只要 token == 0,才会执行 block 中的代码 // dispatch 内部也有一把锁,是能够保证"线程安全"的!而且是苹果公司推荐使用的 // 应该能够做到,多线程执行的时候,block 同样只会执行一次! static dispatch_once_t onceToken; NSLog(@"come here %ld", onceToken); // 同步执行!可以保证后续的代码能够使用到一次性执行后的结果! dispatch_once(&onceToken, ^{ // 只需要执行一次的代码 [NSThread sleepForTimeInterval:1.0]; NSLog(@"%@", [NSThread currentThread]); }); NSLog(@"come here"); } @end
7.单例
#import <Foundation/Foundation.h> /** "单"例的特点 1. 在内存中只有一个实例 2. 提供一个全局的访问点-类方法统一方法 目前使用过的单例: - UIApplication - NSUserDefaults - NSFileManager - NSNotificationCenter ... 目前在 iOS 开发中,单例已经到达被滥用的程度!很多公司的面试题中都要求手写单例! */ @interface Singleton : NSObject /** * 用 dispatch_once 建立的对象 */ + (instancetype)sharedSingleton; /** * 用互斥锁建立的对象 */ + (instancetype)syncSingleton; @end
#import "Singleton.h" @implementation Singleton + (instancetype)sharedSingleton { // 永远只有一个实例,因此需要保存在静态区,随着应用程序启动就分配空间,随着应用程序一起被销毁 static id instance; // 可以保证对象只被实例化一次 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; }); return instance; } + (instancetype)syncSingleton { static id syncInstance; @synchronized(self) { // 懒加载的写法,不能保证线程安全 if (syncInstance == nil) { syncInstance = [[self alloc] init]; } } return syncInstance; } @end
#import "ViewController.h" #import "Singleton.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } /** 互斥锁真的没有dispatch_once性能好! dispatch_once是苹果推荐的一次性执行的解决方案! */ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { long largeNumber = 1000 * 10000; // dispatch_once // 开始时间 CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); for (int i = 0; i < largeNumber; ++i) { [Singleton sharedSingleton]; } // 结束时间 CFAbsoluteTime end = CFAbsoluteTimeGetCurrent(); NSLog(@"dispatch once %f", end - start); // 互斥锁 start = CFAbsoluteTimeGetCurrent(); for (int i = 0; i < largeNumber; ++i) { [Singleton syncSingleton]; } end = CFAbsoluteTimeGetCurrent(); NSLog(@"互斥锁 %f", end - start); } @end
8.调度组
#import "ViewController.h" @interface ViewController () @end @implementation ViewController /** 调度组 有的时候,在执行异步任务的时候,会希望所有的相关任务执行完毕后,统一得到通知! 举个例子:下载小说:红楼梦,西游记,其他小说... 每次提示也是耗电的! */ - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self group2]; } // MARK: 调度组 /** AFN,内部使用到了 enter & leave 代码! 终端输入 $ man dispatch_group_enter void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block) { dispatch_retain(group); dispatch_group_enter(group); dispatch_async(queue, ^{ block(); dispatch_group_leave(group); dispatch_release(group); }); } */ - (void)group2 { // 实例化调度组 dispatch_group_t group = dispatch_group_create(); // 队列 dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // 进入组(增加一个标记-后面再定义的 block,就会被添加到组中) // *** dispatch_group_enter & dispatch_group_leave 必须成对出现 // *** dispatch_group_leave 必须是最后一句 dispatch_group_enter(group); // 定义任务 void (^task1)() = ^ { NSLog(@"download A %@", [NSThread currentThread]); // "当所有任务完成之后",离开组->group 就能够知道任务完成了! dispatch_group_leave(group); }; dispatch_group_enter(group); // 定义任务 void (^task2)() = ^ { NSLog(@"download B %@", [NSThread currentThread]); [NSThread sleepForTimeInterval:2.0]; // "当所有任务完成之后",离开组->group 就能够知道任务完成了! dispatch_group_leave(group); }; // 将任务添加到队列 dispatch_async(queue, task1); dispatch_async(queue, task2); // 监听群组任务的结束 // dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // NSLog(@"OVER"); // }); // 等待调度组中所有任务执行完毕,时间是永远->死等,一直要等到所有任务执行完成 // dispatch_group_wait 等待方式是同步的(知道就行!) dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog(@"come here"); } // MARK: 调度组 - (void)group1 { // 实例化调度组 dispatch_group_t group = dispatch_group_create(); // 队列 dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // 添加任务 dispatch_group_async(group, queue, ^{ [NSThread sleepForTimeInterval:1.0]; NSLog(@"download A, %@", [NSThread currentThread]); }); dispatch_group_async(group, queue, ^{ [NSThread sleepForTimeInterval:0.8]; NSLog(@"download B, %@", [NSThread currentThread]); }); dispatch_group_async(group, queue, ^{ [NSThread sleepForTimeInterval:0.9]; NSLog(@"download C, %@", [NSThread currentThread]); }); // dispatch_group_notify 可以监听调度组中所有的异步任务,在执行完毕后,获得通知! // 监听工作是异步的! dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 监听到所有任务执行完成后,在主线程执行 block // 以下载小说为例:可以异步下载小说,下载完毕后,在主线程更新UI NSLog(@"OVER %@", [NSThread currentThread]); }); NSLog(@"come here"); } @end
9.时钟
#import "ViewController.h" @interface ViewController () @end @implementation ViewController /** 运行循环 作用: 1. 保证程序不会退出 2. 监听所有的事件:触摸、时钟、网络事件 3. 如果没有任何事件发生,会进入休眠状态,省电 4. 一旦有事件发生,运行循环会再次启动,并且处理事件 在"今天"的实际开发中,运行循环几乎用不倒,概念非常重要! runloop是在iOS开发的原始时代,需要大量使用的! */ - (void)viewDidLoad { [super viewDidLoad]; // 就是用 default 模式,将时钟添加到运行循环! // [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateTimer) userInfo:nil repeats:YES]; NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(updateTimer) userInfo:nil repeats:YES]; // 将时钟添加到运行循环 /** NSDefaultRunLoopMode - 默认的运行循环模式:监听时钟,网络等跟UI无关的事件 NSRunLoopCommonModes - 通用的运行循环模式:监听用户交互事件,优先级最高,保证用户一旦交互,立即做出响应! */ [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; } /** 时钟的触发方法是同步的还是异步的 => 并没有开启线程,是同步的! 一定需要注意的,时钟触发方法中,不能有太耗时的操作!否则会造成主线程的卡顿! */ - (void)updateTimer { static int num = 0; NSLog(@"睡"); [NSThread sleepForTimeInterval:1.0]; NSLog(@"%d %@", num++, [NSThread currentThread]); } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end
10.时钟(多线程)
#import "ViewController.h" @interface ViewController () @end @implementation ViewController /** 运行循环 作用: 1. 保证程序不会退出 2. 监听所有的事件:触摸、时钟、网络事件 3. 如果没有任何事件发生,会进入休眠状态,省电 4. 一旦有事件发生,运行循环会再次启动,并且处理事件 在"今天"的实际开发中,运行循环几乎用不倒,概念非常重要! runloop是在iOS开发的原始时代,需要大量使用的! NSDefaultRunLoopMode - 默认的运行循环模式:监听时钟,网络等跟UI无关的事件 NSRunLoopCommonModes - 通用的运行循环模式:监听用户交互事件,优先级最高,保证用户一旦交互,立即做出响应! */ - (void)viewDidLoad { [super viewDidLoad]; // 将时钟放在异步触发 dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"开启时钟 %@", [NSThread currentThread]); NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(updateTimer) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; // 如果要监听事件,需要启动子线程的运行循环!仅供了解 // 这句话是真正的死循环!启动运行循环,不再执行后续的代码! // 如果用 run 直接启动运行循环,很难停掉循环!线程永远不会被终止 [[NSRunLoop currentRunLoop] run]; NSLog(@"come here"); }); } - (void)updateTimer { static int num = 0; NSLog(@"睡"); [NSThread sleepForTimeInterval:1.0]; NSLog(@"%d %@", num++, [NSThread currentThread]); } // 停止时钟 - (IBAction)stopTimer:(id)sender { } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end
11.停止运行循环
#import "ViewController.h" @interface ViewController () // 标记是否停止 @property (nonatomic, assign, getter=isFinished) BOOL finished; @end @implementation ViewController /** 运行循环 作用: 1. 保证程序不会退出 2. 监听所有的事件:触摸、时钟、网络事件 3. 如果没有任何事件发生,会进入休眠状态,省电 4. 一旦有事件发生,运行循环会再次启动,并且处理事件 在"今天"的实际开发中,运行循环几乎用不倒,概念非常重要! runloop是在iOS开发的原始时代,需要大量使用的! NSDefaultRunLoopMode - 默认的运行循环模式:监听时钟,网络等跟UI无关的事件 NSRunLoopCommonModes - 通用的运行循环模式:监听用户交互事件,优先级最高,保证用户一旦交互,立即做出响应! */ - (void)viewDidLoad { [super viewDidLoad]; // 将时钟放在异步触发 dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"开启时钟 %@", [NSThread currentThread]); NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(updateTimer) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; // 如果要监听事件,需要启动子线程的运行循环!仅供了解 self.finished = NO; // 手写了一个死循环 // 问题:性能不好!这种方法是通过 OC 停止运行循环使用最广泛的方法。 while (!self.isFinished) { // 让运行循环监听 0.1 秒的事件 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; } // 一旦执行到这句话,就说明线程要死亡了! NSLog(@"come here"); }); } - (void)updateTimer { static int num = 0; NSLog(@"睡"); [NSThread sleepForTimeInterval:1.0]; NSLog(@"%d %@", num++, [NSThread currentThread]); } // 停止时钟 - (IBAction)stopTimer:(id)sender { self.finished = YES; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end
12.CF停止运行循环
#import "ViewController.h" @interface ViewController () /** 时钟线程所在的运行循环 */ @property (nonatomic, assign) CFRunLoopRef timerRunloop; @end @implementation ViewController /** 运行循环 作用: 1. 保证程序不会退出 2. 监听所有的事件:触摸、时钟、网络事件 3. 如果没有任何事件发生,会进入休眠状态,省电 4. 一旦有事件发生,运行循环会再次启动,并且处理事件 在"今天"的实际开发中,运行循环几乎用不倒,概念非常重要! runloop是在iOS开发的原始时代,需要大量使用的! NSDefaultRunLoopMode - 默认的运行循环模式:监听时钟,网络等跟UI无关的事件 NSRunLoopCommonModes - 通用的运行循环模式:监听用户交互事件,优先级最高,保证用户一旦交互,立即做出响应! */ - (void)viewDidLoad { [super viewDidLoad]; // 将时钟放在异步触发 dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"开启时钟 %@", [NSThread currentThread]); NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(updateTimer) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; // 如果要监听事件,需要启动子线程的运行循环!仅供了解 // 使用 CoreFoundation 是 C 语言的框架 // 以下代码,是绝大多数第三方框架中使用的方法! self.timerRunloop = CFRunLoopGetCurrent(); // 启动运行循环 // 利用底层的方式启动运行循环,不需要程序员再写 while 死循环,就能够利用 runloop 本身的特性 // 没有事件自动休眠 CFRunLoopRun(); // 一旦执行到这句话,就说明线程要死亡了! NSLog(@"come here"); }); } - (void)updateTimer { static int num = 0; NSLog(@"睡"); [NSThread sleepForTimeInterval:1.0]; NSLog(@"%d %@", num++, [NSThread currentThread]); } // 停止时钟 - (IBAction)stopTimer:(id)sender { // 让时钟所在线程的运行循环停止 CFRunLoopStop(self.timerRunloop); } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end
13.自动释放池
#import <Foundation/Foundation.h> @interface Person : NSObject @property (nonatomic, copy) NSString *name; + (instancetype)personWithName:(NSString *)name; @end
#import "Person.h" @implementation Person /** 有alloc/init就需要有release */ + (instancetype)personWithName:(NSString *)name { // autorelease 的目的->延迟释放 // autorelase的对象出了作用域才会被添加到自动释放池中->达到延迟释放 // Person *p = [[[self alloc] init] autorelease]; Person *p = [[self alloc] init]; p.name = name; // [p release]; return p; // [p release]; } @end
#import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } // 一个大数 long largeNumber = 1000; - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { /** 问题:以下代码有什么问题? 如果有?如何解决? 有几种解决方式?请简述各种方式的优缺点及效率! 在 Foundation 框架中,如果是返回对象的类方法,对象都是自动释放的 - autorelease 的对象 答案: 1. 每一个str 都是自动释放的,如果循环次数很大,会占用过多的自动释放池空间!甚至让自动释放池耗尽! 2. 解决方法:网络上有两种解决方法! 1> 在for循环外面使用自动释放池,一个运行循环不仅仅只有一个for,前后都会有代码 结果:在for循环结束后,倾倒自动释放池! 2> 在for循环内使用自动释放池 结果:内存非常的平缓! 3. 哪一个执行效率高! 网上答案统一是外面快! 在实际工作中,应该毫不犹豫的使用自动释放池! */ CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); [self question1]; CFAbsoluteTime end = CFAbsoluteTimeGetCurrent(); NSLog(@"%f", end - start); start = CFAbsoluteTimeGetCurrent(); [self question2]; end = CFAbsoluteTimeGetCurrent(); NSLog(@"%f", end - start); } - (void)question2 { NSLog(@"开始"); for (int i = 0; i < largeNumber; i++) { @autoreleasepool { NSString *str = [NSString stringWithFormat:@"Hello - %04d", i]; str = [str stringByAppendingString:@" - World"]; str = [str uppercaseString]; } } NSLog(@"内 --- 结束"); } - (void)question1 { NSLog(@"开始"); @autoreleasepool { for (int i = 0; i < largeNumber; i++) { NSString *str = [NSString stringWithFormat:@"Hello - %04d", i]; str = [str stringByAppendingString:@" - World"]; str = [str uppercaseString]; } } NSLog(@"外 --- 结束"); } - (void)question { // 问题:以下代码有什么问题?如果有?如何解决?有几种解决方式?请简述各种方式的优缺点及效率! for (int i = 0; i < largeNumber; i++) { NSLog(@"----- %d", i); NSString *str = [NSString stringWithFormat:@"Hello - %04d", i]; NSLog(@"%p", str); str = [str stringByAppendingString:@" - World"]; NSLog(@"%p", str); str = [str uppercaseString]; NSLog(@"%p", str); NSLog(@"%@", str); } } @end