【iOS面试题二】Swift编程语言

APP(iOS烂笔头---您的iOS面试备考神器!)

该APP除了包含完整的内容,还有AI知识扩展。使用便捷、欢迎下载。

题库(一):Objective-C高级编程

题库(二):Swift编程语言

题库(三):SwiftUI编程思想

题库(四):SwiftUI与Combine编程

题库(五):函数响应式编程

题库(六):知识要点

一、基础语法

1、什么是强制解析?

用!来访问一个可选变量,如:

let someValue: Int? = 3
print(someValue!)

2、什么是隐式解析?

用!来声明一个可选变量(人为保证其不为空值),如:

let someValue: Int! = 3
print(someValue)

3、什么是可选绑定?

用if let来将可选变量有值情况下的值赋给另外一个变量,如:

let someValue: Int? = 3
if let value = someValue {
    print(value)
}

4、条件检查函数有哪些?

(1) assert(断言):只在调试环境有效

(2) precondition(先决条件):同时在调试和生产环境有效,但在unchecked编译模式下不进行检查

(3) fatalError(强制检查):不受编译环境和编译模式控制

5、区间运算符有哪些?

闭区间、半开区间和单侧区间

6、闭包表达式的参数有什么要求?

不能拥有默认值(可以是in-out、可变的)

7、什么是尾随闭包?

写在函数圆括号后面的闭包表达式,函数支持将其作为最后一个参数调用

8、什么是闭包的值捕获?

值捕获是指闭包可以在其被定义的上下文中捕获常量或变量,即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包的函数体内引用和修改这些值。

9、什么是逃逸闭包?

逃逸闭包通过@escaping修饰,是指当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。

10、什么是自动闭包?

自动闭包(@autoclosure)是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。

11、恒等运算符和等价运算符的区别是什么?

恒等运算符(===),用于引用类型,判定两个常量或者变量是否引用同一个类实例。等价操作符(==),用于值类型或引用类型,判断哈希值是否相等。

12、常量的使用规则是什么?

值类型的常量,所有属性也成为常量,不可修改;引用类型的常量,仍然可以修改它的可变属性。

13、输入输出参数(in-out)的内存工作方式是怎样的?

输入输出参数采用拷入拷出内存模式,在函数内部使用的是参数的copy,函数结束后,再对参数重新赋值。因此,如果将带有观察器的属性传递给输入输出参数,它的willSet和didSet都会调用。

14、什么是异变方法(mutating)?

异变方法表示可以在方法中修改结构体的属性或者枚举的值,该方法结束时会将改变写回原值中,也可以直接在方法中给隐含的self属性赋予一个全新的实例(即,用于改变实例的属性值或实例本身)

15、下标(subscript)的参数有什么要求?

不能是输入输出的和默认的(可以是可变的)

二、枚举、结构体和类

1、Swift和OC的枚举有什么不同?

(1) 枚举默认情况下没有原始值。可以将原始值类型声明为int,它就会像OC一样为rawValue赋予默认值。除了可以声明为int类型,也可以声明为其他类型,原始值的类型可以没有,但有的时候必须是唯一的。

(2) 枚举还可以有关联值,关联值可以存储任意类型的值,如果需要的话,每个枚举成员的关联值类型可以各不相同。

(3) 原始值和关联值是不同的。原始值是在定义枚举时被预先填充的值。对于一个特定的枚举成员,它的原始值始终不变。关联值是创建一个基于枚举成员的常量或变量时才设置的值,枚举成员的关联值可以变化。

2、结构体比类少了哪些特性?

析构器、继承、类型转换和引用计数。

3、枚举中是否可以有存储属性?

不可以。存储属性只能用于类和结构体,计算属性可以用于所有类型(类、结构体和枚举)。

4、延时加载属性(lazy)是计算属性还是存储属性?为什么它只能声明为变量(var)?

延时加载属性是存储属性,因为它的初始值可能在实例构造完成之后才会得到,而常量必须在构造过程完成之前有初始值,所以只能声明为变量。

