iOS面试题汇总一

iOS 属性中常用修饰词的总结


开发常用的工具有哪些?

通过回答这个问题,一方面可以看出这个应聘者在iOS开发领域的深入程度。如果只知道Xcode,Cocoapods,说明是初级或者根本不愿意在业余时间花费精力去扩展。

参考答案:

常用的iOS开发工具有:

  • Xcode开发工具及配套的Instruments工具
  • Xcode常用的插件
  • Cocoapods第三方库管理依赖工具
  • SourceTree是git版本管理工具
  • CornerStone是SVN版本管理工具

熟悉CocoaPods么?能大概讲一下工作

iOS项目中使用第三方类库可以说是非常常见的事,但是要正确地配置他们有时候是非常繁琐的事情,幸运的是CocoaPods是一个很好的解决方案。
什么是CocoaPods
CocoaPodsOS XiOS下的一个第三类库管理工具,通过CocoaPods工具我们可以为项目添加被称为“Pods”的依赖库(这些类库必须是CocoaPods本身所支持的),并且可以轻松管理其版本。
Cocoapods意义体现在两个方面。第一,在引入第三方库时它可以自动为我们完成各种各样的配置,包括配置编译阶段、连接器选项、甚至是ARC环境下的-fno-objc-arc配置等。第二,使用CocoaPods可以很方便地查找新的第三方库,这些类库是比较标准的,而不是网上随便找到的,这样可以让我们找到真正好用的类库。
接下来我们将介绍CocoaPods的使用。
CocoaPods的核心组件
CocoaPods是用Ruby写的,并划分成了若干个Gem包。
CocoaPods在解析执行过程中最重要的几个包的路径分别是:CocoaPods/CocoaPodsCocoaPods/Core CocoaPods/Xcodeproj 
CocoaPods / CocoaPod:这是面向用户的组件,每当执行一个pod命令时,这个组件将被激活。它包括了所有实用CocoaPods的功能,并且还能调用其他gem包来执行任务。 
CocoaPods / CoreCore gem提供了与CocoaPods相关的文件(主要是podfilepodspecs)的处理。 
Podfile:该文件用于配置项目所需要的第三方库,它可以被高度定制。本文中我们主要在这里做动作。
Podspec:该文件描述了一个库将怎样被添加进工程中。.podspec文件可以标识该第三方库所需要的源码文件、依赖库、编译选项,以及其他第三方库需要的配置。 
CocoaPods / Xcodeproj:这个包负责处理工程文件,它能创建以及修改.xcodeproj文件和.xcworkspace文件。它也可以作为一个独立的包使用,当你要编写修改项目文件的脚本时,可以考虑使用CocoaPods/Xcodeproj


你一般是怎么用Instruments的?

这个就是工作经验的问题了。Instruments工具里面有很多个选项,没有必要每个都答,其实笔者也只用过里面的几个而已。

参考答案:

  • 使用Allocations来检测内存和堆栈信息
  • 使用Leaks检测内存的使用情况,包括内存泄露问题
  • 使用Zombies来检测过早释放的僵尸对象,通过它可以检测出在哪里崩溃的。
  • 使用Time Profiler来检测CPU内存使用情况

你一般是如何调试Bug的?

这个问题看起来很笼统,但又一针见血。通过应聘者的回答,可很直观地看出这个应聘者的处理bug的能力,以及其解决问题的思维。

参考答案:

Bug分为测试中的Bug和线上的Bug:

  • 线上Bug:项目使用了友盟统计,因此会有崩溃日志,通过解析dYSM可以直接定位到大部分bug崩溃之处。解决线上bug需要从主干拉一个新的分支,解决bug并测试通过后,再合并到主干,然后上线。若是多团队开发,可以将fix bug分支与其他团队最近要上线的分支集成,然后集成测试再上线。
  • 测试Bug:根据测试所反馈的bug描述,若语义不清晰,则直接找到提bug人,操作给开发人员看,最好是可以bug复现。解决bug时,若能根据描述直接定位bug出错之处,则好处理;若无法直观定位,则根据bug类型分几种处理方式,比如崩溃的bug可以通过instruments来检测、数据显示错误的bug,则需要阅读代码一步步查看逻辑哪里写错。

