1.INTERVIEW 共勉
2.INTERVIEW 准备
3.iOS developers 方向
4.INTERVIEW QUESTION
4.1深copy和浅copy
- 浅拷贝:
1.对内存地址的复制,让目标对象指针和源对象指向同一片内存空间.
2.内存销毁的时候,指向这片空间的指针需要重新定义才可以使用,要不然会成为野指针
3.拷贝指向原来对象的指针,使原对象的引用计数加+1
4.相当于创建了一个指向原对象的新指针,并没有创建一个新的对象.
深拷贝:
1.拷贝对象的具体内容,而内存地址是自主分配的
2.拷贝结束之后,两个对象存在的值是相同的,内存地址是不一样的
3.两个对象没有任何关系本质区别:
1.深拷贝是内容拷贝,浅拷贝是指针拷贝
2.是否有新的内存地址
3.是否影响内存地址的引用计数.案例一
NSString * str1 = @"copyStr";
NSMutableString *str2 = [str1 copy];
NSMutableString *str3 = [str1 mutableCopy];
NSLog(@"str1:%p--%@",str1,str1);
NSLog(@"str1:%p--%@",str2,str2);
NSLog(@"str1:%p--%@",str3,str3);
2018-04-14 14:50:54.117652+0800 MutyCopy-Copy[2644:63575] str1:0x109a48068--copyStr
2018-04-14 14:50:54.117885+0800 MutyCopy-Copy[2644:63575] str1:0x109a48068--copyStr
2018-04-14 14:50:54.118010+0800 MutyCopy-Copy[2644:63575] str1:0x600000259a40--copyStr
1.str1,str2地址相同,而Str3地址不同
2.NSString的copy是浅拷贝,copy返回的对象是不可变对象
3.mutablecopy是深拷贝
*案例二:
NSMutableString * str1 = [NSMutableString stringWithString:@"mutableStr"];
NSMutableString * str2 = [str1 copy];
NSMutableString * str3 = [str1 mutableCopy];
NSLog(@"str:%p-----%@",str1,str1);
NSLog(@"str:%p-----%@",str2,str2);
NSLog(@"str:%p-----%@",str3,str3);
2018-04-14 15:04:50.092820+0800 MutyCopy-Copy[2685:70866] str:0x60000025b210-----mutableStr
2018-04-14 15:04:50.093059+0800 MutyCopy-Copy[2685:70866] str:0x60000022ca40-----mutableStr
2018-04-14 15:04:50.093217+0800 MutyCopy-Copy[2685:70866] str:0x60000025b540-----mutableStr
1.str1,str2,str3地址都不同
2.NSMutableString对象copy与mutableCopy都是深拷贝
3.copy返回的对象是不可变对象
4.2 iOS程序的启动过程
- 首先找到程序入口,执行main函数
- main -->> UIApplicationMain
- 创建UIApplication对象
- 创建UIApplication的代理对象,给UIApplication对象代理属性赋值
- 开启主运行循环,作用接收事件,让程序一直运行
- 加载info.plist,判断有没有指定main.storyboard,如果指定就去加载.
4.3 loadView
- 什么时候被调用?
每次访问VC的view而且view为nil,loadView方法被调用 - 作用
loadView方法是用来负责创建VC的view. - 默认实现是怎样的?
默认实现即[spuer loadView]
1.它会先去查找与UIViewController相关联的xib文件,通过加载xib文件来创建VC的view.
2.如果在初始化VC指定了xib文件名,就会根据传入的xib文件名加载对应的xib文件.如果没有明显xib文件名,就会加载跟自己同名的xib文件.
3.如果没有找到关联的xib文件,就会创建一个空白的UIView,然后赋值给VC的view属性
4.4 单例模式
可以看我地址:
https://www.jianshu.com/p/9e9b405b4d94
4.5 多线程
进程
1.进程是指系统中正在运行的一个应用程序
2.每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内.线程
1.1个进程要想执行任务,必须得有线程,每1个进程至少要有1条线程
2.线程是进程的基本执行单元
3.一个进程的素有任务都在线程中执行多线程
1.1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务
2.进程--工厂,线程--工厂工人
3.多线程可以提高程序的执行效率
比如,我们同时开启2条线程下载文件A,文件B.
- 多线程的原理
1.同一时间,CPU只能处理1条线程,只有1条线程在工作
2.多线程并发执行,是CPU快速地在多条线程之间调度切换
注意:如果线程非常非常多,会发生什么情况?
cpu会在多个多线程之间进行调度,消耗大量的CPU资源.这样的话,每条线程被调度执行的频次会降低,线程执行效率降低.
多线程的优缺点
1.优点:
1.1.能适当的提高程序的执行效率
1.2.能适当调高资源利用率
2.缺点:
2.1.开启线程需要占用一定的内存空间,如果开启大量的线程,会占用大量的内存空间,降低程序的性能
2.2线程越多,CPU在调度线程上的开销就越大.多线程应用
1.什么是主线程?
一个iOS程序运行后,默认会开启1条线程 ,称为主线程
2.主线程的主要作用?
2.1.显示/刷新UI界面
2.2.处理UI事件
3.主线程使用注意?
3.1.别将比较耗时的操作放到主线程中
3.2.耗时操作会卡住主线程,严重影响UI的流畅度
4.多线程实现技术方案?
pthread,NSThread,GCD,NSOperation四中方案.
4.6 NSThread
- 创建,启动线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadFun) object:nil];
[thread start];
线程一启动,就会告诉CPU准别就绪,可以随时接受CPU调度.CPU调度当前线程之后,就会在线程thread中执行self的run方法
- 主线程用法
- 其他方式创建线程
创建线程后自动启动线程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
隐式创建并启动线程
[self performSelectorInBackground:@selector(run) withObject:nil];
-
线程状态
1.启动线程,start.就绪状态-->>运行状态.当新厂任务执行完毕,自动进入死亡状态
2.阻塞(暂停)线程,进入阻塞状态
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
3.强制停止线程
进入死亡状态
注意:一旦线程停止了,就不能再次开启任务.
-
多线程的安全隐患
1.资源共享
一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源.当多线程访问同一块资源时,很容易引发数据错乱和数据安全问题.
2.如图,
如果,多个线程访问同一块资源的话,会造成数据错乱的.
我们应该怎么解决呢?
3.如图,
线程A和线程B同时访问资源变量Integer,
为了防止抢夺资源,
线程A在读取资源变量Integer之前先加一把锁,
然后读取Integer的数据并在线程A中完成数据操作(17+1=18),
然后把数据写入Integer中,
最后开锁Unlock.在线程A对Integer操作的过程中,
线程B是无权访问Integer的,
只有线程A_Unlock后,线程B才可以访问资源变量Integer.
4.互斥锁使用格式
@synchronized(self){//需要锁定的代码}
注意: 锁定1分代码只用1把锁,用多把锁是无效的
5.互斥锁的优缺点
互斥锁的使用前提:多条线程抢夺同一块资源
优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的CPU
6.nonatomic和atomic
atomic:
原子属性,为setter方法加锁(默认就是atomic)
线程安全,需要消耗大量的资源
nonatomic:
非原子属性,不会为setter方法加锁
非线程安全,适合内存小的移动设备
4.7 GCD
- 什么是GCD?
全程Grand Central Dispatch,中枢调度器
纯C语言,提供了非常多强大的函数 - GCD的优势
1.GCD是苹果公司为多核的并行运算提出的解决方案
2.GCD会自动利用更多的CPU内核
3.GCD自动管理线程的生命周期(创建线程,调度任务,销毁线程) - GCD有2个核心概念
1.任务:执行什么操作
2.队列;用来存放任务 - 任务和队列
1.执行任务
GCD中有2个用来执行任务的函数
1.1用同步的方式执行任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:队列
block:任务
1.2用异步的方式执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
1.3同步和异步的区别
同步:只能在当前线程中执行任务,不具备开启新线程的能力
异步:可以再新的线程中执行任务,具备开启新线程的能力
- 队列的类型
GCD的队列可以分为2大类型
并发队列:可以让多个任务并发执行(并发功能只能在异步函数下才有效)
串行队列:让任务一个接着一个地执行 - 容易混淆的术语
有4个术语比较容易混淆:
同步,异步,并发,串行
注意: 同步函数 + 主队列 == 死锁
- 并发队列
GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建
使用dispatch_get_global_queue函数获得全局的并发队列
dispatch_queue_t dispatch_get_global_queue(
dispatch_queue_priority_t priority, 队列的优先级
unsigned long flags);
全局并发队列
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- 串行队列
GCD中获得串行的2中途径
1.使用dispatch_queue_create函数创建串行队列
dispatch_queue_t =
dispatch_queue_create(const char*label, 队列名称
dispatch_queue_attr_t attr); 队列属性,一般用NULL即可
2.使用主队列
放在主队列中的任务,都会放到主线程中执行
使用dispatch_get_main_queue()获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
- 从子线程回到主线程
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
执行耗时的异步操作...
dispatch_async(dispatch_get_main_queue(), ^{
回到主线程,执行UI刷新操作
});
});
- 延时执行
设定好延迟的时间后,它会先执行后边的代码,2秒后再调用self的run方法(并且不会卡主线程,在主线程调最后会回到主线程,在子线程调最后会回到子线程)
withObject:参数
afterDelay:延迟的时间
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
使用GCD函数(2秒后自动开启新线程 执行block中的代码,不会卡主当前的线程,在主/子线程调用都可以使用)
DISPATCH_TIME_NOW:现在开始的意
2.0 * NSEC_PER_SEC:设置的秒数(直接更改数字即可)
dispatch_get_main_queue():主队列的意思
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
2秒后执行这里的代码... 在哪个线程执行,跟队列类型有关
});
3.会卡住主线程
[NSThread sleepForTimeInterval:3]
- 只执行一次
使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
在设计模式中,单例模式也会用到
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
程序运行过程中,永远只执行1次的代码(这里面默认是线程安全的)
});
- 队列组
需求:1.分别异步执行2个耗时的操作,其次,等2个异步操作都执行完毕后,再回到主线程执行操作
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
执行1个耗时的异步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
执行1个耗时的异步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
等前面的异步操作都执行完毕后,回到主线程...
});
GCD的创建和释放
在iOS6.0之前,在GCD中每当使用带creat单词的函数创建对象之后,都应该对其进行一次release操作.
在iOS6.0之后,GCD被纳入到了ARC的内存管理机制中,在使用GCD的时候我们就像对待普通OC对象一样对待GCD,因此不再需要我们调用release方法.-
GCD 的基本使用
1.异步函数+并发队列
1.创建队列(并发队列)
dispatch_queue_t queue = dispatch_queue_create("com.baidu.www", DISPATCH_QUEUE_CONCURRENT);
异步函数
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
2.异步函数+串行队列
1.创建队列(串行队列)
dispatch_queue_t queue = dispatch_queue_create("com.baidu.www", DISPATCH_QUEUE_SERIAL);
异步函数
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
3.同步函数+串行队列
1.创建队列(串行队列)
dispatch_queue_t queue = dispatch_queue_create("com.baidu.www", DISPATCH_QUEUE_SERIAL);
同步函数
dispatch_sync(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
4.同步函数+并发队列
//获得全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 同步函数
dispatch_sync(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
5.异步函数+主队列
1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
异步函数
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
6.同步函数+主队列
1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
4.8 NSOperation
- NSOperation作用?
配合使用NSOperation 和NSOperationQueue也能实现多线程编程 - NSOperation 和NSOperationoQueue实现多线程的具体步骤
NSOperation的子类
NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类
子类的方式有3中:
1.NSInvocationOperation
2.NSBlockOperation
3.自定义子类继承NSOperation,实现内部响应的方法NSInvocationOperation
1.创建对象
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
2.调用start方法开始执行操作
- (void)start;
一旦执行操作,就会调用target的sel方法
默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作;只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作
- NSBlockOperation
1.创建NSBlockOperation对象
+ (id)blockOperationWithBlock:(void (^)(void))block;
通过addExecutionBlock:方法添加更多的操作
- (void)addExecutionBlock:(void (^)(void))block;
只要NSBlockOperation封装的操作数 > 1,就会异步执行操作
- NSOperationQueue作用
如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperationQueue中的操作
- (void)addOperation:(NSOperation *)operation;
- (void)addOperationWithBlock:(void (^)(void))block;
- 最大并发数
同时执行的任务数
最大并发数相关的方法
-(NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
- 自定义NSOperation
重写-(void)main方法,在里面实现想执行的任务
重写-(void)main方法的注意点:自动创建自动释放池,如果是异步操作,无法访问主线程的自动释放池
4.9 RunLoop
- 如果没有RunLoop,程序输出后就退出了
int main(int argc, char * argv[]) {
NSLog(@"main");
return 0;
}
- 如果有了RunLoop,由于main函数里面启动了RunLoop,所以程序并不会马上退出,保持持续运行状态
int main(int argc, char * argv[]) {
BOOL run = YES;
do{
//执行各种任务,处理各种事件
}while(run);
return 0;
}
- main函数中的RunLoop,UIApplicationMaiin函数内部就启动了一个RunLoop,所以UIApplicationMain函数一直没有返回,保持了程序的持续运行,这个默认启动的RunLoop跟主线程相关联
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
RunLoop与线程
1.每条线程都有唯一的与之对应的RunLoop对象
2.主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
3.RunLoop在第一次获取时创建,在线程结束时销毁-
获得RunLoop对象
-
RunLoop相关类
Core Foundation中关于RunLoop的5个类:
CFRunLoopRef:它自己,也就代表一个RunLoop对象
CFRunLoopModeRef:RunLoop的运行模式
CFRunLoopSourceRef:事件源
CFRunLoopTimerRef:时间的触发器
CFRunLoopbaserverRef:观察者 监听CFRunLoopRef的状态
CFRunLoopModeRef
系统默认注册了5个Mode模式:
kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode-
RunLoop处理逻辑
1.通知观察者,即将进入Loop
2.通知观察者,将要处理定时器
3.通知观察者,将要处理非基于端口的源
4.处理非基于端口的源
5.如果有基于端口的源准备好并处于等待状态,立即启动,跳到第9步
6.通知观察者,线程即将休眠
7.休眠,等待唤醒
8.通知观察者,线程刚被唤醒
9.处理唤醒时收到的消息,之后跳到第2步
10.通知观察者,即将推出Loop
-
RunLoop应用
RunLoop面试题
1.什么是RunLoop?
字面意思运行循环
其实它内部就是do-while循环,这个循环内部不断处理各种任务(比如Source,Timer,Observer);
一个线程对应一个RunLoop,主线程的RunLoop默认启动,子线程的RunLoop手动启动;
RunLoop只能选择一个Mode启动,如果当前Mode中没有任何Source,Timer,那么就直接退出RunLoop.
4.10 HTTP通信过程-请求/响应
HTTP协议规定:1个完整的由客户端发给服务器的HTTP请求中包含以下内容