iOS 面试题总结

432 阅读14分钟

被问几率高

1.属性关键字assign、retain、weak、copy

  • assign:用于基本数据类型和结构体。如果修饰对象的话,当销毁时,属性值不会自动置nil,可能造成野指针。

  • weak:对象引用计数为0时,属性值也会自动置nil

  • retain:强引用类型,ARC下相当于strong,但block不能用retain修饰,因为等同于assign不安全。

  • strong:强引用类型,修饰block时相当于copy。

  • 让一个属性在类内部可读可写,外部只读:在.h文件中声明为readoly类型,在.m文件中再次声明为readwrite

  • 2.简述iOS中的消息机制

    OC中对象的底层都是基于runtime实现,当调用一个类的方法时,会通过该类的isa指针找到类的内存地址,然后在该类的方法列表和父类的方法列表中寻找方法执行,如果找到该方法则调用并缓存到方法列表中。如果在最顶层父类中没找到方法执行,则可以动态实现该方法,如果没有动态实现,就会进行相应消息转发,指定相关的代理实现该方法。如果没有相关的代理,则会报错。(unrecognized selector crash)

    备注:每个对象都有对应的方法列表:缓存方法列表(方法调用之后会保存到该列表中)和方法列表,每个对象有对应的isa(指针),每个Method(方法)包括Sel(方法标识,即方法名)和IMP(方法实现)

    简单概括为3个步骤:1.方法查询 2.动态解析 3.消息转发

    3.Runloop与线程的关系?Runloop的mode? Runloop的作用?内部机制?

    • 每一个线程都有一个runloop,主线程的runloop默认启动。对于子线程来说, runloop是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop被开启,不然定时器不会回调。当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。runloop在第一次获取时被创建,在线程结束时被销毁。

    • mode:主要用来指定事件在运行时循环的优先级

    • 作用:保持程序的持续运行、随时处理各种事件、节省cpu资源(没事件休息释放资源)、渲染屏幕UI

    4. SDWebImage的原理

  • 缓存:采用了二级 缓存策略。 图片缓存的时候, 在内存有缓存, 在磁盘中也有缓存, 其中内存缓存是用NSCache做的

  • 缓存的步骤: 0、下载图片 1、将图片缓存在内存中 2、判断图片的格式png或jpeg,将图片转成NSData数据 3、获取图片的存储路径, 其中图片的文件名是通过传入Key经过MD5加密后获得的 4、将图片存在进磁盘中。

  • 获取图片: 1、在内存缓存中找 2、如果内存中找不到, 会去默认磁盘目录中寻找, 如果找不到,在去自定义磁盘目录中寻找 3、如果磁盘也找不到就会下载图片 4、获取图片数据之后, 将图片数据从NSData转化UIImage。其中转化根据图片的类型进行转化 5、默认对图片进行解压缩,生成位图图片 6、将位图图片返回

  • 解压缩: 1、判断图片是否是动态图片,如果是,不能解压缩 2、判断图片是否透明,如果是,不能解压缩 3、判断图片的颜色空间是不是RGB如果不是、不能解压缩 4、根据图片的大小创建一个上下文 5、将图片绘制在上下文中 6、从上下文中读取一个不透明的位图图像,该图像就是解压缩后的图像 7、将位图图像返回

5.Block的循环引用、内部修改外部变量、三种block

  • block强引用self,self强引用block

  • 内部修改外部变量:block不允许修改外部变量的值,这里的外部变量指的是栈中指针的内存地址。__block的作用是只要观察到变量被block使用,就将外部变量在栈中的内存地址放到堆中。

  • 三种block:NSGlobalBlack(全局)、NSStackBlock(栈block)、NSMallocBlock(堆block)

6.NSOperation和GCD的区别

  • GCD底层使用C语言编写高效、NSOperation是对GCD的面向对象的封装。对于特殊需求,如取消任务、设置任务优先级、任务状态监听,NSOperation使用起来更加方便。

  • NSOperation可以设置依赖关系,而GCD只能通过dispatch_barrier_async实现

  • NSOperation可以通过KVO观察当前operation执行状态(执行/取消)

  • NSOperation可以设置自身优先级(queuePriority)。GCD只能设置队列优先级(DISPATCH_QUEUE_PRIORITY_DEFAULT),无法在执行的block中设置优先级

  • NSOperation可以自定义operation如NSInvationOperation/NSBlockOperation,而GCD执行任务可以自定义封装但没有那么高的代码复用度

  • GCD高效,NSOperation开销相对高

被问几率中等

