iOS 面试总结

被面试问的问题, 和网上找到的我觉得不错的问题

会保持更新 -- 因都写在一起了, 所以可能会穿插添加. 请谅解

iOS-多线程

常见的几种线程锁 / 保证线程安全

  • @synchronized 适用线程不多,任务量不大的多线程加锁
  • NSLock 常用锁 性能一般
  • dispatch_semaphore_t GCD信号锁,性能不错
  • OSSpinLock 性能非常高 但是线程出现了问题

Run字辈

简单讲一下Runtime

Runtime 它是OC的运行时, 其中最重要的就是消息机制 C语言是编译时就已经确定了如何调用.
OC是在运行的时候才会确定

当我们调用了一个方法的时候 运行过程是?

  1. Runtime会将我们的方法调用 转为消息发送objc_msgSend, 并且把方法的调用者和方法选择器 作为参数一起传递过去
  2. 方法的调用者会通过isa指针查找到该方法所属的类, 然后在Cache 或者 Method_Lists中查找该方法, 如果有则调用执行
  3. 如果没有找到该方法, 则通过SuperClass 继续像父类查找(如何到NSObject还未查找到则进入消息转发)

动态方法解析

一般情况下程序Carsh 类似

unrecognized selector sent to instance 内存地址

在Crash之前Runtime会给我们补救的机会(消息转发)

  1. 检测这个 selector 是不是要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会 retain,release 这些函数了
  2. 检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉
  3. 如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行. 如果 cache 找不到就找一下方法分发表
  4. 如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止

如果还找不到就要开始进入消息转发了,消息转发的大致过程如图:

iOS 面试总结_第1张图片
  1. 进入resolveInstanceMethod:方法,询问是否动态添加方法,如果返回Yes, 通过class_addMethod动态添加函数方法, 消息的处理完毕, 此流程完毕. 返回No 进入 2
  2. 进入ForwardTargetForSelector: 方法, 用于指定那个对象来响应这个Selector, 如果返回某个对象, 该对象调用此方法, 此流程完毕. 返回nil 进入 3
  3. 通过methodSignatureForSelector:来指定方法签名,返回nil, 表示不处理, 返回签名(通过NSMethodSignature signatureWithObjcType:"v@:" 来进行重新签名)则进入 4
  4. 调用forwardInvlication:方法, 通过invocation 修改实现方法,修改响应对象等。

Runtime 能做什么

  • 字典和模型相互转换
  • 方法交换
  • 给分类添加属性
  • 自动归解档

Category 原理

参考自

通过attachCategoryMethods这个函数来处理的, 将类中旧方法和Category新添加的方法整合成一个新的方法列表, 并赋值给method_lists 或者 method_list. 主类中的方法和 Category 中的方法在 runtime 看来并没有区别,它们是被同等对待的,都保存在主类的方法列表中。我们需要调用时引入头文件, 是要"照顾"编辑器大大的感受而已...

Runtime 常见的定义

  • IMP implementation 实现的意思 指向一个方法实现的指针

Runloop

一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出

超详细Runloop

NSTime 为什么在滑动UIScrollView时候会暂停

因为Runloop 原因, 为了保证UIScrollView滑动时候的流畅度, 会将Runloop的Mode切换为TrackingRunLoopMode, 这时候NSTimer就不会得到回调. 只需要将NSTimer的model 改为 commonModeItems即可了

NSAutoreleasePool

NSAutoreleasePool 类被用来支持自动引用计数内存管理系统.

autoreleased 对象什么时候释放

对象创建后会被添加到最近的autoReleasePool中, 只有这个autoReleasePool 自身drain的时候, autoReleasePool中的对象才会被release

根据苹果官方文档中对 NSAutoreleasePool 的描述,我们可知,在主线程的 NSRunLoop 对象(在系统级别的其他线程中应该也是如此,比如通过 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 获取到的线程)的每个 event loop 开始前,系统会自动创建一个 autoreleasepool ,并在 event loop 结束时 drain 。我们上面提到的场景 1 中创建的 autoreleased 对象就是被系统添加到了这个自动创建的 autoreleasepool 中,并在这个 autoreleasepool 被 drain 时得到释放

AutoreleasePoolPage

那这里的 AutoreleasePoolPage 是什么东西呢?其实,autoreleasepool 是没有单独的内存结构的,它是通过以 AutoreleasePoolPage 为结点的双向链表来实现的。
autoreleasepool 的运行过程可以简单地理解为 objc_autoreleasePoolPush()[对象 autorelease]objc_autoreleasePoolPop(void *) 三个过程

和线程之间的关系

每一个线程都会维护自己的 autoreleasepool 堆栈。换句话说 autoreleasepool 是与线程紧密相关的,每一个 autoreleasepool 只对应一个线程。

