面试题-性能优化及错误处理

690 阅读12分钟

1.编译过程做了哪些事情?

  • Objective,Swift都是编译语言。编译语言在执行的时候,必须先通过编译器生成机器码,机器码可以直接在CPU上执行,所以执行效率较高。Objective,Swift二者的编译都是依赖于Clang + LLVM. OC和Swift因为原理上大同小异,知道一个即可!
  • iOS编译 不管是OC还是Swift,都是采用Clang作为编译器前端,LLVM(Low level vritual machine)作为编译器后端。
  • 编译器前端 :编译器前端的任务是进行:语法分析,语义分析,生成中间代码(intermediate representation )。在这个过程中,会进行类型检查,如果发现错误或者警告会标注出来在哪一行
  • 编译器后端 :编译器后端会进行机器无关的代码优化,生成机器语言,并且进行机器相关的代码优化。LVVM优化器会进行BitCode的生成,链接期优化等等,LLVM机器码生成器会针对不同的架构,比如arm64等生成不同的机器码。

2.C和 OC 如何混编?

xcode可以识别以下几种扩展名文件:

  • .m文件,可以编写 OC语言 和 C 语言代码
  • .cpp: 只能识别C++ 或者C语言(C++兼容C)
  • .mm: 主要用于混编 C++和OC代码,可以同时识别OC,C,C++代码

3.Swift 和OC 如何调用?

  • Swift 调用 OC代码 需要创建一个 Target-BriBridging-Header.h 的桥文件,在乔文件导入需要调用的OC代码头文件即可

  • OC 调用 Swift代码 直接导入 Target-Swift.h文件即可, Swift如果需要被OC调用,需要使用@objc 对方法或者属性进行修饰

4.Foundation 对象与 CoreFoundation 对象 有什么区别?

  • Foundation对象是OC的,在MRC下需要手动管理内存,ARC下不需要手动管理

  • Core Foundation对象是C对象, MRC和ARC都需要手动管理内存

  • 数据类型之间的转换

    • ARC:__bridge_retained, __bridge_transfer(自动内存管理)
    • 非ARC: __bridge

5.与OC比较.Swift有什么优点?

  • Swift 是一门新型语言,借鉴了JS,Python,C#,Ruby等语言特性,看上去偏脚本化,Swift 仍支持 cocoa touch 框架

  • 优点:

    • Swift更加安全,它是类型安全的语言。
    • Swift容易阅读,语法和文件结构简易化。
    • Swift更易于维护,文件分离后结构更清晰。
    • Swift代码更少,简洁的语法,可以省去大量冗余代码
    • Swift速度更快,运算性能更高。

静态库,动态库有什么区别?

静态库
  • 以.a 和 .framework为文件后缀名
  • 链接时会被完整的复制到可执行文件中,被多次使用就有多份拷贝
  • 静态库.a 和 framework区别
    • .a 主要是二进制文件,不包含资源,需要自己添加头文件.framework 可以包含头文件+资源信息
6.动态库
  • 以.tbd(之前叫.dylib) 和 .framework 为文件后缀名
  • 链接时不复制,程序运行时由系统动态加载到内存,系统只加载一次,多个程序共用(如系统的UIKit.framework等),节省内存。

7.OC的优点和缺点分别是什么?

优点:
  • OC是C语言的超集, 在C语言基础上增加了面向对象特性, 开发使用起来会方便高效.
  • 分类可以快速扩展类的方法.扩展模块之间相互不影响
  • 运行时特性,动态特性(动态类型,动态绑定,动态加载),提高了编程的灵活性
  • OC可以与C / C++进行混编
缺点:
  • 不支持多继承,多继承可以使用分类,协议,消息转发来弥补
  • 不支持运算符重载
  • 使用动态运行时类型,所有的方法都是函数调用,所以很多编译时优化方法都用不到,如内联函数等,性能低劣。
  • 执行效率比C低,语法怪异

8.遇到过BAD_ACCESS的错误吗?你是怎样调试的的?

BAD_ACCESS 报错属于内存访问错误,会导致程序崩溃,错误的原因是访问了野指针(悬挂指针)。

调试方法:
  • 设置全局断点快速定位问题代码所在行。
  • 开启僵尸对象诊断
  • Analyze分析
  • 重写object的respondsToSelector方法,现实出现EXEC_BAD_ACCESS前访问的最后一个object。
  • Xcode 7 已经集成了BAD_ACCESS捕获功能:Address Sanitizer。 用法如下:在配置中勾选✅Enable Address Sanitizer。

