---恢复内容开始---
pthread小结(了解)
- 在 C 语言中,没有
对象
的概念,对象是以结构体
的方式来实现的 - 通常,在 C 语言框架中,对象类型以
_t/Ref
结尾,而且声明时不需要使用*
- C 语言中的
void *
和 OC 中的id
是等价的 - 内存管理
- 在 OC 中,如果是
ARC
开发,编译器会在编译时,根据代码结构,自动添加retain
/release
/autorelease
- 但是,
ARC
只负责管理OC
部分的内存管理,而不负责C 语言
代码的内存管理 - 因此,开发过程中,如果使用的
C
语言框架出现retain
/create
/copy
/new
等字样的函数,大多都需要release
,否则会出现内存泄漏
- 在 OC 中,如果是
- 在混合开发时,如果在
C
和OC
之间传递数据,需要使用__bridge
进行桥接,桥接
的目的就是为了告诉编译器如何管理内存 - 桥接的添加可以借助 Xcode 的辅助功能添加
MRC
中不需要使用桥接
NSThread小结:(了解)
GCD :(掌握)
异步执行任务:
- (void)gcdDemo1 { // 1. 全局队列 dispatch_queue_t q = dispatch_get_global_queue(0, 0); // 2. 任务 void (^task)() = ^ { NSLog(@"%@", [NSThread currentThread]); }; // 3. 指定执行任务的函数 // 异步执行任务 - 新建线程,在新线程执行 task dispatch_async(q, task); NSLog(@"come here"); }
异步执行任务(精简代码):
- (void)gcdDemo2 { for (int i = 0; i < 10; ++i) {
//下面这句代码的意思就是:把任务添加到全局队列异步执行 dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"%@ %@", [NSThread currentThread], @"hello"); }); } }
同步异步:执行任务的函数
队列:负责调度任务
笔记中说的贼清楚了
串行队列同步执行:不开线程
串行队列异步执行:开线程
开不开线程决定权在于:同步还是异步 异步就开线程,不是异步就不开线程
//串行,异步 //开线程,一个线程,保证任务任然有序
//串行,同步 //在当前线程 //有序执行
//并发, 同步 //不开线程,任务有序==串行,同步
//并发, 异步 //开很多线程,线程复用,优化的措施之一 //无序 //在老版本Xcode GCD线程不会很多,但是在新的Xcode中,极限情况下,60+线程,线程数量太多,苹果推荐各位使用NSOperation //执行效率最大
死锁:只有在主队列同步才会发生,建议用主队列异步,主队列异步是在主线程执行任务
进程是正在运行的程序,线程是基本的执行单元,队列呢是用来调度任务的,线程是执行队列中的任务,执行任务函数包括同步和异步,用来决定是否开线程
//特殊: //1.一定在主线程 //2.在主线程不忙的时候,队列才会调度任务去主线程执行 //死锁:谁也执行不了,在主线程上主队列,同步 //推荐大家使用主队列,异步执行
全局并发队列就当并发队列去用,一般不是特殊情况,没有必要自己去创建一个并发队列.
队列组:
要知道是干嘛用的:
队列组就是用来处理下载多个任务时,所有异步任务下载完后再通知队列组说,我下完了,可以安装了,例如,我需要下载一个游戏,这个游戏很大,需要很多压缩包,并且所有压缩包下载完毕后才去执行安装,所以这个时候是需要队列组来处理.
// // ViewController.m // 队列组的小练习 // // Created by 曹魏 on 2016/12/8. // Copyright © 2016年 itcast. All rights reserved. // #import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self demo2]; } - (void)demo2{ /* 查看更多的信息 man dispatch_group_async 终端 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); }); } */ //1.创建一个队列组 dispatch_group_t group = dispatch_group_create(); //2.添加下载任务到队列组 dispatch_group_enter(group); dispatch_async(dispatch_get_global_queue(0, 0), ^{ //模拟延时 [NSThread sleepForTimeInterval:arc4random_uniform(5)]; NSLog(@"LOL1.zip %@",[NSThread currentThread]); dispatch_group_leave(group); }); // dispatch_group_async(group,dispatch_get_global_queue(0, 0), ^{ // [NSThread sleepForTimeInterval:arc4random_uniform(5)]; // NSLog(@"LOL2.zip %@",[NSThread currentThread]); // // }); dispatch_group_enter(group); dispatch_async(dispatch_get_global_queue(0, 0), ^{ //模拟延时 [NSThread sleepForTimeInterval:arc4random_uniform(5)]; NSLog(@"LOL2.zip %@",[NSThread currentThread]); dispatch_group_leave(group); }); //3.异步任务执行完毕后通知队列组下载完成,可以弹出个提示框,提示安装之类 的 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"下载完成,可以安装 %@",[NSThread currentThread]); }); } //队列组,有时候我们下载游戏或者其他大型的东西时,不可能只有一个压缩包,因为只有一个压缩包很大,下载速度超级慢, //所以会分好几个压缩包,异步下载,全部压缩包下载完后才能解压,这个时候就用到了队列组的概念 - (void)demo1{ //1.创建一个队列组 dispatch_group_t group = dispatch_group_create(); //2.添加下载任务到队列组 dispatch_group_async(group,dispatch_get_global_queue(0, 0), ^{ //模拟延时 [NSThread sleepForTimeInterval:arc4random_uniform(5)]; NSLog(@"LOL1.zip %@",[NSThread currentThread]); }); dispatch_group_async(group,dispatch_get_global_queue(0, 0), ^{ [NSThread sleepForTimeInterval:arc4random_uniform(5)]; NSLog(@"LOL2.zip %@",[NSThread currentThread]); }); //3.异步任务执行完毕后通知队列组下载完成,可以弹出个提示框,提示安装之类 的 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"下载完成,可以安装 %@",[NSThread currentThread]); }); } @end
如何实现一次性执行?
什么叫一次性执行?就是多线程同时执行一个方法,怎么保证只执行一次:dispatch_once_t
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [self demo1]; // for (int i = 0; i<20; i++) { // dispatch_async(dispatch_get_global_queue(0, 0), ^{ // [self demo1]; // }); // } } - (void)demo1 { //问题:在多线程的环境下 不能保证盼有一定有用,因为线程是切换运行的 // if(isinit) // return; // NSLog(@"demo1"); // isinit = YES; NSLog(@"begin"); /** * 线程安全的一次性执行 当onceToken为0 的时候,表示没有执行过,执行过之后,非0 同步执行 */ static dispatch_once_t onceToken; NSLog(@"%ld",onceToken); dispatch_once(&onceToken, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"demo1---------"); }); NSLog(@"over"); }
单例:dispatch_once_t
如果面试官问到:你们单例怎么写的?你了解单例吗?
答:首先回答什么是单例?单例就是指在内存中只有一个实例对象,那么为什么要用到单例呢?因为在程序的开发过程中,有些代码我只想让它执行一次,这里就用到了单例.
单例一般我们都是用dispatch_once_t,为什么能做到只访问一次,这是因为dispatch中有一把锁,这把锁的名称是互斥锁,
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc]init]; }); return instance;
当程序第一次进去的时候,long型的onceToken是为0,这是说明程序还没有执行,那么执行一次后,onceToken的值变成非0 ,互斥锁打开,阻止后面的程序进入,达到只执行一次的效果.
如果我想无论怎么实例化该单例类下的对象,都保证这个对象是个单例对象,怎么操作呢?即我想让下面代码中的person对象成为一个单例对象,怎么操作呢?
// // ViewController.m // 单例 // // Created by apple on 16/8/20. // Copyright © 2016年 itcast. All rights reserved. // #import "ViewController.h" #import "HMPerson.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; HMPerson *person = [[HMPerson alloc] init];//这个方法去实例化对象,即alloc方法,会调用系统的allocwithzone方法,所以我要想保证代码只执行一次就要在这个方法里面加一把互斥锁,如下个代码所示. HMPerson *person2 = [HMPerson sharedPerson];//这个方法道理也一样,在sharedPerson方法中加一把互斥锁,同样达到只执行一次的效果 HMPerson *person3 = person2.copy;//这个方法的前提是必须让person遵循NSCoping协议,协议里有个方法叫copyWithZone方法,person2.copy,由于是浅拷贝,所以就是person3指针指向了person2的地址,所以返回的还是person2对象,下面补充一下关于copy的知识. NSLog(@"%@,%@",person,person3); NSLog(@"sss"); } @end
static id instance; +(instancetype)sharedPerson{ //开发的时候,推荐这种写法,没有必要重写allocwithzone来禁用alloc方法等,提供更灵活创建对象的方式 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc]init]; }); return instance; } +(instancetype)allocWithZone:(struct _NSZone *)zone{ //保证内存分配一次 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [super allocWithZone:zone]; }); return instance; } -(id)copyWithZone:(NSZone *)zone{ return instance; }
copy的深层次理解:
首先,什么是copy?
Copy的字面意思是“复制”、“拷贝”,是一个产生副本的过程。
常见的复制有:文件复制,作用是利用一个源文件产生一个副本文件。
特点:1、修改源文件的内容,不会影响副本文件;
2、修改副本文件的内容,不会影响源文件。
OC中copy的作用是:利用一个源对象产生一个副本对象
特点:1、修改源对象的属性和行为,不会影响副本对象;
2、修改副本对象的属性和行为,不会影响源对象。
如何使用copy功能?
一个对象可以调用copy或mutableCopy方法来创建一个副本对象。
1、copy:创建的时不可变副本(如NSString、NSArray、NSDictionary)。
2、mutableCopy:创建的可变副本(如NSMutableString、NSMutableArray、NSMutableDictionary)。
使用copy功能的前提:
1、copy:需要遵守NSCopying协议,实现copyWithZone:方法。
@protocol NSCopying
- (id)copyWithZone:(NSZone *)zone;
2、mutableCopy : 需要遵守NSMutableCopying协议,实现mutableCopyWithZone:方法
@protocol NSMutableCopying
- (id)mutableCopyWithZone:(NSZone *)zone;
@end
深复制和浅复制的区别:
深复制(深拷贝、内容拷贝、deep copy):
特点:1、源对象和副本对象是不同的两个对象;
2、源对象引用计数器不变,副本对象计数器为1(因为是新产生的)。
本质:产生了新对象。
浅复制(浅拷贝、指针拷贝、shallow copy):
特点:1、源对象和副本对象是同一对象;
2、源对象(副本对象)引用计数器+1,相当于做一次retain操作。
本质:没有产生新对象。
常见的复制如下图:
只有源对象和副本对象都不可变时,才是浅复制,其他都是深复制。
关于区分深复制与浅复制的一些详细代码如下:
/** NSMutableString调用mutableCopy : 深复制 */ void mutableStringMutableCopy() { NSMutableString *srcStr = [NSMutableString stringWithFormat:@"age is %d", 10]; NSMutableString *copyStr = [srcStr mutableCopy]; [copyStr appendString:@"abc"]; NSLog(@"srcStr=%@, copyStr=%@", srcStr, copyStr); } /** NSMutableString调用copy : 深复制 */ void mutableStringCopy() { NSMutableString *srcStr = [NSMutableString stringWithFormat:@"age is %d", 10]; NSString *copyStr = [srcStr copy]; [srcStr appendString:@"abc"]; NSLog(@"srcStr=%p, copyStr=%p", srcStr, copyStr); } /** NSString调用mutableCopy : 深复制 */ void stringMutableCopy() { NSString *srcStr = [NSString stringWithFormat:@"age is %d", 10]; NSMutableString *copyStr = [srcStr mutableCopy]; [copyStr appendString:@"abc"]; NSLog(@"srcStr=%@, copyStr=%@", srcStr, copyStr); } /** NSString调用copy : 浅复制 */ void stringCopy() { // copy : 产生的肯定是不可变副本 // 如果是不可变对象调用copy方法产出不可变副本,那么不会产生新的对象 NSString *srcStr = [NSString stringWithFormat:@"age is %d", 10]; NSString *copyStr = [srcStr copy]; NSLog(@"%p %p", srcStr, copyStr); } @property内存管理策略的选择 1.非ARC 1> copy : 只用于NSString\block; 2> retain : 除NSString\block以外的OC对象; 3> assign : 基本数据类型、枚举、结构体(非OC对象),当2个对象相互引用,一端用retain,一端 用assign。 2.ARC 1> copy : 只用于NSString\block; 2> strong : 除NSString\block以外的OC对象; 3> weak : 当2个对象相互引用,一端用strong,一端用weak; 4> assgin : 基本数据类型、枚举、结构体(非OC对象)。
消息循环:NSRunLoop
面试官肯定会问:你了解消息循环吗?或者你对NSRunLoop了解多少?
回答:了解,首先消息循环的目的是:
1.保证程序不退出,随时接收输入消息(或者说是输入源),为什么我要加入这个消息循环?或者说为什么我要让程序不退出?因为程序一旦运行结束就退出了,就不能接收消息了,举个例子,我们玩微信,有个事件是:我输入一句话,这个事件肯定在程序中执行,但是如果不用消息循环,程序执行完就退出了,即微信自动退出,那么再发消息难道要重新运行程序吗?所以说这就是消息循环存在的意义.
2.负责处理输入事件
3.如果没有事件发生,会让程序进入休眠状态
接下来说:消息循环处理的事件分为两大时间:1.input Sources(输入源事件)2.timer Sources(定时源事件)
输入源事件:I/O :I是Input O是Output 输入源一般是指:键盘,鼠标,NSPort,NSConnection等 输出源一般指:显示屏,音响,打印机
定时源事件:就是指NSTimer事件
接下来说:一般情况下我们怎么使用消息循环?
使用消息循环大致分三步:1.创建事件 2.获得当前消息循环对象 3.设置消息循环模式
然后答:消息循环的模式分为两个:1.NSDefaultRunLoopMode--默认模式 2.NSRunLoopCommonModes--普通模式
主线程默认是开启消息循环的,子线程是默认不开启消息循环的
子线程必须手动开启消息循环,即
//开启 [runloop run];
有三种开启消息循环的方法:
第一种:直接run(一般情况下这一个就足够了,但缺点是开启了不能关闭,如果想用代码去关闭,可以采用第二种方法)
第二种:apple推荐的方式 BOOL shouldKeepRunning = YES; // global NSRunLoop *theRL = [NSRunLoop currentRunLoop]; //运行一次 while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); 第三种:第三种指定时间,不靠谱 [runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
具体消息循环代码如下所示:
// // ViewController.m // 消息循环 // // Created by apple on 16/8/20. // Copyright © 2016年 itcast. All rights reserved. // #import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; } //主线程 -(void)demo1{ /* 1.时间间隔 2.对象 3.方法 4.其他信息 5.是否重复 */ NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(task) userInfo:nil repeats:YES]; //2.得到当前线程的消息循环 NSRunLoop *runloop = [NSRunLoop currentRunLoop]; //3.把定时源加入到消息循环中 /* 1.定时源 timer 2.模式 */ /* NSDefaultRunLoopMode 触摸界面->timer停止 NSRunLoopCommonModes 触摸界面不影响timer的运行 kCFRunLoopDefaultMode->UITrackingRunLoopMode->kCFRunLoopDefaultMode NSRunLoopCommonModes起码包含kCFRunLoopDefaultMode UITrackingRunLoopMode模式的一组集合 */ [runloop addTimer:timer forMode:NSRunLoopCommonModes]; } - (void)task { //打印当前现成的消息循环 //kCFRunLoopDefaultMode NSRunLoop *runloop = [NSRunLoop currentRunLoop]; NSString *str = runloop.currentMode; NSLog(@"task is running %@",str); } #pragma mark 子线程 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [self demo2]; } -(void)demo2{ //子线程 //结论:主线程里面的消息循环是开启的,子线程默认不开启 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(mytask) object:nil]; [thread start]; //往子线程的消息循环中添加事件 /* 1.方法 2.线程 3.参数 4.waitUntilDone 是否等待 把一个输入员添加到指定线程的消息循环中 */ [self performSelector:@selector(test2) onThread:thread withObject:nil waitUntilDone:NO]; } -(void)mytask{ NSLog(@"---->%@",[NSThread currentThread]); //开启子线程的消息循环 NSRunLoop *runloop = [NSRunLoop currentRunLoop]; //开启 [runloop run]; // apple推荐的方式 // BOOL shouldKeepRunning = YES; // global // NSRunLoop *theRL = [NSRunLoop currentRunLoop]; // //运行一次 // while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); // // // 第三种指定时间,不靠谱 // [runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; NSLog(@"over"); } -(void)test2{ NSLog(@"???? test2"); } @end
注意:如果子线程没有手动打开消息循环,打印如下:
2016-12-09 23:33:37.013 消息循环[4824:501434] ----><NSThread: 0x608000260740>{number = 3, name = (null)} 2016-12-09 23:33:37.014 消息循环[4824:501434] over
即不会执行test2方法,是因为,子线程默认不开启消息循环,所以,程序运行后会退出,当子线程开启的时候,会立即执行mytask方法,而且,mytask方法并没有什么耗时任务,所以来不及添加test2方法就已经程序执行完毕,退出了,所以就没有执行test2方法.
如果开启消息循环,打印结果如下:
2016-12-09 23:46:40.963 消息循环[4855:506953] ----><NSThread: 0x600000071bc0>{number = 3, name = (null)} 2016-12-09 23:46:40.963 消息循环[4855:506953] ???? test2
那么问题来了,为什么没有执行NSLog(@"over");方法?
因为,消息循环一旦开启,执行到消息循环开启的方法[runloop run];就像一个漩涡一样,一旦进去就不会出来了,就会一直执行循环,不会运行下面的代码.
补充消息循环:
1. 什么是消息循环?
runloop就是消息循环,每一个线程内部都有一个消息循环
主线程消息循环默认开启,子线程消息循环默认不开启,要手动开启
2.消息循环目的?
保证程序不退出
负责处理输入事件
如果没有事件发生,会让程序进入休眠状态
消息循环的步骤:
1.创建事件 2.获取当前消息循环,并且把事件加入到消息循环 3.指定事件模式
事件模式要和消息循环模式相匹配才能执行下去
事件模式分为两种:第一种是NSDefaultRunLoopMode 第二种是NSRunLoopCommonModes 而第二种模式是一个模式组,包含第一种模式
消息循环模式:没有触摸界面时默认是kCFRunLoopDefaultMode,触摸界面时会变成UITrackingRunLoopMode,停止触摸又变回kCFRunLoopDefaultMode
所以当我往SB上拖拽一个textView时,上下拉它还能继续进行消息循环的只有commonModes模式匹配.
另外还要注意的是子线程手动开启消息循环的方法是run方法
//开启
[runloop run];
总结两把锁:自旋锁联想到上厕所很急,急得团团转,就是自旋锁,
互斥锁联想到排他性,就是单例里的.
---恢复内容结束---