美文网首页
整理一篇iOS经典面试题大全

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

作者: ios南方 | 来源:发表于2021-08-04 15:05 被阅读0次

    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经典面试题大全

          本文链接:https://www.haomeiwen.com/subject/ybgevltx.html