9.什么是函数式编程?链式编程?响应式编程?

函数编程:
  • 函数式编程是一种编程模型,他将计算机运算看做是数学中函数的计算,并且避免了状态以及变量的概念。函数式编程就像流水线一样,一顺顺的把问题解决完,从一个起点开始,一个个的调用函数,因为上一个函数有返回值是工具类本身,所以一个函数执行完之后,可以用上一个函数继续调用,有点链式思维在里面。
[view mas_makeConstraints:^(MASConstraintMaker *make){     
make.top.bottom.left.right.equalTo(self.view);
}];

链式编程:
  • top.bottom.left.right.equalTo(self.view)通过"."语法,将需要执行的代码连续的书写,就叫做链式编程,它使得代码简单易懂。
响应式编程:
  • 响应式编程是一种面向数据流和变化传播的编程范式。
  • 例如,在命令式编程环境中,a:=b+c表示将表达式的结果赋给a,而之后改变b或c的值不会影响a。但在响应式编程中,a的值会随着b或c的更新而更新。
  • Reactive Cocoa就是一个响应式编程的经典作品!

10.iOS代码签名作用

  • 确保从App Store下载的app是没被恶意篡改,如果修改则无法安装, 以及验证app开发者身份;

11.容错处理你们一般是注意哪些?

在团队协作开发当中,由于每个团队成员的水平不一,很难控制代码的质量,保证代码的健壮性,经常会发生由于后台返回异常数据造成app崩溃闪退的情况,为了避免这样的情况项目中做一些容错处理,显得格外重要,极大程度上降低了因为数据容错不到位产生崩溃闪退的概率。

发生错误的地方如下:

  • 1.字典
  • 2.数组;
  • 3.野指针;
  • 4.NSNull

11.项目开始容错处理没做?如何防止拦截潜在的崩溃?

  • 1.category给类添加方法用来替换掉原本存在潜在崩溃的方法。
  • 2.利用runtime方法交换技术,将系统方法替换成类添加的新方法。
  • 3.利用异常的捕获来防止程序的崩溃,并且进行相应的处理。
  • 4.使用 @try__Catch__方法进行拦截

#####总结:

  • 1.不要过分相信服务器返回的数据会永远的正确。
  • 2.在对数据处理上,要进行容错处理,进行相应判断之后再处理数据,这是一个良好的编程习惯。

12.@try @catch异常机制

Objective-C 异常机制 : -- 作用 : 开发者将引发异常的代码放在 @try 代码块中, 程序出现异常 使用 @catch 代码块进行捕捉; -- 每个代码块作用 : @try 代码块存放可能出现异常的代码, @catch 代码块 异常处理逻辑, @finally 代码块回收资源; -- 语法示例 :

try{
   //..执行的代码,其中可能有异常。一旦发现异常,则立即跳到catch执行。否则不会执行catch里面的内容
}catch(){
  //...除非try里面执行代码发生了异常,否则这里的代码不会执行
}finally{
  //..不管什么情况都会执行,包括try catch 里面用了return ,可以理解为只要执行了try或者catch,就一定会执行 finally
}
可以用于查找 bug,或者调试,防止崩溃使用

13.单元测试是什么?

单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。

    1. 单元测试可以进行 逻辑测试/异步测试/性能测试
    1. 单元测试是以代码测试代码。不是靠 NSLog 来测试,而是使用断言来测试的,提前预判条件必须满足。XCTAssert(条件, 不满足条件的描述)
    1. 可以在单元测试类中编写单独的测试用例方法。这些方法与普通的方法类似,但是方法名称必须以 test 开头,且不能有参数,不然不会识别为测试方法。
    1. 不是所有的方法都需要测试。例如私有方法不需要测试,只有暴露在 .h 中的方法需要测试。
  • 一般而言,代码的覆盖度大概在 50% ~ 70%。从 github 上得知:YYModel 测试覆盖度为 83%,AFNetworking 测试覆盖度为 77%,两者都是比较高的。

总结: 单元测试可以根据项目需要,针对一些关键业务,编写一些测试用例,可以方便的排查业务逻辑可能出现的问题.在后续改动时候也可以方便的测试等等.

14.一个上线的项目,知道这个方法可能会出问题,在不破坏改方法前提下,怎么搞?

  • 做一些容错处理,防止崩溃
  • 加一些日志收集,收集问题再具体分析
  • try_catch