对于开发中出现的崩溃或者数据显示不正常,那就需要根据经验或者相关工具来检测可能出错之处。当然,团队内沟通解决是最好的。

你在你的项目中用到了哪些设计模式?

项目中使用了很多的设计模式,我相信面试官最好听到的不仅仅是设计模式的名字,更想听到的是这些设计模式在项目中如何应用。因此,笔者认为这个问题隐式地说明了应该回答设计模式及其在项目中的应用。

参考答案:

  • 单例设计模式:在项目中,单例是必不可少的。比如UIApplication、NSUserDefaults就是苹果提供的单例。在项目中经常会将用户数据管理封装成一个单例类,因此用户的信息需要全局使用。
  • MVC设计模式:现在绝大部分项目都是基于MVC设计模式的,现在有一部分开发者采用MVVM、MVP等模式。
  • 通知(NSNotification)模式:通知在开发中是必不可少的,对于跨模块的类交互,需要使用通知;对于多对多的关系,使用通知更好实现。
  • 工厂设计模式:在我的项目中使用了大量的工厂设计模式,特别是生成控件的API,都已经封装成一套,全部是扩展的类方法,可简化很多的代码。
  • KVC/KVO设计模式:有的时候需要监听某个类的属性值的变化而做出相应的改变,这时候会使用KVC/KVO设计模式。在项目中,我需要监听model中的某个属性值的变化,当变化时,需要更新UI显示,这时候使用KVC/KVO设计模式就很方便了。

就说这么多吧,还有很多的设计模式,不过其它并不是那么常用。

如何实现单例,单例会有什么弊端?

单例在项目中的是必不可少的,它可以使我们全局都可共享我们的数据。这只是简单的问题,大家根据自己的情况回答。

参考答案:

  • 首先,单例写法有好几种,通常的写法是基于线程安全的写法,结合 dispatch_once 来使用,保证单例对象只会被创建一次。如果不小心销毁了单例,再调用单例生成方法是不会再创建的。 
  • 其次,由于单例是约定俗成的,因此在实际开发中通常不会去重写内存管理方法。

单例确实给我们带来的便利,但是它也会有代价的。单例一旦创建,整个App使用过程都不会释放,这会占用内存,因此不可滥用单例。

iOS是如何管理内存的?

我相信很多人的回答是内存管理的黄金法则,其实如果我是面试官,我想要的答案不是这样的。我希望的回答是工作中如何处理内存管理的。

参考答案:

  • Block内存管理:由于使用block很容易造成循环引用,因此一定要小心内存管理问题。最好在基类controller下重写dealloc,加一句打印日志,表示类可以得到释放。如果出现无打印信息,说明这个类一直得不到释放,表明很有可能是使用block的地方出现循环引用了。对于block中需要引用外部controller的属性或者成员变量时,一定要使用弱引用,特别是成员变量像 _testId 这样的,很多人都没有使用弱引用,导致内存得不到释放。 
  • 对于普通所创建的对象,因为现在都是ARC项目,所以记住内存管理的黄金法则就可以解决。

如何追踪app崩溃率,如何解决线上闪退

当iOS设备上的App应用闪退时,操作系统会生成一个crash日志,保存在设备上。crash日志上有很多有用的信息,比如每个正在执行线程的完整堆栈跟踪信息和内存映像,这样就能够通过解析这些信息进而定位crash发生时的代码逻辑,从而找到App闪退的原因。通常来说,crash产生来源于两种问题:违反iOS系统规则导致的crash和App代码逻辑BUG导致的crash,下面分别对他们进行分析。

违反iOS系统规则产生crash的三种类型:

