iOS 面试基础知识 (三)
我们接着上篇文章iOS面试基础知识 (二)继续给大家分享一下 iOS 面试题。
多线程
多线程创建方式
iOS 创建多线程方式主要有 NSThread、NSOperation、GCD,这三种方式创建多线程的优缺点如下:
NSThread
NSThread 封装了一个线程,通过它可以方便的创建一个线程。NSThread 线程之间的并发控制,是需要我们自己来控制的。它的缺点是需要我们自己维护线程的生命周期、线程之间同步等,优点是轻量,灵活。
NSOperation
NSOperation 是一个抽象类,它封装了线程的实现细节,不需要自己管理线程的生命周期和线程的同步等,需要和 NSOperationQueue 一起使用。使用 NSOperation ,你可以方便地控制线程,比如取消线程、暂停线程、设置线程的优先级、设置线程的依赖。NSOperation 常用于下载库的实现,比如 SDWebImage 的实现就用到了 NSOperation。
GCD
GCD(Grand Central Dispatch) 是 Apple 开发的一个多核编程的解决方法。GCD 是一个可以替代 NSThread 的很高效和强大的技术。在平常开发过程中,我们用的最多的就是 GCD。哦,对了,NSOperation 是基于 GCD 实现的。
多线程同步
多线程情况下访问共享资源需要进行线程同步,线程同步一般都用锁实现。从操作系统层面,锁的实现有临界区、事件、互斥量、信号量等。这里讲一下 iOS 中多线程同步的方式。
atomic
属性加上 atomic 关键字,编译器会自动给该属性生成代码用以多线程访问同步,它并不能保证使用属性的过程是线程安全的。一般我们在定义属性的时候用 nonatomic,避免性能损失。
@synchronized
@synchronized 指令是一个对象锁,用起来非常简单。使用 obj 为该锁的唯一标识,只有当标识相同时,才为满足互斥,如果线程 1 和线程 2 中的 @synchronized 后面的 obj 不相同,则不会互斥。@synchronized 其实是对 pthread_mutex 递归锁的封装。
@synchronized 优点是我们不需要在代码中显式的创建锁对象,使用简单; 缺点是 @synchronized 会隐式的添加一个异常处理程序,该异常处理程序会在异常抛出的时候自动的释放互斥锁,从而带来额外开销。
NSLock
最简单的锁,调用 lock 获取锁,unlock 释放锁。如果其它线程已经调用 lock 获取了锁,当前线程调用 lock 方法会阻塞当前线程,直到其它线程调用 unlock 释放锁为止。NSLock 使用简单,在项目中用的最多。
NSRecursiveLock
递归锁主要用来解决同一个线程频繁获取同一个锁而不造成死锁的问题。注意 lock 和 unlock 调用必须配对。
NSConditionLock
条件锁,可以设置自定义条件来获取锁。比如生产者消费者模型可以用条件锁来实现。
NSCondition
条件,操作系统中信号量的实现,方法- (void)wait 和- (BOOL)waitUntilDate:(NSDate *)limit 用来等待锁直至锁有信号;方法- (void)signal 和- (void)broadcast 使 condition 有信号,通知等待 condition 的线程,变成非阻塞状态。
dispatchsemaphoret
信号量的实现,可以实现控制 GCD 队列任务的最大并发量,类似于 NSOperationQueue 的 maxConcurrentOperationCount 属性。
pthread_mutex
mutex 叫做”互斥锁”,等待锁的线程会处于休眠状态。使用 pthreadmutexinit 创建锁,使用 pthreadmutexlock 和 pthreadmutexunlock 加锁和解锁。注意:mutex 可以通过 PTHREADMUTEXRECURSIVE 创建递归锁,防止重复获取锁导致死锁
OSSpinLock
OSSpinLock 是自旋锁,等待锁的线程会处于忙等状态。一直占用着 CPU。自旋锁就好比写了个 while,whil(被加锁了) ; 不断的忙等,重复这样。OSSpinLock 是不安全的锁(会造成优先级反转),什么是优先级反转,举个例子:
有线程 1 和线程 2,线程 1 的优先级比较高,那么 cpu 分配给线程 1 的时间就比较多,自旋锁可能发生优先级反转问题。如果优先级比较低的线程 2 先加锁了,紧接着线程 1 进来了,发现已经被加锁了,那么线程 1 忙等,while(未解锁); 不断的等待,由于线程 1 的优先级比较高,CPU 就一直分配之间给线程 1,就没有时间分配给线程 2,就有可能导致线程 2 的代码就没有办法往下走,就会造成线程 2 没有办法解锁,所以这个锁就不安全了。
建议不要使用 OSSpinLock,用 osunfairlock 来代替。
osunfairlock
osunfairlock 用于取代不安全的 OSSpinLock,从 iOS10 开始才支持 从底层调用看,等待 osunfairlock 锁的线程会处于休眠状态,并非忙等
性能
性能从高到低排序
1、osunfairlock
2、OSSpinLock
3、dispatch_semaphore
4、pthread_mutex
5、NSLock
6、NSCondition
7、pthread_mutex(recursive)
8、NSRecursiveLock
9、NSConditionLock
10、@synchronized
JSON Model 互转
项目中 JSON Model 转换方式
平常开发过程中,经常需要进行 JSON 与 Model 互转,尤其是接口数据转换。我们可以手动解析,也可以用 MJExtension、YYModel 这些第三方库,用第三方库最大的好处他可以自动给你转换并且处理类型不匹配等异常情况,从而避免崩溃。
MJExtension 实现原理
MJExtension 是如何做属性映射的?
MJExtension 在遍历 dic 属性时,比如遍历到 name 属性时,先去缓存里查找这个类是否有这个属性,有就赋值 akon。没有就遍历 UserModel 的属性列表,把这个类的属性列表加入到缓存中,查看这个类有没有定义 name 属性,如果有,就把 akon 赋给这个属性,否则不赋值。
MJExtension 是如何给属性赋值的?
利用 KVC 机制,在查找到 UserModel 有 name 的属性,使用[self setValue:@"akon" forKey:@"name"]进行赋值。
如何获取类的属性列表?
通过 class_copyPropertyList 方法
如何遍历成员变量列表?
通过 class_copyIvarList 方法
数据存储方式
iOS 常见数据存储方式及使用场景
iOS 中可以采用 NSUserDefaults、Archive、plist、数据库等方式等来存储数据,以上存储方式使用的业务场景如下:
NSUserDefaults 一般用来存储一些简单的 App 配置。比如存储用户姓名、uid 这类轻量的数据。
Archive 可以用来存储 model,如果一个 model 要用 Archive 存储,需要实现 NSCoding 协议。
plist 存储方式。像 NSString、NSDictionary 等类都可以直接存调用 writeToFile:atomically:方法存储到 plist 文件中。
-数据库存储方式。大量的数据存储,比如消息列表、网络数据缓存,需要采用数据库存储。可以用 FMDB、CoreData、WCDB、YYCache 来进行数据库存储。建议使用WCDB来进行数据库存储,因为 WCDB 是一个支持 orm,支持加密,多线程安全的高性能数据库。
数据库操作
笔者在面试中,一般会问下面试者数据库的操作,以此开考察一下面试者对于数据库操作的熟练程度。
考察常用 crud 语句书写。
创建表、给表增加字段、插入、删除、更新、查询 SQL 怎么写。尤其是查询操作,可以考察 order by, group by ,distinct, where 匹配以及联表查询等技巧。
SQL 语句优化技巧。如索引、事务等常用优化技巧。
怎么分库、分表?
FMDB 或者 WCDB(orm 型)实现原理。
怎么实现数据库版本迁移?
资料推荐
如果你正在跳槽或者正准备跳槽不妨动动小手,添加一下咱们的交流群1012951431来获取一份详细的大厂面试资料为你的跳槽多添一份保障。
评论