15.Xcode编译器发展简史

  • Xcode3 以前: GCC;
  • Xcode3: 增加LLVM,GCC(前端) + LLVM(后端);
  • Xcode4.2: 出现Clang - LLVM 3.0成为默认编译器;
  • Xcode4.6: LLVM 升级到4.2版本;
  • Xcode5: GCC被废弃,新的编译器是LLVM 5.0,从GCC过渡到Clang-LLVM的时代正式完成

15.埋点处理

  • 埋点是用户行为统计
  • 埋点分为两种:
    • 页面统计,即在进入页面和离开页面的时候埋点,统计停留页面时长
    • 交互事件统计
  • 无痕埋点(自动埋点)解决方案: 技术原理:Method-Swizzling

对于一个给定的事件,UIControl会调用sendAction:to:forEvent:来将行为消息转发到UIApplication对象,再由UIApplication对象调用其sendAction:to:fromSender:forEvent:方法来将消息分发到指定的target上,那么,我们写一个UIControl的类别,通过替换它的sendAction:to:forEvent:方法,结合本地配置的埋点json或者plist文件(若埋点需要额外的参数,需要给UIControl的类别通过Runtime添加属性),便可以实现自动埋点的功能。

16.符号表

  • iOS 构建时产生的符号表,是内存地址、函数名、文件名和行号的映射表。格式大概是:
<起始地址> <结束地址> <函数> [<文件名:行号>]

Crash 时的堆栈信息,全是二进制的地址信息。如果利用这些二进制的地址信息来定位问题是不可能的,因此我们需要将这些二进制的地址信息还原成源代码种的函数以及行号,这时候符号表就起作用了。利用符号表将原始的 Crash 的二进制堆栈信息还原成包含行号的源代码文件信息,可以快速定位问题。iOS 中的符号表文件(DSYM) 是在编译源代码后,处理完 Asset Catalog 资源和 info.plist 文件后开始生成,生成符号表文件(DSYM)之后,再进行后续的链接、打包、签名、校验等步骤。

17.Pods的原理

  • 简单理解:快速的搜索多第三方框架,然后自动集成多工程里面。并编译成一个libPod.a的静态库给我们的项目用。

18.代码从 Git 上拉下来到生成 .ipa 都有哪些过程,期间都生成了什么文件。

  • git clone 远程地址到本地
  • pod 三方集成
  • 配置证书信息,签名
  • 打包 ipa

18.如果没有instruments,该如何检测memory leak, zombie object 之类的问题。

  • 查看MLeaksFinder源码分析,国内三方

19.为什么说Objective-C是一门动态的语言

  • 首先动态类型语言和静态类型语言
    • 动态类型语言
      • 动态类型语言是指在运行期间才去做数据类型检查的语言,也就是说,在用动态类型的语言编程时,永远也不用给任何变量指定数据类型,该语言会在你第一次赋值给变量时,在内部将数据类型记录下来。
    • 静态类型语言
      • 静态类型语言与动态类型语言刚好相反,它的数据类型是在编译其间检查的,也就是说在写程序时要声明所有变量的数据类型,C/C++是静态类型语言的典型代表,其他的静态类型语言还有C#、JAVA等。
  • OC具有相当多的动态特性,表现在三个方面:动态类型、动态绑定、动态加载。之所以叫做动态,是因为必须到运行时才会做一些事情。
    • 动态类型:即运行时再决定对象的类型。这类动态类型在日常应用中非常常见。简单说就是id类型。实际上静态类型因为其固定性和可预知性而使用的非常广泛,静态类型是请类型,而动态类型属于弱类型。运行时决定接受者。
    • 动态绑定:基于动态类型,在某个实例对象被确定后,起类型就被确定了。该对象的属性和响应的消息也被完全确定,这就是动态绑定
    • 动态加载:根据需求加载所需要的资源,这点很容易理解,对于ios开发来说,基本就是根据不同的急性左适配。最经典的例子就是在Retina设备上加载@2X的图片,而在老一些的普通设备上加载原图。随着Retina iPad的推出,和之后可能的Retina Mac的出现,这个特性相信会被越来越多的使用。让程序在运行时添加代码块以及其他资源。用户可以根据需要加载一些课指向代码和资源,而不是在启动时就加载所有组件。可执行代码中可以含有和程序运行时整合的新类。