7.swift中类和结构体的区别,应用场景

  • swift中,类是引用类型,结构体是值类型。值类型在传递和赋值时将自身进行复制,而引用类型则只会复制指针。两者之间的主要区别就是两个不同的类型。

  • 内存中,引用类型(例如类)是存在堆上,而值类型(例如结构体)是存在栈上。栈比堆的速度快很多,所以苹果官方推荐使用结构体,可以提高APP运行的效率。

  • 如果是轻量级对象(CGPoint,CGSize),可用结构体进行替代,它占用空间小,运行效率快,适用于复制操作,比一个class的实例被多次引用更加安全,无需担心内存泄露(因为栈区对象出栈后就会被释放)或多线程冲突问题(因为使用时会复制一个新的对象,不会出现多线程操作一个对象的情况)

  • 如果类中的功能逻辑相对复杂,则选用class,class可拓展性高(类目),可以被继承,子类可以使用父类的特性和方法

8.weak属性底层原理及如何自动置nil的

1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

9.TableView的优化有哪些

  • 重用机制(缓存池)

  • 少用有透明度的View

  • 尽量避免使用xib

  • 尽量避免过多的层级结构

  • 高度缓存

  • 减少离屏渲染操作(圆角、阴影啥的)

  • 耗时的操作另外开线程处理

10.内存管理机制

  • 对象通过 alloc copy new 生成的对象在MRC年代需要手动管理内存, 利用的技术是returnCount引用计数器,来管理对象的释放时机,alloc创建对象时,引用计数器 + 1, retain持有关系 ,引用计数器 +1,即当引用一个对象时,会对该对象进行一次retain(即a=b,则b的returnCount+1),当对对像进行release(如[b release]),则 引用计数器 - 1。 如果当前对象的returnCount = 0 ,对象就会给dealloc方法发送通知,在里面进行释放,如果当前returnCount大于0得时候,就会一直被持有

    autorealsepool:自动释放池,将对象统一放至autorealsepool中,当autorealsepool作用域结束时会将里面所有对象进行一次release,它的作用是将对象统一进行管理,减少每个对象单独调用release的繁琐操作

    ARC是iOS5之后推出的自动引用计数器,无需程序员手动去操作对象的引用计数器,也是不允许操作的,系统会在合适的时机对该对象进行相关的操作

11.KVO底层实现原理?手动触发KVO?swift如何实现KVO?

    • KVO原理:当观察一个对象时,runtime会动态创建继承自该对象的子类,并重写被观察对象的setter方法,重写的setter方法会负责在调用原setter方法前后通知所有观察对象值得更改,最后会把该对象的isa指针指向这个创建的子类,对象就变成子类的实例。
    • 如何手动触发KVO:在setter方法里,手动实现NSObject两个方法:willChangeValueForKey、didChangeValueForKey
    • swift的kvo:继承自NSObject的类,或者直接willset/didset实现。

    12.KVC底层实现原理

    KVC设置值原理分析

    • 步骤一:首先找这三种setter:方法,分别是:set<Key>->_set<Key>->setIs<Key>,其中<Key>成员变量的名字,且首字母大写,比如本文中的name,顺序就是setName->_setName->setIsName。当找到这三种setter中任意一个时,则进行赋值,如果没有找到则进入步骤二.

    • 步骤二:判断+ (BOOL)accessInstanceVariablesDirectly函数是否返回YES,如果返回YES,则按照 _key->_iskey->key->iskey的顺序搜索成员,找到任意一个则进行赋值,否则进入步骤三;如果返回NO,则直接进入步骤三

    • 步骤三:如果setter方法 或者 实例变量都没有找到,系统会执行该对象的setValue:forUndefinedKey:函数,默认抛出异常,所以我们使用KVC进行解析模型数据时,要实现setValue:forUndefinedKey:函数,否则会崩溃。

    • 设值总结

    KVC取值原理分析
    当我们调用valueForKey:时,内部操作流程如下:

    • 步骤一:调用getter方法,调用顺序是:get<Key> -> <key> -> is<Key> -> _<key>,以name为例,getName -> name -> isName -> _name,如果找到,则直接返回对应的值(可能是对象),如果找不到则进入步骤二

    • 步骤二:判断+ (BOOL)accessInstanceVariablesDirectly函数是否返回YES,如果为YES,则访问成员变量,顺序为:_<key>->_is<Key>-><key>-> is<Key>,以name为例则是_name->_isName->name-> isName,找到则返回对应的值(可能是对象),找不到则进入步骤三;如果返回的是NO,则截止进入步骤三

    • 步骤三:均找不到,则调用valueForUndefinedKey:抛出异常。

    13.内存泄露问题

    主要集中在循环引用问题中,如block、NSTime、perform selector引用计数问题。

    14.多线程读写数据安全

    • 并发变串行
    • 加锁(常见的锁,NSLock,synchronized)

    15.事件的传递和响应机制

    事件传递过程:

    1、点击屏幕,产生触摸事件之后,放入UIApplication的队列中。
    2、从队列中取出最前面的事件,传递给keyWindow,从父控件传递到子控件
    3、寻找最合适的view(怎样找?)

    • 判断控件(keyWindow)是否能够处理(通过hitTest方法判断)
    • 判断触摸点是否在自己身上(通过pointInside方法判断)
    • 遍历子控件,重复上面两个步骤
    • 找到对应view,从后往前遍历view(因为后加入的子空间处理事件的机会大,最上层的响应),知道没有更合适的view
    • 没有更合适的子控件,就自己处理这个事件

    事件响应过程:

    1、如果当前view是控制器的view,那么控制器就是上一个响应者,事件就传递给控制器;如果当前view不是控制器的view,那么父视图就是当前view的上一个响应者,事件就传递给它的父视图
    2、在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
    3、如果window对象也不处理,则其将事件或消息传递给UIApplication对象
    4、如果UIApplication也不能处理该事件或消息,则将其丢弃