5、延时加载属性(lazy,普通的和闭包的)是线程安全的吗?

不是的,当在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。

6、什么是计算属性?

计算属性不直接存储值,而是提供一个getter和一个可选的setter,来间接获取和设置其他属性或变量的值。它可用于枚举、结构体和类。

7、为什么计算属性(包括只读的)只能用var来声明?

因为计算属性的值不是固定的,所以只能用var来声明。而let关键字只用来声明常量属性,表示初始化后再也无法修改的值。

8、属性观察器(willSet/didSet)有哪些限制?

属性观察器不能用于延迟加载存储属性和非重写的计算属性(可以直接通过它的setter来监控),也可以在子类中通过重写属性的方式为继承的属性(常量存储属性和只读计算属性不可以)添加属性观察器。

9、在父类初始化方法调用之前,给子类的属性赋值时会调用子类属性的观察器吗?

不会

10、在父类初始化方法调用之后,在子类构造器中给父类的属性赋值时,会调用父类属性的观察器吗?

11、继承对属性访问性的变化规则

可放大不可缩小,即只读可重写为读写(只需要在重写版本的属性里同时提供getter和setter即可),读写不可重写为只读。

12、类的构造器有哪几类?

(1) 指定构造器:每一个类都必须至少拥有一个,大多数类通过继承了父类中的指定构造器而满足了这个条件。

(2) 便利构造器

13、类构造器的代理规则

(1) 指定构造器必须调用其直接父类的的指定构造器。

(2) 便利构造器必须调用同类中定义的其它构造器。

(3) 便利构造器最后必须调用指定构造器。

14、类实例的构造过程

构造过程分为两段:

(1) 阶段一

① 类的某个指定构造器或便利构造器被调用。

② 完成类的新实例内存的分配,但此时内存还没有被初始化。

③ 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。

④ 指定构造器切换到父类的构造器,对其存储属性完成相同的任务。

⑤ 这个过程沿着类的继承链一直往上执行,直到到达继承链的最顶部。

⑥ 当到达了继承链最顶部,而且继承链的最后一个类已确保所有的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段一完成。

(2) 阶段二

① 从继承链顶部往下,继承链中每个类的指定构造器都有机会进一步自定义实例。构造器此时可以访问self、修改它的属性并调用实例方法等等。

② 最终,继承链中任意的便利构造器有机会自定义实例和使用self。

(3) 安全检测

① 指定构造器必须保证它所在类的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。

② 指定构造器必须在为继承的属性设置新值之前向上代理调用父类构造器。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。

③ 便利构造器必须为任意属性(包括所有同类中定义的)赋新值之前代理调用其它构造器。如果没这么做,便利构造器赋予的新值将被该类的指定构造器所覆盖。

④ 构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用self作为一个值。

15、OC和Swift的子类默认情况下会继承父类的构造器吗?

OC会,Swift不会

16、在子类中写一个与父类便利构造器相匹配的构造器需要加override吗?

不需要,因为Swift默认不继承父类的构造器,它们没有重写关系

17、构造器的继承规则是什么?

(1) 默认情况不会继承

(2) 如果子类引入的新属性都提供了默认值,且没有定义任何指定构造器,则自动继承父类的所有指定构造器。

(3) 如果子类提供了父类的所有指定构造器实现(无论是自动继承的,还是手动定义的),自动继承父类的所有便利构造器。

18、可失败构造器和非可失败构造器的参数签名(参数名+参数类型)可以相同吗?

不可以

19、可以把可失败构造器重写为非可失败的吗?

可以,反之不行。当你用子类的非可失败构造器重写父类的可失败构造器时,向上代理到父类的可失败构造器的唯一方式是对父类的可失败构造器的返回值进行强制解包。

20、什么是必要构造器(required)?

用于表明所有该类的子类都必须实现该构造器(不需要加override),它不仅可以修饰指定构造器,还可以修饰便利构造器。

