2019年iOS面试题

作者: 酷拼车 | 来源:发表于2019-05-30 15:59 被阅读17次

    1、#import跟 #include 有什么区别,@class呢,#import<> 跟 #import””有什么区别?

    答:
    1). #import是Objective-C导入头文件的关键字,#include是C/C++导入头文件的关键字,使用#import头文件会自动只导入一次,不会重复导入。
    2). @class告诉编译器某个类的声明,当执行时,才去查看类的实现文件,可以解决头文件的相互包含。
    3). #import<>用来包含系统的头文件,#import””用来包含用户头文件。
    

    2、@property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?

    属性可以拥有的特质分为四类:
    1.原子性--- nonatomic 特质
    2.读/写权限---readwrite(读写)、readonly (只读)
    3.内存管理语义---assign、strong、 weak、unsafe_unretained、copy
    4.方法名---getter=<name> 、setter=<name>
    5.不常用的:nonnull,null_resettable,nullable
    

    3、属性关键字 readwrite,readonly,assign,retain,copy,nonatomic 各是什么作用,在那种情况下用?

    答:
    1). readwrite 是可读可写特性。需要生成getter方法和setter方法。
    2). readonly 是只读特性。只会生成getter方法,不会生成setter方法,不希望属性在类外改变。
    3). assign 是赋值特性。setter方法将传入参数赋值给实例变量;仅设置变量时,assign用于基本数据类型。
    4). retain(MRC)/strong(ARC) 表示持有特性。setter方法将传入参数先保留,再赋值,传入参数的retaincount会+1。
    5). copy 表示拷贝特性。setter方法将传入对象复制一份,需要完全一份新的变量时。
    6). nonatomic 非原子操作。决定编译器生成的setter和getter方法是否是原子操作,atomic表示多线程安全,一般使用nonatomic,效率高。
    

    4、什么情况使用 weak 关键字,相比 assign 有什么不同?

    1.在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性。
    2.自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。
    
    IBOutlet连出来的视图属性为什么可以被设置成weak?
        因为父控件的subViews数组已经对它有一个强引用。
    
    不同点:
    assign 可以用非 OC 对象,而 weak 必须用于 OC 对象。
    weak 表明该属性定义了一种“非拥有关系”。在属性所指的对象销毁时,属性值会自动清空(nil)。
    

    5、怎么用 copy 关键字?

     用途:
     1. NSString、NSArray、NSDictionary 等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary;
     2. block 也经常使用 copy 关键字。
    
     说明:
     block 使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区.在 ARC 中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 也无伤大雅,还能时刻提醒我们:编译器自动对 block 进行了 copy 操作。如果不写 copy ,该类的调用者有可能会忘记或者根本不知道“编译器会自动对 block 进行了 copy 操作”,他们有可能会在调用之前自行拷贝属性值。这种操作多余而低效。
    

    6、用@property声明的 NSString / NSArray / NSDictionary 经常使用 copy 关键字,为什么?如果改用strong关键字,可能造成什么问题?

    答:用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作(就是把可变的赋值给不可变的),为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。
    
    1. 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本。
    2. 如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。
    
    //总结:使用copy的目的是,防止把可变类型的对象赋值给不可变类型的对象时,可变类型对象的值发送变化会无意间篡改不可变类型对象原来的值。
    

    7、浅拷贝和深拷贝的区别?

    答:
    浅拷贝:只复制指向对象的指针,而不复制引用对象本身。
    深拷贝:复制引用对象本身。内存中存在了两份独立对象本身,当修改A时,A_copy不变。
    

    8、系统对象的 copy 与 mutableCopy 方法

    不管是集合类对象(NSArray、NSDictionary、NSSet ... 之类的对象),还是非集合类对象(NSString, NSNumber ... 之类的对象),接收到copy和mutableCopy消息时,都遵循以下准则:
    1. copy 返回的是不可变对象(immutableObject);如果用copy返回值调用mutable对象的方法就会crash。
    2. mutableCopy 返回的是可变对象(mutableObject)。
    
    一、非集合类对象的copy与mutableCopy
      在非集合类对象中,对不可变对象进行copy操作,是指针复制,mutableCopy操作是内容复制;
      对可变对象进行copy和mutableCopy都是内容复制。用代码简单表示如下:
        NSString *str = @"hello word!";
        NSString *strCopy = [str copy] // 指针复制,strCopy与str的地址一样
        NSMutableString *strMCopy = [str mutableCopy] // 内容复制,strMCopy与str的地址不一样
    
        NSMutableString *mutableStr = [NSMutableString stringWithString: @"hello word!"];
        NSString *strCopy = [mutableStr copy] // 内容复制
        NSMutableString *strMCopy = [mutableStr mutableCopy] // 内容复制
    
    二、集合类对象的copy与mutableCopy (同上)
      在集合类对象中,对不可变对象进行copy操作,是指针复制,mutableCopy操作是内容复制;
      对可变对象进行copy和mutableCopy都是内容复制。但是:集合对象的内容复制仅限于对象本身,对集合内的对象元素仍然是指针复制。(即单层内容复制)
        NSArray *arr = @[@[@"a", @"b"], @[@"c", @"d"];
        NSArray *copyArr = [arr copy]; // 指针复制
        NSMutableArray *mCopyArr = [arr mutableCopy]; //单层内容复制
       
        NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
        NSArray *copyArr = [mutableArr copy]; // 单层内容复制
        NSMutableArray *mCopyArr = [mutableArr mutableCopy]; // 单层内容复制
        
    【总结一句话】:
        只有对不可变对象进行copy操作是指针复制(浅复制),其它情况都是内容复制(深复制)!
    

    9、Objective-C 如何对内存管理的,说说你的看法和解决方法?

    答:Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池。
    1). 自动内存计数ARC:由Xcode自动在App编译阶段,在代码中添加内存管理代码。
    2). 手动内存计数MRC:遵循内存谁申请、谁释放;谁添加,谁释放的原则。
    3). 内存释放池Release Pool:把需要释放的内存统一放在一个池子中,当池子被抽干后(drain),池子中所有的内存空间也被自动释放掉。内存池的释放操作分为自动和手动。自动释放受runloop机制影响。
    

    10、Objective-C 中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么?

    答:线程创建有三种方法:使用NSThread创建、使用GCD的dispatch、使用子类化的NSOperation,然后将其加入NSOperationQueue;在主线程执行代码,方法是performSelectorOnMainThread,如果想延时执行代码可以用performSelector:onThread:withObject:waitUntilDone:
    

    11、为什么我们常见的delegate属性都用是week而不是retain/strong?

    答:是为了防止delegate两端产生不必要的循环引用。
    @property (nonatomic, weak) id<UITableViewDelegate> delegate;
    

    12、什么时候用delete,什么时候用Notification?

    Delegate(委托模式):1对1的反向消息通知功能。
    Notification(通知模式):只想要把消息发送出去,告知某些状态的变化。但是并不关心谁想要知道这个。
    

    13、什么是 KVO 和 KVC?

    1). KVC(Key-Value-Coding):键值编码 是一种通过字符串间接访问对象的方式(即给属性赋值)
        举例说明:
        stu.name = @"张三" // 点语法给属性赋值
        [stu setValue:@"张三" forKey:@"name"]; // 通过字符串使用KVC方式给属性赋值
        stu1.nameLabel.text = @"张三";
        [stu1 setValue:@"张三" forKey:@"nameLabel.text"]; // 跨层赋值
    2). KVO(key-Value-Observing):键值观察机制 他提供了观察某一属性变化的方法,极大的简化了代码。
         KVO只能被KVC触发,包括使用setValue:forKey:方法和点语法。
       // 通过下方方法为属性添加KVO观察
       - (void)addObserver:(NSObject *)observer
                         forKeyPath:(NSString *)keyPath
                         options:(NSKeyValueObservingOptions)options
                         context:(nullable void *)context;
       // 当被观察的属性发送变化时,会自动触发下方方法                   
       - (void)observeValueForKeyPath:(NSString *)keyPath
                                  ofObject:(id)object
                                      change:(NSDictionary *)change
                                     context:(void *)context{}
        
    KVC 和 KVO 的 keyPath 可以是属性、实例变量、成员变量。
    

    14、KVC的底层实现?

    当一个对象调用setValue方法时,方法内部会做以下操作:
    1). 检查是否存在相应的key的set方法,如果存在,就调用set方法。
    2). 如果set方法不存在,就会查找与key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值。
    3). 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值。
    4). 如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。
    这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。
    

    15、KVO的底层实现?

    KVO基于runtime机制实现。
    

    16、block的注意点

    1). 在block内部使用外部指针且会造成循环引用情况下,需要用__week修饰外部指针:
        __weak typeof(self) weakSelf = self; 
    2). 在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下。
        __strong typeof(self) strongSelf = weakSelf;
    3). 如果需要在block内部改变外部栈区变量的话,需要在用__block修饰外部变量。
    

    17、BAD_ACCESS在什么情况下出现?

    答:这种问题在开发时经常遇到。原因是访问了野指针,比如访问已经释放对象的成员变量或者发消息、死循环等。
    

    18、你一般是怎么用Instruments的?

    Instruments里面工具很多,常用:
    1). Time Profiler: 性能分析
    2). Zombies:检查是否访问了僵尸对象,但是这个工具只能从上往下检查,不智能。
    3). Allocations:用来检查内存,写算法的那批人也用这个来检查。
    4). Leaks:检查内存,看是否有内存泄露。
    

    19、iOS中常用的数据存储方式有哪些?

    数据存储有四种方案:NSUserDefault、KeyChain、file、DB。
        其中File有三种方式:plist、Archive(归档)
        DB包括:SQLite、FMDB、CoreData
    

    、iOS多线程技术有哪几种方式?
    答:pthread、NSThread、GCD、NSOperation

    20、GCD 与 NSOperation 的区别:

    GCD 和 NSOperation 都是用于实现多线程:
        GCD 基于C语言的底层API,GCD主要与block结合使用,代码简洁高效。
        NSOperation 属于Objective-C类,是基于GCD更高一层的封装。复杂任务一般用NSOperation实现。
    

    21、写出使用GCD方式从子线程回到主线程的方法代码

    答:dispatch_sync(dispatch_get_main_queue(), ^{ });
    

    22、如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)

    // 使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。
    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_async(group, queue, ^{ /*加载图片1 */ });
    dispatch_group_async(group, queue, ^{ /*加载图片2 */ });
    dispatch_group_async(group, queue, ^{ /*加载图片3 */ }); 
    // 当并发队列组中的任务执行完毕后才会执行这里的代码
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            // 合并图片
    });
    

    23、dispatch_barrier_async(栅栏函数)的作用是什么?

    函数定义:dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
    作用:
        1.在它前面的任务执行结束后它才执行,它后面的任务要等它执行完成后才会开始执行。
        2.避免数据竞争
    
    // 1.创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    // 2.向队列中添加任务
    dispatch_async(queue, ^{  // 1.2是并行的
        NSLog(@"任务1, %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任务2, %@",[NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"任务 barrier, %@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{   // 这两个是同时执行的
        NSLog(@"任务3, %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任务4, %@",[NSThread currentThread]);
    });
    
    // 输出结果: 任务1 任务2 ——》 任务 barrier ——》任务3 任务4 
    // 其中的任务1与任务2,任务3与任务4 由于是并行处理先后顺序不定。
    

    24、以下代码运行结果如何?

    - (void)viewDidLoad {
        [super viewDidLoad];
        NSLog(@"1");
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"2");
        });
        NSLog(@"3");
    }
    // 只输出:1。(主线程死锁)
    

    25、什么是 RunLoop

    从字面上讲就是运行循环,它内部就是do-while循环,在这个循环内部不断地处理各种任务。
    一个线程对应一个RunLoop,基本作用就是保持程序的持续运行,处理app中的各种事件。通过runloop,有事运行,没事就休息,可以节省cpu资源,提高程序性能。
    
    主线程的run loop默认是启动的。iOS的应用程序里面,程序启动后会有一个如下的main()函数
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    

    26、什么是 Runtime

    Runtime又叫运行时,是一套底层的C语言API,其为iOS内部的核心之一,我们平时编写的OC代码,底层都是基于它来实现的。
    

    27、Runtime实现的机制是什么,怎么用,一般用于干嘛?

    1). 使用时需要导入的头文件 <objc/message.h> <objc/runtime.h>
    2). Runtime 运行时机制,它是一套C语言库。
    3). 实际上我们编写的所有OC代码,最终都是转成了runtime库的东西。
        比如:
            类转成了 Runtime 库里面的结构体等数据类型,
            方法转成了 Runtime 库里面的C语言函数,
            平时调方法都是转成了 objc_msgSend 函数(所以说OC有个消息发送机制)
        // OC是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。
        // [stu show];  在objc动态编译时,会被转意为:objc_msgSend(stu, @selector(show));    
    4). 因此,可以说 Runtime 是OC的底层实现,是OC的幕后执行者。
    

    有了Runtime库,能做什么事情呢?

    Runtime库里面包含了跟类、成员变量、方法相关的API。
    比如:
    (1)获取类里面的所有成员变量。
    (2)为类动态添加成员变量。
    (3)动态改变类的方法实现。
    (4)为类动态添加新的方法等。
    因此,有了Runtime,想怎么改就怎么改。
    

    28、什么是 Method Swizzle(黑魔法),什么情况下会使用?

    1). 在没有一个类的实现源码的情况下,想改变其中一个方法的实现,除了继承它重写、和借助类别重名方法暴力抢先之外,还有更加灵活的方法 Method Swizzle。
    2). Method Swizzle 指的是改变一个已存在的选择器对应的实现的过程。OC中方法的调用能够在运行时通过改变,通过改变类的调度表中选择器到最终函数间的映射关系。
    3). 在OC中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用OC的动态特性,可以实现在运行时偷换selector对应的方法实现。
    4). 每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的方法实现。
    5). 我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP。
    6). 我们可以利用 class_replaceMethod 来修改类。
    7). 我们可以利用 method_setImplementation 来直接设置某个方法的IMP。
    8). 归根结底,都是偷换了selector的IMP。
    

    29、_objc_msgForward 函数是做什么的,直接调用它将会发生什么?

    答:_objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。
    

    30、什么是 TCP / UDP ?

    TCP:传输控制协议。
    UDP:用户数据协议。
    
    TCP 是面向连接的,建立连接需要经历三次握手,是可靠的传输层协议。
    UDP 是面向无连接的,数据传输是不可靠的,它只管发,不管收不收得到。
    简单的说,TCP注重数据安全,而UDP数据传输快点,但安全性一般。
    

    31、通信底层原理(OSI七层模型)

    OSI采用了分层的结构化技术,共分七层:
        物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
    

    32、请简单的介绍下APNS发送系统消息的机制

    APNS优势:杜绝了类似安卓那种为了接受通知不停在后台唤醒程序保持长连接的行为,由iOS系统和APNS进行长连接替代。
    APNS的原理:
        1). 应用在通知中心注册,由iOS系统向APNS请求返回设备令牌(device Token)
        2). 应用程序接收到设备令牌并发送给自己的后台服务器
        3). 服务器把要推送的内容和设备发送给APNS
        4). APNS根据设备令牌找到设备,再由iOS根据APPID把推送内容展示
    

    33、谈谈 UITableView 的优化

    1). 正确的复用cell。
    2). 设计统一规格的Cell
    3). 提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;
    4). 异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;
    4). 滑动时按需加载,这个在大量图片展示,网络加载的时候很管用!
    5). 减少子视图的层级关系
    6). 尽量使所有的视图不透明化以及做切圆操作。
    7). 不要动态的add 或者 remove 子控件。最好在初始化时就添加完,然后通过hidden来控制是否显示。
    8). 使用调试工具分析问题。
    

    34、如何实行cell的动态的行高

    如果希望每条数据显示自身的行高,必须设置两个属性,1.预估行高,2.自定义行高。
    设置预估行高 tableView.estimatedRowHeight = 200。
    设置定义行高 tableView.estimatedRowHeight = UITableViewAutomaticDimension。 
    如果要让自定义行高有效,必须让容器视图有一个自下而上的约束。
    

    35 、如何让计时器调用一个类方法

    计时器只能调用实例方法,但是可以在这个实例方法里面调用静态方法。
    使用计时器需要注意,计时器一定要加入RunLoop中,并且选好model才能运行。scheduledTimerWithTimeInterval方法创建一个计时器并加入到RunLoop中所以可以直接使用。
    如果计时器的repeats选择YES说明这个计时器会重复执行,一定要在合适的时机调用计时器的invalid。不能在dealloc中调用,因为一旦设置为repeats 为yes,计时器会强持有self,导致dealloc永远不会被调用,这个类就永远无法被释放。比如可以在viewDidDisappear中调用,这样当类需要被回收的时候就可以正常进入dealloc中了。
    
     [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
    
    -(void)timerMethod
    {
    //调用类方法
        [[self class] staticMethod];
    }
    
    -(void)invalid
    {
        [timer invalid];
        timer = nil;
    }
    

    36 、如何重写类方法

    1、在子类中实现一个同基类名字一样的静态方法
    2、在调用的时候不要使用类名调用,而是使用[self class]的方式调用。原理,用类名调用是早绑定,在编译期绑定,用[self class]是晚绑定,在运行时决定调用哪个方法。
    

    37 、NSTimer创建后,会在哪个线程运行。

    用scheduledTimerWithTimeInterval创建的,在哪个线程创建就会被加入哪个线程的RunLoop中就运行在哪个线程
    自己创建的Timer,加入到哪个线程的RunLoop中就运行在哪个线程。
    


    第三方框架
    1、AFNetworking 底层原理分析

    AFNetworking主要是对NSURLSession和NSURLConnection(iOS9.0废弃)的封装,其中主要有以下类:
    1). AFHTTPRequestOperationManager:内部封装的是 NSURLConnection, 负责发送网络请求, 使用最多的一个类。(3.0废弃)
    2). AFHTTPSessionManager:内部封装是 NSURLSession, 负责发送网络请求,使用最多的一个类。
    3). AFNetworkReachabilityManager:实时监测网络状态的工具类。当前的网络环境发生改变之后,这个工具类就可以检测到。
    4). AFSecurityPolicy:网络安全的工具类, 主要是针对 HTTPS 服务。
    
    5). AFURLRequestSerialization:序列化工具类,基类。上传的数据转换成JSON格式
        (AFJSONRequestSerializer).使用不多。
    6). AFURLResponseSerialization:反序列化工具类;基类.使用比较多:
    7). AFJSONResponseSerializer; JSON解析器,默认的解析器.
    8). AFHTTPResponseSerializer; 万能解析器; JSON和XML之外的数据类型,直接返回二进
    制数据.对服务器返回的数据不做任何处理.
    9). AFXMLParserResponseSerializer; XML解析器;
    

    2、描述下SDWebImage里面给UIImageView加载图片的逻辑

    SDWebImage 中为 UIImageView 提供了一个分类UIImageView+WebCache.h, 这个分类中有一个最常用的接口sd_setImageWithURL:placeholderImage:,会在真实图片出现前会先显示占位图片,当真实图片被加载出来后再替换占位图片。
        
    加载图片的过程大致如下:
        1.首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以url 作为数据的索引先在内存中寻找是否有对应的缓存
        2.如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来
        3.如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片
        4.下载后的图片会加入缓存中,并写入磁盘中
        5.整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来
    

    SDWebImage原理:

    调用类别的方法:
        1. 从内存(字典)中找图片(当这个图片在本次使用程序的过程中已经被加载过),找到直接使用。
        2. 从沙盒中找(当这个图片在之前使用程序的过程中被加载过),找到使用,缓存到内存中。
        3. 从网络上获取,使用,缓存到内存,缓存到沙盒。
    

    3、友盟统计接口统计的所有功能

    APP启动速度,APP停留页面时间等
    

    相关文章

      网友评论

        本文标题:2019年iOS面试题

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