整理一篇iOS经典面试题大全

1.INTERVIEW 共勉

2.iOS developers 方向

3.INTERVIEW QUESTION

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 单例模式

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.如图,

如果,多个线程访问同一块资源的话,会造成数据错乱的.
我们应该怎么解决呢?

image

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 的基本使用

image

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处理逻辑

image

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请求中包含以下内容

(iOS面试资料大全)

你可能感兴趣的:(整理一篇iOS经典面试题大全)