高级iOS面试题

1、NSSet与NSArray的区别

  • NSArray在内存中是连续的,NSSet不连续

  • NSSet效率更高,内部使用hash查找;NSArray查找需要遍历

  • NSSet通过anyObject访问元素,NSArray通过下标访问

2、NSHashTable与NSMapTable

  • NSHashTable是NSSet的通用版本,对元素弱引用,可变类型;可以在访问成员时copy

  • NSMapTable是NSDictionary的通用版本,对元素弱引用,可变类型;可以在访问成员时copy

(注:NSHashTable与NSSet的区别:NSHashTable可以通过option设置元素弱引用/copyin,只有可变类型。但是添加对象的时候NSHashTable耗费时间是NSSet的两倍。NSMapTable与NSDictionary的区别,同上)

3、属性关键字assign、retain、weak、copy

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

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

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

  • copy:会创建一个已经存在对象的copy。

4、weak属性如何自动置nil的?

runtime会对weak属性进行内存布局,构建hash表:以weak属性对象内存地址为key,weak属性值(weak自身地址)为value。当对象引用计数为0 dealloc时,会将weak属性值自动置nil。

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

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

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

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

6、KVO底层实现原理?手动触发KVO?Swift如何实现KVO?

  • KVO原理:当观察一个对象时,runtime会动态创建继承自该对象的类,并重写被观察对象的setter方法,重写的setter方法会负责在调用原setter方法前后通知所有观察对象值的更改,最后会把该对象的isa指针指向创建的子类,对象就变成子类的实例。

  • 如何手动触发KVO:在setter方法里,手动实现NSObject两个方法:willChangeValueForKey、didChangeValueForKey

  • Swift的KVO:继承自NSObject的类,或者直接willSet/didSet实现。

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

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

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

  • category是用来扩展类的功能的,而extension仅仅扩展定义,没有源代码的话是不能使用扩展的。

  • category方法会在runtime初始化的时候copy到原来前面,调用分类方法的时候直接返回,不再调用原类。category 其实并不是完全替换掉原来类的同名方法,只是 category 在方法列表的前面而已,所以我们只要顺着方法列表找到最后一个对应名字的方法,就可以调用原来类的方法。如何保持原类方法也调用(https://www.jianshu.com/p/40e28c9f9da5)。

  • 多个category的调用顺序按照:Build Phases -> Complie Source 中的编译顺序。

8、load方法和initialize方法的异同。——主要说一下执行时间,各自用途,没实现子类的方法会不会调用父类的?

load Initialize
调用时机 app启动后,runtime初始化的时候 第一个方法调用前调用
调用顺序 父类 -> 本类 -> 分类 父类 -> 本类(如果有分类直接调用分类,本类不会调用)
没实现子类的方法会不会调用父类
是否沿用父类实现

9、对runtime的理解。——主要是方法调用时如何查找缓存,如何查找方法,找不到方法时怎么转发,对象的内存布局

OC中向对象发送消息时,runtime会根据对象的isa指针找到对象所属的类,然后在该类的方法列表和父类的方法列表中寻找方法执行。如果在最顶层父类中没有找到方法执行,就会进行消息转发:Method resoution(实现方法)、fast forwarding(转发给其他对象)、normal forwarding(完整消息转发。可以转发给多个对象)。

10、runtime中,SEL和IMP的区别?

每个类对象都有一个方法列表,方法列表存储方法名、方法实现、参数类型,SEL是方法名(编号),IMP指向方法实现的首地址。

11、autoreleasepool的原理和使用场景

  • 若干个autoreleasepoolpage组成的双向链表的栈结构,objc_autoreleasepoolpush、objc_autoreleasepoolpop、objc_autorelease

  • 使用场景:多次创建临时变量导致内存上涨时,需要延迟释放

  • autoreleasepoolpage的内存结构:4k存储大小

11.jpg

12、Autorelase对象什么时候释放?

在没有手加Autorelease Pool的情况下,Autorelease对象是在当前runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop。

13、runloop与线程的关系?runloop的mode?runloop的作用?内部机制?

  • 每一个线程都有一个runloop,主线程的runloop默认启动。

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

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

14、iOS中使用的锁、死锁的发生与避免

  • @synchronized、信号量、NSLock等

  • 死锁:多个线程同时访问同一资源,造成循环等待。GCD使用异步线程、并行队列。

15、NSOperation和GCD的区别

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

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

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

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

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

  • GCD高效,NSOperation开销相对高。

16、OC与JS交互

  • 拦截url

  • JavaScriptCore(只适用于UIWebView)

  • WKScriptMessageHandler(只适用于WKWebView)

  • WebViewJavaScriptBridge(第三方框架)

17、Swift相比OC有什么优势?

  • 安全,Swift从语法上避免了很多未定义的行为,比如空值访问、值类型等,可以提升项目稳定性、降低崩溃率。

  • 性能,Swift的语言设计让方法调度通过静态调度完成而不是OC的动态派发,运行时效率优于OC。

  • 编码效率,富有表现力的语法特性让代码更清晰易于理解,减少的代码量约有30%-50%不等,让开发人员更高效的支撑业务发展,提升了研发效率。

18、Struct、Class的区别

  • class可以继承,struct不可以。

  • class是引用类型,struct是值类型。

  • struct在function里修改property时需要metating关键字修饰。

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

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

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

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

20、OC与Swift混编

  • OC调用Swift:import "工程名-swift.h" @objc。

  • Swift调用OC:桥接文件。

21、map、filter、reduce?map与filter的区别?

  • map:数组中每个元素都经过某个方法转换,最后返回新的数组(xx.map({0})。

  • flatmap:同map类似,区别在flatmap返回的数组不存在nil,并且会把optional解包;而且还可以把嵌套的数组变成一个([[1, 2], [3, 4], [5, 6]] -> [1, 2, 3, 4, 5, 6])。

  • filter:用户筛选元素(xx.filter({$0 > 25},筛选出大于25的元素组成新数组)。

  • reduce:把数组元素组合计算为一个值,并接收初始值()。

21.jpg

22、guard与defer

  • gurad用于提前处理错误数据,else退出程序,提高代码可读性。

  • defer延迟执行,回收资源。多个defer反序执行,后执行内层。

23、try、try?与try!

  • try:手动捕捉异常。

  • try?:系统帮我们处理,出现异常返回nil;没有异常返回对应的对象。

  • try!:直接告诉系统,该方法没有异常。如果出现异常程序会crash。

24、autoclosure:把一个表达式自动封装成闭包

25、throws与rethrows:throws另一个throws时,将前者改为rethrows

26、App启动优化策略?main函数执行前后怎么优化?

  • 启动时间=pre-main耗时+main耗时。

  • pre-main阶段优化:

    • 删除无用代码

    • 抽象重复代码

    • +load方法做的事情延迟到initialize中,或者+load的事情不宜花费太多时间。

    • 减少不必要的framework,或者优化已有framework。

  • main阶段优化:

    • didFinishLauchingWithOptions里代码延后执行。

    • 首次启动渲染的页面优化。

27、crash防护?

  • unrecognized selector crash

  • KVO crash

  • NSNotification crash

  • NSTimer crash

  • Container crash(数组越界、插nil等)

  • NSString carsh(字符串操作的crash)

  • Bad Access crash(野指针)

  • UI not on Main Thread crash(非主线程刷UI(机制待改善))。

28、内存泄露问题?

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

29、UI卡顿优化?

  • 用轻量对象代替重量对象,比如使用CALayer代替UIView。
  • 减少不必要的属性修改,比如UIView的frame。
  • 将对象的销毁放到后台队列中销毁。
  • 在后台提前计算好布局,在需要时一次性调整好对应的属性。
  • 对于复杂视图,不是用自动布局。
  • 当一个页面上有大量文本时,文本在计算时使用NSAttributedString的方法来计算文本宽高和绘制文本。
  • 自定义文本控件,使用CoreText对文本进行异步绘制,虽然麻烦,但可以避免多次计算,占用的内存也比较小。
  • 图片的解码,常见网络图片库都自带功能。
  • 减少视图的圆角、边框、阴影和遮罩,避免离屏渲染带来的CPU资源消耗,可以使用图片代码。
  • 多个视图重叠在一起时,GPU会把他们混合到一起,这个过程会消耗很多GPU资源,应尽量减少视图的层次。

30、架构&设计模式?

  • MVC设计模式介绍
    • Model处理数据,封装了应用程序的数据,并定义操控和处理该数据的逻辑和运算。
    • View管理的是可视层,管理信息的可视化显示。
    • Controller是沟通Model和View之间的桥梁,当用户操作了View后,View通过Controller去更新对应的数据,而当数据改变后,Model通过Controller去调整View。
    • 缺点是ViewController做了太多的事情,会很臃肿,解决的办法是将一部分功能提取出来,放到一个单独的类中处理,例如UITableViewDataSource。
  • MVVM介绍、MVC与MVVM的区别?
    • MVVM:Model-ViewController/View-ViewModel,虽然ViewController与View在技术上是不同的组件,但几乎每次都是一起使用,所以进行了合并。Model依然是数据层。我们将ViewController中所有表示逻辑放到ViewModel中。
    • MVVM可以更好的进行单元测试,可以针对ViewModel编写单元测试。
  • ReactiveCocoa的热信号与冷信号。
  • 缓存架构设计LRU方案。
    • LRU为最近最少使用,通过链表实现,当插入一个数据时,将数据插入到列表的头部,当访问一个数据时,则将数据移动到链表的头部,当链表满了时,删除链表尾部数据。
  • SDWebImage源码,如何实现解码。
  • AFNetworking源码分析。
    • 有四个文件夹,分别放着处理网络请求的类、检测网络变化的类、处理网络安全策略的类和处理网络请求头和响应头的类。
    • 最核心的类是AFURLSessionManager,其中子类AFHTTPSessionManager是专门用来处理网络请求的。
    • AFSessionManager中的核心实现类是苹果提供的NSURLSession和NSURLSessionDataTask。
  • 组件化的实施,中间件的设计。
  • 哈希表的实现原理?如何解决冲突?
    • 哈希表的本质是一个数组,数组中每一个元素称为一个箱子(bin),箱子中存放的是键值对。数组长度即箱子数。
    • 哈希表还有一个重要的属性: 负载因子(load factor),它用来衡量哈希表的 空/满 程度,一定程度上也可以体现查询的效率。
    • 负载因子越大,意味着哈希表越满,越容易导致冲突,性能也就越低。因此,一般来说,当负载因子大于某个常数(可能是 1,或者 0.75 等)时,哈希表将自动扩容。
    • 哈希表在自动扩容时,一般会创建两倍于原来个数的箱子,因此即使 key 的哈希值不变,对箱子个数取余的结果也会发生改变,因此所有键值对的存放位置都有可能发生改变,这个过程也称为重哈希(rehash)。
    • 哈希表的扩容并不总是能够有效解决负载因子过大的问题。

31、数据结构&算法

  • 快速排序、并归排序。
    • 快速排序:令第一个元素为基准元素,并设置两个变量i和j, i在序列的最左端,j在序列的最右端。j 从右往左试探,直到 j 找到小于基准元素值的元素就停止先; i 从左往右试探,直到 i 找到大于基准元素值的元素就停止。当 i 和 j 都停止了后,交换 i 和 j 所指向的元素。交换后, j 、 i 按以上步骤继续试探。直到 i 和 j 相遇之后,则i的值与基准元素交换,第一轮排序结束。第一轮排序结束后,分别对基准元素左右两边的序列重复以上步骤。
    • 并归排序:
  • 二维数组查找(每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数)。
  • 二叉树的遍历:判断二叉树的层数。
  • 单链表判断环。

32、计算机基础

  • http与https?socket编程?tcp、udp?get与post?
  • tcp三次握手与四次握手。
  • 进程与线程的区别。
    • 速度。线程产生的速度快,通讯快,切换快,因为他们处于同一地址空间。
    • 线程的资源利用率好。
    • 线程使用公共变量或者内存的时候需要同步机制,但进程不用。

转自:https://zhuanlan.zhihu.com/p/77789398
自己敲一遍来加深记忆。

你可能感兴趣的:(高级iOS面试题)