iOS手势问题

响应者链

处理事件响应者先后顺序链. 有些时候,Touch后系统通过hit test 机制找到了触碰到的Initial View,但是Initial view并没有或者无法正常处理此次Touch。这个时候,系统便会通过响应者链寻找下一个响应者,以对此次Touc 进行响应。

Hit-Test机制

当你点击屏幕一个点时, 这个操作由硬件系统传递到操作系统, 然后封装为一个事件(Event) , 由UIApplication->UIWindows->RootView到最接近你手指的View来响应事件

子类超出父类边缘时候, 亦可以响应事件(正常是不可以的, 你需要重写override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? 这个方法)

iOS关键字

static声明局部变量

static 声明的局部变量, 会改变它的存储方式(生命周期), 使变量成为静态变量, 在编译时候就会分配内存, 知道程序退出才会释放内存. 这样就带有记忆的能力, 可以记录上一次的数据, 但是因为变量还是局部变量, 所有只能在其代码块内使用.

static 声明外部变量

用static声明外部变量,其本身就是静态变量,这只会改变其连接方式,使其只在本文件内部有效,而其他文件不可连接或引用该变量。

作用域函数

使用static用于函数定义时,对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的。这样的函数又叫作静态函数。使用静态函数的好处是,不用担心与其他文件的同名函数产生干扰,另外也是对函数本身的一种保护机制。

网络部分

简单讲一下Socket

Socket 套接字, 他本身不是协议, 是对TCP/IP协议的封装, 通过Socket才可以使用TCP/IP协议.
建立socket连接. 服务器监听->客户端进行请求->连接确认
在iOS中, 一般使用CocoaAsyncSocket 这个库来使用Socket

简单讲一下HTTP请求,以及GET和POST的区别

HTTP 请求

这个Http请求讲的很明白

HTTP是计算机通过网络进行通信的原则. HTTP是一中无状态协议(即Web服务器和Web服务器间不用建立持久的链接,当客户端发送请求,服务器端返回响应,链接就关闭了).

一次完整的通信过程 需要经历7个步骤

  • 建立TCP链接
  • Web浏览器向Web服务器发送请求命令
  • Web浏览器发送请求头信息
  • Web服务器应答
  • Web服务器发送应答头信息
  • Web服务器向浏览器发送数据
  • Web服务器关闭TCP链接

GET和POST区别

'标准答案'

  • GET在浏览器回退时是无害的,而POST会再次提交请求。
  • GET产生的URL地址可以被Bookmark,而POST不可以。
  • GET请求会被浏览器主动cache,而POST不会,除非手动设置。
  • GET请求只能进行url编码,而POST支持多种编码方式。
  • GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
  • GET请求在URL中传送的参数是有长度限制的,而POST么有。
  • 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
  • GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
  • GET参数通过URL传递,POST放在Request body中。

装B答案

无论是GET还是POST 本质上都是TCP链接,并无差别.

但是他们有重要的区别在于GET方式请求产生一个TCP数据包;POST是两个.

对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据

而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。 并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。

HTTPS的加密方式

参考 1

HTTPS的加密方式

对称加密

对称加密是指加密和解密使用相同密钥的加密算法。它要求发送方和接收方在安全通信之前,商定一个密钥。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信至关重要。

  • 优点:算法公开、计算量小、加密速度快、加密效率高。

  • 缺点:

    • 交易双方都使用同样钥匙,安全性得不到保证;

    • 每对用户每次使用对称加密算法时,都需要使用其他人不知道的惟一钥匙,这会使得发收信双方所拥有的钥匙数量呈几何级数增长,密钥管理成为用户的负担。

    • 能提供机密性,但是不能提供验证和不可否认性。

非对称加密

这种加密或许理解起来比较困难,这种加密指的是可以生成公钥和私钥。凡是公钥加密的数据,公钥自身不能解密,而需要私钥才能解密;凡是私钥加密的数据,私钥不能解密,需要公钥才能解密(比如说RSA)

非对称加密相比对称加密更加安全,但也存在两个明显缺点:

  • CPU计算资源消耗非常大。一次完全TLS握手,密钥交换时的非对称解密计算量占整个握手过程的90%以上。而对称加密的计算量只相当于非对称加密的0.1%,如果应用层数据也使用非对称加解密,性能开销太大,无法承受。

  • 非对称加密算法对加密内容的长度有限制,不能超过公钥长度。比如现在常用的公钥长度是2048位,意味着待加密内容不能超过256个字节。

所以公钥加密目前只能用来作密钥交换或者内容签名,不适合用来做应用层传输内容的加解密

流程