21、如何使用闭包或函数设置属性的默认值

let someProperty: SomeType = {
    return someValue
}()

(1) 闭包结尾的花括号后面接了一对空的小括号。这用来告诉 Swift 立即执行此闭包。如果你忽略了这对括号,相当于将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。

(2) 如果你使用闭包来初始化属性,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能在闭包里访问其它属性,即使这些属性有默认值。同样,你也不能使用隐式的self属性,或者调用任何实例方法。

22、什么是析构器(deinit)

用于类实例在释放前进行清理工作(析构器会被立即调用),只适用于类类型

23、任何类型(Any)和任意对象(AnyObject)有什么区别

AnyObject是class的对象,Any是任意类型的对象,前者是后者的子集

24、Swift五大访问控制级别

(1) open(开放):模块外(只能用于类和类成员,可被模块外继承和重写)

(2) public(公开):模块外

(3) internal(内部):模块内

(4) fileprivate(文件):文件作用域

(5) private(私有):区域作用域

25、可测试特性(@testable)可以访问哪些访问控制级别的实体?

所有的,便于进行单元测试

26、类型成员的默认访问级别规则

(1) 一个public类型的所有成员的访问级别默认为internal级别,而不是public级别。

(2) 但如果你将类型指定为private或者fileprivate级别,那么该类型的所有成员的默认访问级别也会变成private或者fileprivate级别。

27、元组的访问控制级别规则

元组的访问级别将由元组中访问级别最严格的类型来决定。例如,如果你构建了一个包含两种不同类型的元组,其中一个类型为internal,另一个类型为private,那么这个元组的访问级别为private。

28、函数的访问控制级别规则

由访问级别最严格的参数类型或返回类型的访问级别来决定。但是,如果这种访问级别不符合函数定义所在环境的默认访问级别,那么就需要明确地指定该函数的访问级别。

29、枚举及其成员的访问控制级别规则

(1) 枚举定义中的任何原始值或关联值的类型的访问级别至少不能低于枚举类型的访问级别

(2) 枚举成员的访问级别和该枚举类型相同

30、嵌套类型的访问控制级别规则

嵌套类型的访问级别和包含它的类型的访问级别相同,嵌套类型是public的情况除外。在一个public的类型中定义嵌套类型,那么嵌套类型自动拥有internal的访问级别。如果你想让嵌套类型拥有public访问级别,那么必须显式指定该嵌套类型的访问级别为public。

31、子类的访问控制级别规则

一个子类的访问级别不得高于父类的访问级别,可以通过重写给所继承类的成员提供更高的访问级别。

32、Setter的访问级别可以低于对应的Getter的访问级别吗?

可以,采用该方式可以控制变量、属性或下标的读写权限。

33、结构体默认的成员逐一构造器的默认访问控制级别是什么?

如果结构体中任意存储型属性的访问级别为private,那么该结构体默认的成员逐一构造器的访问级别就是private。否则,这种构造器的访问级别依然是internal。

34、什么情况下,自定义类型会为等价运算符提供默认实现?

(1) 如果是引用类型需要显示实现Equatable协议

(2) 如果是值类型:结构体->只拥有存储属性,并且它们全都遵循Equatable协议的结构体;枚举->没有关联类型;只拥有关联类型,并且它们全都遵循Equatable协议的枚举

三、扩展、协议和泛型

1、扩展(extension)的使用规则

不可以添加存储属性、不能为现有属性添加观察器、不可以重写方法、不可以添加指定构造器

2、协议规定的属性应该用计算属性还是存储属性实现?

都可以,协议只规定属性为可读写性{ get set }的变量(var关键字修饰),不要求是存储的还是计算的,由实现该协议的具体类型去定义(变量、常量或计算属性)。

3、协议规定的类方法用什么修饰?

static

4、协议规定的方法可以提供默认值吗?

不可以

5、协议规定的异变方法(mutating)如何实现?

由类实现不需要mutating修饰;由结构体或枚举实现需要mutating修饰

