iOS自编面试题
最近要负责面试,于是各处搜罗了一些面试题,用于当面面试使用(非笔试题),也当作自己的一个复习。同时提供了一个我自己角度的评判,仅供参考。
笔试题部分请参考:iOS自编笔试题
以下所以答案,如果面试时未能回答,作为补充问题回答正确后,相应分值 - 1。
回答了其他答案,正确且合理的情况下,酌情给分。
Swift 相关
主要考察Swift语法相关内容
基础
什么是Swift方法默认值?什么情况下会使用它?
得分 | 分值 | 答案 |
---|---|---|
1 | 在定义函数时给参数赋一个默认值,调用该函数时可以不传该参数值 | |
3 | 减少函数重载的需要 | |
1 | 避免在调用函数时多次输入相同的值 |
什么是Optional类型,它的作用是什么?如何使用Optional类型?
得分 | 分值 | 答案 |
---|---|---|
2 | 用来表示一个值可能存在,也可能不存在的情况 | |
1 | Optional 类型的作用是为避免因为没有处理 nil 而导致程序崩溃的问题 | |
1 | Optional 类型可以通过在类型名后加一个 ? 来定义 | |
2 | 如果一个 Optional 类型的变量有值,那么可以使用可选绑定 (if let)将其取出 | |
3 | 在使用 Optional 类型时,尽量避免使用强制解包 | |
3 | Optional 的实际实现是一个枚举 |
Swift中的struct和class有什么区别?什么情况下应该使用struct?
区别:
得分 | 分值 | 答案 | 备注 |
---|---|---|---|
1 | class 支持继承,而 struct 不支持继承 | - | |
1 | class 支持类型转换,而 struct 不支持类型转换 | 这里的类型转换指的是子类和父类之间的隐式类型转换 | |
1 | class 是引用类型,而 struct 是值类型 | - |
使用情况:
得分 | 分值 | 答案 |
---|---|---|
1 | 如果对象的复杂度较高,并需要支持继承和类型转换,应该使用 class | |
1 | 如果对象比较简单,并且需要高性能,可以考虑使用 struct |
什么是 Swift 中的访问控制(Access Control)?它有哪些级别?
得分 | 分值 | 答案 | 备注 |
---|---|---|---|
1/3 | Open: 可以被同一模块内和外部模块的代码访问,允许被继承和重写 | 提到外部模块计3分,否则1分 | |
1/3 | Public: 可以被同一模块内和外部模块的代码访问,但是在外部模块不能被重写或继承,内部模块可以 | 提到外部模块计3分,否则1分 | |
1/3 | Internal: 只能被同一模块内的代码访问,不能被外部模块访问(默认访问级别) | 提到是默认权限计3分,否则1分 | |
1 | fileprivate: 只能被同一源文件内的代码访问 | - | |
1 | private: 只能在所定义的作用域内访问(比如函数、方法、类、结构体等),不能被同一文件中的其他作用域访问。 | - | |
2 | Xcode 生成的 struct 的初始化方法是 Internal 属性,有外部访问需求的话要手动实现 init 方法 | - |
什么是 Swift属性观察器(Property Observer)?
得分 | 分值 | 答案 | 备注 |
---|---|---|---|
1 | 知道属性观察器是 willSet 和 didSet | - | |
1 | 只能应用于存储属性,而不能应用于计算属性 | - | |
2+1 | 如果属性是在初始化期间(init和定义时赋值)设置的,则不会调用 willSet 和 didSet 观察器 | 回答 init,2分,回答定义时,1分,总分3分 |
Swift中一个方法如何返回多个值?
得分 | 分值 | 答案 |
---|---|---|
1 | 可以使用元组(Tuple)来实现一个方法返回多个值的效果 | |
0 | 自定义类型、数组、字典等 |
什么是 Swift 中的 Extension(扩展)?如何使用 Extension?
得分 | 分值 | 答案 |
---|---|---|
1 | 允许开发者向已有的类型添加新的方法、计算属性等,而无需继承子类或使用 Objective-C runtime | |
1 | 不能添加存储属性或属性观察器 | |
1 | 扩展中的方法不能和已有方法重名 | |
2 | 使得代码更加模块化,易于维护 | |
2 | 也可以对第三方类进行扩展,而无需改动原代码,更加方便地满足自己的需求 |
Swift中的协议(protocol)是什么?它有什么作用?
如果回答中包含了协议中的范型等内容,参考 什么是范型 一题中的分值。
得分 | 分值 | 答案 | 备注 |
---|---|---|---|
1 | 协议定义了一组方法、属性 | - | |
1 | 可以被类、结构体和枚举类型实现,然后实现方就可以使用协议中提供的内容 | - | |
3 | 提高代码的模块化程度、可重用性和可维护性,减少代码的耦合性 | - | |
1/2 | 可以扩展协议,为协议添加默认实现 | 如果面试者没有提,可以进行提问“Swift 协议如何做默认实现?” | |
5 | 习惯使用扩展遵循协议,而不是在声明类型时 | - | |
-5 | 习惯在声明类型时遵循协议 | - | |
1/3/10 | 提到了面向协议编程 | 仅仅提到,计1分;有对应描述、解释,计3分;描述详细,并配合示例,计10分 |
什么是泛型(Generic)类型?它们在Swift中有什么作用?
笔试题中包含范型擦出,所以本题主要考察面试者有没有 “用范型提高代码可读性、维护性” 的意识。
在这里不必回答范型擦出。回答范型擦出不额外计分。
得分 | 分值 | 答案 |
---|---|---|
1 | 可以用于定义函数、类、结构体、枚举等多种类型的通用类型机制 | |
2 | 泛型类型可以用于消除代码重复,提高代码的可读性和可维护性 | |
1 | 定义协议时,可以使用关联类型来实现更加灵活的数据处理 |
什么是Swift字面量协议?
得分 | 分值 | 答案 |
---|---|---|
1 | ExpressibleByNilLiteral | |
1 | ExpressibleByIntegerLiteral:允许类型从整数字面量创建实例。 | |
1 | ExpressibleByFloatLiteral:允许类型从浮点数字面量创建实例。 | |
1 | ExpressibleByBooleanLiteral:允许类型从布尔字面量创建实例。 | |
1 | ExpressibleByStringLiteral:允许类型从字符串字面量创建实例。 | |
1 | ExpressibleByArrayLiteral:允许类型从数组字面量创建实例。 | |
1 | ExpressibleByDictionaryLiteral:允许类型从字典字面量创建实例。 |
只要提到有这么几个协议(不用说出具体的名字),可以通过数字、字符串、Bool、数组、字典字面量来初始化对应的类型即可。
什么是延迟加载(懒加载,Lazy Loading)?在Swift中你一般什么时候使用它?
得分 | 分值 | 答案 | 备注 |
---|---|---|---|
1 | 在Swift中,延迟加载可以通过使用 lazy 关键字来实现 | - | |
1 | lazy 关键字可以用于属性 | - | |
1 | 在第一次访问该变量或属性时,系统会执行相应的代码进行初始化 | - | |
5 | 几乎无脑使用懒加载来定义任何的属性 | - |
Swift中的高阶函数(Higher Order Function)有哪些?请分别说明它们的作用。
主要包含以下几个即可,其余的例如 contains、allSatisfy 等说了也计1分。
如果只说出了函数名,没有说明作用,依然得分。或者能说出来功能,具体函数名拼不全也可。如果下面这些没有说全,面试官可提示 “还有没有了?”
得分 | 分值 | 答案 | 备注 |
---|---|---|---|
1 | map:将一个集合中的每个元素通过一个函数映射为另一个元素,返回一个新的集合 | - | |
1 | filter:过滤一个集合中符合特定条件的元素,返回一个新的集合 | - | |
1 | reduce:通过对一个集合中的所有元素进行累加或累积操作,返回一个最终的结果 | - | |
1 | flatMap:将一个集合中的每个元素通过一个函数映射为另一个集合,然后将所有集合中的元素合并为一个新的集合 | - | |
1 | compactMap:对一个序列进行变换操作,并返回一个新的序列,该序列将所有的 nil 元素去除 | - | |
1 | sorted:对一个集合中的元素进行排序,返回一个新的排序后的集合 | - | |
1 | forEach:对一个集合中的每个元素执行一段特定的代码 | - |
flatMap 和 compactMap 这两个之间有什么区别?
得分 | 分值 | 答案 | 备注 |
---|---|---|---|
1 | flatMap 用于高维数组的降维,例如二维数组拍平为一维数组 | - | |
1 | compactMap 用于过滤数组中的可选值,去掉所有返回 nil 的元素 | - | |
3 | Swift 4.2 中将这两个函数拆开,不可混用 | 提到不可混用即可 |
进阶
在Swift中如何实现一段代码在整个程序的生命周期内只会执行一次?
得分 | 分值 | 答案 | 备注 |
---|---|---|---|
0 | dispatch_once | 不得分,Swift 3 及以上版本已废弃 | |
0 | 单例 | 不得分,题目要求的是 “一段代码”,而不是一个对象 | |
3 | (lazy var)/static + Void 类型的属性,定义时使用 {}() 包括需要执行的代码。借助了懒加载的特性 |
Swift中的类型推断(Type Inference)是什么?请说明它的作用
得分 | 分值 | 答案 | 备注 |
---|---|---|---|
2 | 编译器能够推断出变量或常量的类型而不需要显式声明其类型 | - | |
2 | 使代码更加简洁,减少了类型声明的冗余,同时也减少了因类型声明错误而引起的编译错误的可能性 | - | |
-10 | 不建议使用 | 如果面试者说建议显式声明类型(除非是为了解决Xcode代码提示的bug),否则扣10分 |
Swift中的final关键字是什么意思?它的作用是什么?
得分 | 分值 | 答案 | 备注 |
---|---|---|---|
1+2+2 | final 关键字可以修饰类、方法和变量 | 提到类计1分;提到方法、变量每个2分,总分5分 | |
1 | final 修饰后不可集成、不可重写 | - | |
3 | private 的类型,其内的属性和方法会在编译时自动添加 final | - |
Swift中的String类型和ObjC中的NSString类型有什么区别?
得分 | 分值 | 答案 |
---|---|---|
1 | 底层实现不同,Swift 的 String 类型是一个值类型,而ObjC的 NSString 类型是一个类 | |
1 | Swift的 String 类型可以无缝地与ObjC中的 NSString 类型互相转换 | |
2 | Swift中的字符串是Unicode兼容的,可以直接使用Unicode字符和表情符号,而ObjC中的字符串是基于ASCII编码的,无法直接处理Unicode字符和表情符号 |
Swift中的枚举相比较ObjC中的枚举有什么优势?
得分 | 分值 | 答案 |
---|---|---|
2 | 每个枚举成员可以有自己的关联值,这使得枚举更加灵活。而ObjC中的枚举只能包含整数 | |
3 | 枚举是一等公民,它和类、结构体等类型一样,可以有自己的方法,甚至遵循协议。 |
Swift中的延迟(Defer)语句有什么作用?
得分 | 分值 | 答案 | 备注 |
---|---|---|---|
0/2/3 | Defer语句可以用来在函数/作用域执行完毕之前执行一些清理工作 | 回答 “函数” 0分,“作用域” 2分,能给出作用域的例子,例如 if 中使用的情况,3分。如果面试者没提到作用域问题,则可以当作进阶问题抛出 | |
1 | Defer语句可以用来执行一些清理工作 | - | |
1 | 无论函数是正常返回还是抛出异常,都会执行 | - | |
1 | 虽然形式上像是闭包,但更像是语法糖,所以不会捕获变量 | - | |
2 | 当函数内有多个 defer 语句时,将按照倒序的顺序执行,即最后一个 defer 先执行 | 如果面试者没回答,则可以当作进阶问题抛出 |
什么是Swift方法派发?有哪几种?
方法调度和方法派发是一回事,不同的翻译
得分 | 分值 | 答案 | 备注 |
---|---|---|---|
1 | 宏观上分为静态(直接)派发和动态派发两种 | - | |
3 | 其中动态派发又包含两种:表派发/函数表派发和消息派发 | - | |
5/6 | 静态派发:编译时找到指令所在的位置。所以执行速度快,还允许编译期做各种优化,例如内联 | 提到内联,额外加1分,共6分 | |
5/6 | 表派发:运行时决定实现方式,理论上速度也很快,编译期决定函数表 | 提到编译期决定寒暑表,额外加1分,共6分 | |
5 | 消息派发:Objective-C 的逻辑,可以在运行时修改消息接收对象 | - | |
5 | 会通过看 SIL 确定派发方式 | - |
请说明Swift中Array类型的底层实现方式
总分值10分
Array 使用了一个名为“缓冲区”(buffer)的数据结构来存储元素。
缓冲区由三个部分组成:
- 指向元素存储位置的指针
- 缓冲区的容量大小
- 缓冲区的元素个数
Swift的switch语法和ObjC的switch语法,在底层实现方式上有什么区别?
总分值10分
ObjC 的 switch 还是某种意义上的 goto 语法,命中的时候会跳到对应的代码的地址,这段代码是连续的,需要 break 来决定执行到哪里。这种实现方式主要是为了方便从早期 C 语言中继承而来。
而 swift 的 switch 会被编译成类似 if else 的语法,作用域更明确。
SIL是什么?怎么将Swift代码转换为SIL代码?
得分 | 分值 | 答案 | 备注 |
---|---|---|---|
1 | SIL是Swift中间语言,是源代码和机器代码之间的一种中间代码 | - | |
1/2 | 编译器会将Swift代码编译成SIL,然后对SIL进行优化和转换,最后将其编译成机器代码 | 提到编译器对 SIL 进行优化,计2分,否则计1分 | |
3 | 使用 swiftc -emit-sil + <file_name.swift> 将 swift 文件转换为 SIL 代码 | - |
请简述 Swift extension 的实现原理
总分10分
在 Swift 内部,每个类或结构体都有一个名为 vtable(虚函数表)的表格,其中记录了类或结构体的方法的地址。当 Swift 调用一个方法时,它会先检查类或结构体的类型,然后查找该类型的 vtable 表格,以获取该方法的地址并调用它。
当我们在 Extension 中定义一个新方法时,Swift 编译器会将其添加到 vtable 表格中,并根据需要重新生成 vtable 表格,以确保所有方法都在正确的位置。这就是 Swift Extension 实现的底层原理。
iOS 开发相关
主要考察 iOS 开发相关知识点,和具体开发语言没有太大的关系。
基础
请简述iOS开发中,APNS的推送机制
总分5分
APNS的推送机制包括以下几个步骤:
-
应用程序在设备上注册远程通知。注册成功后,设备会产生一个唯一的令牌(device token),用于标识该设备。
-
应用程序将令牌发送给后台服务器,后台服务器将令牌与设备绑定起来。
-
当需要向设备发送通知时,后台服务器发送一个推送通知请求到APNS服务器。
-
APNS服务器将推送通知发送到指定的设备上。
-
设备接收到推送通知后,根据推送通知的内容执行相应的操作,例如打开应用程序、显示提醒等。
常见的触发离屏渲染的场景有哪些?
总分10分
进阶
离屏渲染是什么?为什么会触发离屏渲染
总分15分
什么是RunLoop?RunLoop的运行模式有哪几种?
总分10分
RunLoop是iOS中的一个事件循环机制,负责处理用户事件,如触摸事件、定时器事件、网络请求事件等,同时还要处理UI更新、线程通信等任务。
RunLoop会将事件源分发到相应的处理器进行处理,如果RunLoop没有事件需要处理,那么它会进入休眠状态,直到有新的事件到来。
常见的RunLoop运行模式有以下几种:
-
DefaultRunLoopMode:默认模式,处理UI事件、定时器、网络事件等。
-
UITrackingRunLoopMode:处理UIScrollView滑动过程中的事件,优先级高于DefaultRunLoopMode。
-
CommonModes:是一个集合模式,可以添加多个模式,包括DefaultRunLoopMode和UITrackingRunLoopMode等,用于处理需要同时响应多个模式下的事件。
在运行RunLoop时,需要指定一个运行模式,如果没有指定,则会使用DefaultRunLoopMode。
iOS开发中的响应链机制是什么?请举例说明
总分15分
在iOS应用中,将用户的触摸事件(touch event)传递到正确的视图(view)或者控制器(controller)的过程就是响应链。
当用户在屏幕上进行触摸时,触摸事件会被UIWindow捕获,并从UIWindow开始向下传递,直到找到合适的响应者处理事件为止。
当用户触摸到界面上的按钮时,iOS系统会将此事件封装成一个 UIEvent 对象,并通过 UIApplication 对象将该事件发送给当前 UIWindow 对象。接着,该 UIWindow 对象会将该事件发送给最前端的 UIResponder 对象,即当前展示在屏幕上的 UIViewController 的 view 属性对应的 UIView 对象。这一过程即为响应链的开始。
接着,该 UIView 对象会通过 hitTest:withEvent: 方法判断当前触摸点是否在该 UIView 对象内。如果在,则该 UIView 对象会将该事件传递给自己的 touchesBegan:withEvent: 方法进行处理。如果不在,则该 UIView 对象会将该事件传递给其子视图进行处理。这一过程就是响应链的传递过程。
当子视图接收到事件后,会按照相同的方式进行处理。如果该子视图的 userInteractionEnabled 属性为 NO,则该视图将不会响应该事件,并将该事件传递给下一个响应者。如果子视图的 userInteractionEnabled 属性为 YES,则该视图会先进行 hitTest:withEvent: 方法的判断,如果触摸点在该视图内,则该视图将该事件传递给自己的 touchesBegan:withEvent: 方法进行处理,否则将该事件传递给下一个响应者。
最终,如果所有的子视图都没有处理该事件,则该事件会传递给该 UIView 对象的父视图进行处理,一直传递到 UIWindow 对象和 UIApplication 对象。如果该事件都没有被处理,则该事件将被丢弃。
架构相关
主要考察架构、设计能力
基础
什么是MVC架构?它的组成部分是什么?在iOS开发中,如何实现MVC架构?
总分10分
什么是MVVM架构?它的组成部分是什么?在iOS开发中,如何实现MVVM架构?
总分15分
什么是装饰器(Decorator)模式?在Swift中如何使用?
得分 | 分值 | 答案 |
---|---|---|
1 | 装饰器模式允许开发者在不修改对象结构的情况下,动态地添加行为。 | |
3 | 在 Swift 中,装饰器模式可以通过使用协议和扩展来实现。 | |
5 | 具体来说,可以定义一个协议来表示被装饰者的基本行为,然后创建一个装饰器协议,该协议继承自被装饰者协议,并添加了额外的行为。最后,可以使用一个类来实现装饰器协议,该类通过存储被装饰者对象的引用来实现对被装饰者对象的包装。 |
进阶
什么是尾递归优化(Tail-Call Optimization)?在Swift中如何实现尾递归优化?
得分 | 分值 | 答案 |
---|---|---|
2 | 尾递归优化是一种优化技术,它可以避免在递归算法中出现栈溢出的问题 | |
3 | 在递归函数的最后一个操作是调用自身,并且没有其他操作时,尾递归优化可以将递归转换为循环,避免了栈的增长 |
什么是函数柯里化(Function Currying)?在Swift中如何实现函数柯里化?
得分 | 分值 | 答案 |
---|---|---|
2 | 函数柯里化是一种将多个参数的函数转换为一系列只有单个参数的函数的技术 | |
3 | 在swift中,可以通过将函数返回值声明为闭包类型,将原本需要的参数放到返回值的闭包中,来实现函数柯里化 |
设计
在你的项目中,你是如何划分模块(Module)和层级(Layer)的?请举例说明。
总分10分
开放式题目。
不用 target-action 的方式如何设计一个路由组件?
总分20分
开放式题目。
当你从ObjC转到Swift之后,你最大的感触是什么?
总分20分
开放式题目。
当你从ObjC转到Swift之后,你做了哪些事让你的Swift代码看上去不那么ObjC?
总分30分
开放式题目。下面提供几个示例:
- 使用Swift的数据结构和类型:使用Swift的Array、Dictionary等数据结构,避免使用NSArray和NSDictionary等。
- 使用Swift的可选型(Optional):使用可选型来处理变量是否存在的情况,而不是定义0,空字符串,或者使用ObjC中的nil或NSNotFound等。
- 使用Swift的语言特性:使用Swift中的高阶函数和闭包等语言特性,避免使用ObjC中的循环和函数指针等。
- 减少使用全局变量:尽量避免使用全局变量,而采用更具体和局部的方式来传递数据。
- 减少使用强制类型转换:尽量避免使用强制类型转换,而采用更安全和优雅的方式来处理类型转换。