被问几率低

16.ViewController生命周期

按照执行顺序排列:

1. initWithCoder:通过nib文件初始化时触发。 

2. awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每个对象。 

 3. loadView:开始加载视图控制器自带的view。 

4. viewDidLoad:视图控制器的view被加载完成。 

 5. viewWillAppear:视图控制器的view将要显示在window上。

 6. updateViewConstraints:视图控制器的view开始更新AutoLayout约束。 

7. viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。 

8. viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。 

9. viewDidAppear:视图控制器的view已经展示到window上。 10. viewWillDisappear:视图控制器的view将要从window上消失。 

11. viewDidDisappear:视图控制器的view已经从window上消失。

17.为什么AFN3.0中需要设置self.operationQueue.maxConcurrentOperationCount = 1;而AF2.0却不需要

  • 功能不一样, 2.x是基于NSURLConnection的,其内部实现要在异步并发,所以不能设置1。 3.0 是基于NSURLSession其内部是需要串行的鉴于一些多线程数据访问的安全性考虑, 设置这个达到串行回调的效果。

18.Swift相比OC的优势

  • 语法简洁,易懂

  • 安全性高,oc中被认为不安全的方法(如perform selector)在swift中都会被剔除,swift中的可选类型,泛型都是针对数据安全而设定的

  • 运行效率高,swift中的数组,字典,结构体都是值类型,占用内存空间小,运行效率快

19.oc与js交互

  • 拦截url

  • JavaScriptCore(只适用于UIWebView)

  • WKScriptMessageHandler(只适用于WKWebView)

  • WebViewJavaScriptBridge(第三方框架)

20.访问控制关键字(public、open、private、filePrivate、internal)

  • public与open:public在module内部中,class和func都可以被访问/重载/继承,外部只能访问;而open都可以

  • private与filePrivate:private修饰class/func,表示只能在当前class源文件/func内部使用,外部不可以被继承和访问;而filePrivate表示只能在当前swift源文件内访问

  • internal:在整个模块或者app内都可以访问,默认访问级别,可写可不写

21.categroy为什么不能添加属性?怎么实现添加?与Extension的区别?category覆盖原类方法?多个category调用顺序

  • Runtime初始化时categroy的内存布局已经确定,没有ivar,所以默认不能添加属性。

  • 使用runtime的关联对象,并重写setter和getter方法。

  • Extenstion编译期创建,可以添加成员变量ivar,一般用作隐藏类的信息。必须要有类的源码才可以添加,如NSString就不能创建Extension。

  • category方法会在runtime初始化的时候copy到原来前面,调用分类方法的时候直接返回,不再调用原类。如何保持原类也调用

22.TCP与UDP的区别

原理:

  • TCP:面向字节流传输,每次发送请求需要与服务端通过三次握手建立连接。
  • UDP:面向报文传输,发送数据不需要建立连接,不提供数据包分组、组装和不能对数据包进行排序,也就是说,当报文发送之后,无法得知其是否安全完整到达。

区别:

  • TCP:传输效率相比UDP慢,因为每次请求需要建立连接,但稳定性高,容易被攻击,只能通过点到点传输,即一对一。
  • UDP:传输效率快,稳定性差,不容易被攻击,容易出现丢包情况,可实现一对一,一对多,多对多传输。

应用场景:

  • TCP:对数据稳定性要求高,确保服务端能收到发送数据。

  • UDP:对数据传输速度要求高,不要求其稳定性。例如:语音聊天,视频聊天。

23.Http与Https的区别

  • Http:数据明文传输,传输速度快,安全性低,默认端口号是80。
  • Https:传输速度相比Http较慢,安全性高,数据通过ca证书签名,SSL(RSA)加密传输,有专门的公钥和私钥进行数据匹配,公钥存放在客户端,私钥存放在服务端,默认端口号是443。