6、实现协议规定的构造器时,为什么通常将其定义为必要的(required)?

以确保其所有的子类都提供此构造器实现,从而也能遵循该协议。(final类除外)

7、什么是类专属协议?

继承至AnyObject的协议为类专属协议,限制协议只能被类类型采纳

8、什么是OC专属协议?

标记@objc特性的协议为OC专属协议,限制只能被继承自Objective-C类的类或者@objc类遵循

9、如何为协议提供默认实现?

可以通过协议扩展来为协议要求的方法、计算属性提供默认的实现。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。

四、并发

1、简单描述一下Swift语言级的并发支持

Swift语言级的并发支持采用async/await语法,它是构建在线程上的,但并不会直接跟线程进行交互。

2、什么是异步函数?

异步函数是指在返回箭头前使用了async关键字的函数,表示该函数能在执行一部分代码时被挂起而暂停执行(线程让步)。

3、Task.yield函数的作用?

用于显式地插入挂起点,从而让其他任务有机会执行。

4、如何处理异步的可失败函数(async throws)?

(1) do-catch代码块

(2) 转换为异步的Result函数

5、什么是异步序列?

遵循AsyncSequence协议的类型叫异步序列,可通过for-await-in进行遍历。

6、如何实现多个异步方法并行执行?

采用async-let来定义异步常量,然后将它们放在数组中进行await。如:

async let firstPhoto = downloadPhoto(named: photoNames[0]) 
async let secondPhoto = downloadPhoto(named: photoNames[1]) 
async let thirdPhoto = downloadPhoto(named: photoNames[2]) 
let photos = await [firstPhoto, secondPhoto, thirdPhoto] show(photos)

7、什么是Swift语言级中的任务?

(1) 异步方法或者async-let定义的常量被称为一个任务

(2) 任务具有继承特性,比如放在同一个任务组中的任务拥有相同的父任务,而每个任务也可以拥有自己的子任务。

(3) 父子任务之间的关系:

① 父任务需要等待所有子任务的完成

② 当设置子任务更高的优先级时,父任务的优先级会随之提升

③ 取消父任务时,其所有子任务也会随之取消

④ 父任务的本地值会自动且有效地传播到子任务中

8、如何执行Swift语言级中的任务组?

withTaskGroup或withThrowingTaskGroup可以执行任务组

9、正在工作的任务被取消时,可能会出现哪种情况?

(1) 抛出错误,比如CancellationError

(2) 返回nil或空集合

(3) 返回部分完成的工作结果

10、任务取消涉及的接口有哪些?

Task.checkCancellation、Task.isCancelled、TaskGroup.addTaskUnlessCancelled和Task.withTaskCancellationHandler等

11、什么是Swift语言级中的非结构化任务?

它没有父任务,由开发者进行自行管理,从而拥有完整的灵活性。创建方法:async或asyncDetached。

12、什么是行为体?

行为体(actor)可以看作是一种特殊的类类型。它是一种隔离技术,在同一时间,它只允许一个任务访问它的可变属性,因此它是线程安全的。

13、什么是并发域,什么是可发送类型?

并发域是指可线程安全访问的可变数据,如行为体实例中的可变属性。可发送类型(Sendable)是指可以在并发域之间进行共享的类型。

14、可发送类型的要求是什么?

(1) 如果是枚举类型要遵循Sendable协议,它要求其关联值都是可发送的。

(2) 如果是结构体类型要遵循Sendable协议,它要求其存储属性都是可发送的或者其只能包含只读属性。

(3) 如果是类类型要遵循Sendable协议,它要求其必须标注@MainActord或者其在特定线程或队列中只能被串行访问其属性。

五、其他

1、defer语句的作用?

defer语句用于在代码离开当前代码块前执行一个语句集合,通常用于清理工作。

2、闭包、协议方法和下标的参数要求是怎样的?

(1) 闭包和协议方法:不可以有默认值

(2) 下标:不可以有默认值和不能是输入输出的