(1) 内存报警闪退

当iOS检测到内存过低时,它的VM系统会发出低内存警告通知,尝试回收一些内存;如果情况没有得到足够的改善,iOS会终止后台应用以回收更多内存;最后,如果内存还是不足,那么正在运行的应用可能会被终止掉。在Debug模式下,可以主动将客户端执行的动作逻辑写入一个log文件中,这样程序童鞋可以将内存预警的逻辑写入该log文件,当发生如下截图中的内存报警时,就是提醒当前客户端性能内存吃紧,可以通过Instruments工具中的Allocations 和 Leaks模块库来发现内存分配问题和内存泄漏问题。

(2) 响应超时

当应用程序对一些特定的事件(比如启动、挂起、恢复、结束)响应不及时,苹果的Watchdog机制会把应用程序干掉,并生成一份相应的crash日志。这些事件与下列UIApplicationDelegate方法相对应,当遇到Watchdog日志时,可以检查上图中的几个方法是否有比较重的阻塞UI的动作。

application:didFinishLaunchingWithOptions:

applicationWillResignActive:

applicationDidEnterBackground:

applicationWillEnterForeground:

applicationDidBecomeActive:

applicationWillTerminate:


一看到“用户强制退出”,首先可能想到的双击Home键,然后关闭应用程序。不过这种场景一般是不会产生crash日志的,因为双击Home键后,所有的应用程序都处于后台状态,而iOS随时都有可能关闭后台进程,当应用阻塞界面并停止响应时这种场景才会产生crash日志。这里指的“用户强制退出”场景,是稍微比较复杂点的操作:先按住电源键,直到出现“滑动关机”的界面时,再按住Home键,这时候当前应用程序会被终止掉,并且产生一份相应事件的crash日志。(3) 用户强制退出

应用逻辑的Bug

大多数闪退崩溃日志的产生都是因为应用中的Bug,这种Bug的错误种类有很多,比如:

  • SEGV:(Segmentation Violation,段违例),无效内存地址,比如空指针,未初始化指针,栈溢出等;

  • SIGABRT:收到Abort信号,可能自身调用abort()或者收到外部发送过来的信号;

  • SIGBUS:总线错误。与SIGSEGV不同的是,SIGSEGV访问的是无效地址(比如虚存映射不到物理内存),而SIGBUS访问的是有效地址,但总线访问异常(比如地址对齐问题);

  • SIGILL:尝试执行非法的指令,可能不被识别或者没有权限;

  • SIGFPE:Floating Point Error,数学计算相关问题(可能不限于浮点计算),比如除零操作;

  • SIGPIPE:管道另一端没有进程接手数据;

常见的崩溃原因基本都是代码逻辑问题或资源问题,比如数组越界,访问野指针或者资源不存在,或资源大小写错误等。

crash的收集

如果是在windows上你可以通过itools或pp助手等辅助工具查看系统产生的历史crash日志,然后再根据app来查看。如果是在Mac 系统上,只需要打开xcode->windows->devices,选择device logs进行查看,如下图,这些crash文件都可以导出来,然后再单独对这个crash文件做处理分析。

iOS面试题汇总一_第1张图片

看日志

市场上已有的商业软件提供crash收集服务,这些软件基本都提供了日志存储,日志符号化解析和服务端可视化管理等服务:

  • Crashlytics (www.crashlytics.com)

  • Crittercism (www.crittercism.com)

  • Bugsense (www.bugsense.com)

  • HockeyApp (www.hockeyapp.net)

  • Flurry(www.flurry.com)

开源的软件也可以拿来收集crash日志,比如Razor,QuincyKit(git链接)等,这些软件收集crash的原理其实大同小异,都是根据系统产生的crash日志进行了一次提取或封装,然后将封装后的crash文件上传到对应的服务端进行解析处理。很多商业软件都采用了Plcrashreporter这个开源工具来上传和解析crash,比如HockeyApp,Flurry和crittercism等。