首先服务器端用非对称加密(RSA)产生公钥和私钥。然后把公钥发给客 户端,路径或许有人会截取,但是没有用,因为用公钥加密的文件只有私钥可以解密,而私钥永远都不会离开服务器的。当公钥到达客户端之后,客户端会用对称加密产生一个秘钥并且用公钥来加密发送给服务器端,这个秘钥就是以后用来通信的钥匙。这样服务器端收到公钥加密的秘钥时就可以用私钥来解公钥从而获得秘钥。这样的话客户端和服务器端都获得了秘钥,信息交流相对是安全的



​ 听起来确实是挺安全的,但实际上,还有一种更恶劣的攻击是这种方法无 法防范的,这就是传说中的“中间人攻击”。在身份认证的过程中,出现了一个“中间人”拦截我们的信息,他有意想要知道你们的消息。我们将这个中间人称为M。当服务器第一次给客户端发送公钥的时候,途径M。M知道你要进行密钥交换了,它把公钥扣了下来,假装自己是客户端,伪造了一个伪秘钥(对称加密产生的),然后用服务器发来的公钥加密了伪秘钥发还给服务器,这样服务器以为和客户端完成了密钥交换,实际上服务器是和M完成了密钥交换(获得了伪秘钥)。同时M假扮成服务器自行用非对称加密产生伪公钥和伪私钥,与客户端进行秘钥交换,拿到客户端发送过来的秘钥。现在客户端拿着秘钥,M拿着秘钥和为伪秘钥,服务器拿着伪秘钥,整个交流的过程就是:



这种问题就需要CA证书去解决. (应该可以满足大部分面试问题不再过多深入)

持续化存储方式

  • NSUserDefaults
  • plist
  • NSKeyedArchiver
  • SQL
  • CoreData
  • Realm(新兴数据库, 暂时没接触)

一些三方实现过程

SDWebImage

引用

  1. 入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。
  2. 进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:.
  3. 先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
  4. SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示图片。
  5. 如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。
  6. 根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。
  7. 如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。
  8. 如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。
  9. 共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。
  10. 图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。
  11. connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。
  12. connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。
  13. 图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。
  14. 在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成, imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。
  15. imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。
  16. 通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。
  17. 将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。
  18. SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。
  19. SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。
  20. SDWebImagePrefetcher 可以预先下载图片,方便后续使用。

优化问题

iOS离屏渲染

直接将图层合成到帧的缓冲区中(在屏幕上)比先创建屏幕外缓冲区,然后渲染到纹理中,最后将结果渲染到帧的缓冲区中要廉价很多。因为这其中涉及两次昂贵的环境转换(转换环境到屏幕外缓冲区,然后转换环境到帧缓冲区)。触发离屏渲染后这种转换发生在每一帧,在界面的滚动过程中如果有大量的离屏渲染发生时会严重影响帧率

iOS中可能会触发离屏渲染的操作

  • mask
  • shadow
  • group opacity
  • edge antialiasing

在iOS 8之前 Core Graphics 也会触发离屏渲染, 但是不是GPU离屏渲染是, Core Graphics 绘制 API 是在 CPU 上执行,触发的是 CPU 版本的离屏渲染。

layer.cornerRaius > 0 是否会触发离屏渲染

答案是不会. 只有它和layer.masksToBounds = true 一起才会触发

shouldRasterize = YES 是否会触发呢

但是他较为特殊, 他会将离屏渲染的的同时, 会将光栅化的内容缓存起来, 如果layer 或者 SubLayer 未发生变化, 下一帧会拿来直接使用.

当你使用光栅化时,你可以开启“Color Hits Green and Misses Red”来检查该场景下光栅化操作是否是一个好的选择。绿色表示缓存被复用,红色表示缓存在被重复创建。

对于经常变动的内容,这个时候不要开启,否则会造成性能的浪费(比如TableView)

Crash问题

参考

什么情况下会产生崩溃日志

  • 应用中有Bug 崩溃
  • Watchdog 超时机制. 如果用户强制退出
  • 低内存日志

Crash收集的几种方式

  • 简单可以自己写一个方法 之后在写入词磁盘中, 下次进入时候上传服务器, 之后删除
void handleException(NSException *exception) {
     NSArray *callStack = [exception callStackSymbols];
    NSString *reason = [exception reason];
    NSString *name = [exception name];
}
  • 使用Xcode自带的工具.Windows->Organizer->选择你需要的App->Crashes会自己动下载崩溃 直接打开就可以了
  • 使用第三方框架例如Bugly等

Crash分析

一般采用 .Crash 文件 + .dSYM文件 + symbolicatecrash(Xcode自带工具) 输出.log
详细请看参考 )

其他知识

在不知道二进制文件格式情况下如何区分文件

可以通过二进制头识别文件类型(说几个简单的 不好意思 一个没记住)或者UE等工具打开

参考网址

lldb常用操作

查看我以前的文章

你可能感兴趣的:(iOS 面试总结)