3、Swift中出现内存访问冲突的主要场景是什么?

输入输出参数(in-out)或者异变方法(mutating)

4、Swift中解决循环引用的方法有哪些?

(1) 弱引用(weak)

弱引用不会对其引用的实例保持强引用,声明属性或者变量时,在前面加上weak关键字表明这是一个弱引用。ARC会在引用的实例被销毁后自动将其弱引用赋值为nil。并且因为弱引用需要在运行时允许被赋值为nil,所以它们会被定义为可选类型变量,而不是常量。当ARC设置弱引用为nil时,属性观察不会被触发。

(2) 无主引用(unowned)

和弱引用类似,无主引用不会牢牢保持住引用的实例。和弱引用不同的是,无主引用在其他实例有相同或者更长的生命周期时使用。你可以在声明属性或者变量时,在前面加上关键字unowned表示这是一个无主引用。无主引用通常都被期望拥有值。不过ARC无法在实例被销毁后将无主引用设为nil,因为非可选类型的变量不允许被赋值为nil。

5、弱引用和无主引用的使用场景

(1) 两个类相互引用的属性都允许为nil,生命周期短的采用弱引用。【房子的业主(人)】如一个房子可能拥有一个业主,一个人也可能拥有一个房子,房子的业主属性采用弱引用(先有人再有房子,业主的生命周期更短)。

(2) 两个类相互引用的属性,一个允许为nil、另一个不允许,不允许的采用无主引用。【信用卡的客户(人)】如一个人可能拥有一张信用卡,一张信用卡必须拥有一个客户,信用卡的客户属性采用无主引用。

(3) 两个类相互引用的属性都不允许为nil,需要在构造器中调用另一个类构造器(传递自身self)进行赋值的属性采用隐式解包可选属性,另一个采用无主引用。【国家的首都(城市)】如每个国家必须有首都,每个城市必须属于一个国家,国家的首都属性采用隐式解包可选属性,城市的国家属性采用无主引用。

6、循环引用产生的两种场景

(1) 两个类的属性相互引用

(2) 在类的闭包属性中使用类实例(闭包捕获列表:闭包“捕获”self,产生强引用)

7、闭包捕获列表如何解决循环引用问题?

(1) 在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。(即,闭包通过定义捕获列表中的参数为弱引用或无主引用来解决循环引用问题)

(2) 在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用。相反的,在被捕获的引用可能会变为nil时,将闭包内的捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为nil。这使我们可以在闭包体内检查它们是否存在。原则:如果被捕获的引用绝对不会变为nil,应该用无主引用,而不是弱引用。

8、属性包装器(@propertyWrapper)有什么作用?

属性包装器用于在属性的定义和存储之间添加一个分隔层

9、什么是不透明类型?

(1) 在函数或属性返回一个协议时,在前面加上some关键字。从而要求它们返回一个具体类型,但不对调用者暴露。

(2) 返回不透明类型可以让编译器知道具体的返回类型,从而进行类型检查的同时,也能提高性能。

(3) 相比直接返回协议,实际上就是让这个函数或属性支持多态,编译器并不知道返回的具体类型,需要通过动态派发的方式来对协议调用方法或属性,从而降低性能。

10、什么是包装协议类型?

(1) 用于将协议类型封装为一种具体的类型,便于在程序中灵活处理不确定的类型实现。

(2) 包装协议类型有两种实现方式:

① 用Any/AnyObject包装任意类型或任意类类型,需要手动进行类型转换来使用。

② 用Existential类型包装某个协议类型,无需进行类型转换而可直接调用协议方法。

(3) 包装协议类型还可以实现类型擦除,用于包装含有associatedtype或Self要求的协议,从而绕过它作为参数时的语法限制。

11、Swift中的宏分为哪两类?

(1) 独立宏:以#开头,如#function和#warning等,它用于产生一个值。(产生值)

(2) 附加宏:以@开头,如@OptionSet等,它用于向添加该修饰的声明添加代码。(附加代码)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tospery

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值