iOS面试题汇总一_第2张图片

crash信息

由于自己的crash信息太长,找了一张示例:

1)crash标识是应用进程产生crash时的一些标识信息,它描述了该crash的唯一标识(E838FEFB-ECF6-498C-8B35-D40F0F9FEAE4),所发生的硬件设备类型(iphone3,1代表iphone4),以及App进程相关的信息等;

2)基本信息描述的是crash发生的时间和系统版本;

3)异常类型描述的是crash发生时抛出的异常类型和错误码;

4)线程回溯描述了crash发生时所有线程的回溯信息,每个线程在每一帧对应的函数调用信息(这里由于空间限制没有全部列出);

5)二进制映像是指crash发生时已加载的二进制文件。以上就是一份crash日志包含的所有信息,接下来就需要根据这些信息去解析定位导致crash发生的代码逻辑, 这就需要用到符号化解析的过程(洋名叫:symbolication)。

解决线上闪退

首先保证,发布前充分测试。发布后依然有闪退现象,查看崩溃日志,及时修复并发布。

2.什么是事件响应链,点击屏幕时是如何互动的,事件的传递。

iOS面试题汇总一_第3张图片

事件响应链

对于IOS设备用户来说,他们操作设备的方式主要有三种:触摸屏幕、晃动设备、通过遥控设施控制设备。对应的事件类型有以下三种:

1、触屏事件(Touch Event)

2、运动事件(Motion Event)

3、远端控制事件(Remote-Control Event)

响应者链(Responder Chain)

响应者对象(Responder Object),指的是有响应和处理事件能力的对象。响应者链就是由一系列的响应者对象构成的一个层次结构。

UIResponder是所有响应对象的基类,在UIResponder类中定义了处理上述各种事件的接口。我们熟悉的UIApplication、 UIViewController、UIWindow和所有继承自UIView的UIKit类都直接或间接的继承自UIResponder,所以它们的实例都是可以构成响应者链的响应者对象。

响应者链有以下特点:

1、响应者链通常是由视图(UIView)构成的;

2、一个视图的下一个响应者是它视图控制器(UIViewController)(如果有的话),然后再转给它的父视图(Super View);

3、视图控制器(如果有的话)的下一个响应者为其管理的视图的父视图;

4、单例的窗口(UIWindow)的内容视图将指向窗口本身作为它的下一个响应者

需要指出的是,Cocoa Touch应用不像Cocoa应用,它只有一个UIWindow对象,因此整个响应者链要简单一点;

5、单例的应用(UIApplication)是一个响应者链的终点,它的下一个响应者指向nil,以结束整个循环。

点击屏幕时是如何互动的

iOS系统检测到手指触摸(Touch)操作时会将其打包成一个UIEvent对象,并放入当前活动Application的事件队列,单例的UIApplication会从事件队列中取出触摸事件并传递给单例的UIWindow来处理,UIWindow对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为hit-test view。

UIWindow实例对象会首先在它的内容视图上调用hitTest:withEvent:,此方法会在其视图层级结构中的每个视图上调用pointInside:withEvent:(该方法用来判断点击事件发生的位置是否处于当前视图范围内,以确定用户是不是点击了当前视图),如果pointInside:withEvent:返回YES,则继续逐级调用,直到找到touch操作发生的位置,这个视图也就是要找的hit-test view。

hitTest:withEvent:方法的处理流程如下:首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内;若返回NO,则hitTest:withEvent:返回nil;若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕;若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束;如所有子视图都返回非,则hitTest:withEvent:方法返回自身(self)。

事件的传递和响应分两个链:

传递链:由系统向离用户最近的view传递。UIKit –> active app’s event queue –> window –> root view –>……–>lowest view

响应链:由离用户最近的view向系统传递。initial view –> super view –> …..–> view controller –> window –> Application


你可能感兴趣的:(iOS面试题汇总一)