1.Swift与OC相比有什么优势?
Swift是强类型语言,注重值类型,有类型推断,安全性高
Swift的语法更简洁,使用起来方便,支持函数式编程
Swift拥有更强大的特性,它有元组类型、支持可选类型(optional)、支持运算符重载、支持泛型、支持静态/动态派发,协议不仅可以被类实现还可以被struct和enum实现
Swift支持命名空间、函数支持默认参数
Swift的错误处理机制更完善
oc的优点、运行时
2.Swift中Struct与Class的区别?
主要区别
1.struct是值类型,class是引用类型。
2.二者的本质区别:
struct是深拷贝,拷贝的是内容;class是浅拷贝,拷贝的是指针。
3.property的初始化不同:
class 在初始化时不能直接把 property 放在 默认的constructor 的参数里,而是需要自己创建一个带参数的constructor;而struct可以,把属性放在默认的constructor 的参数里。
4.变量赋值方式不同:
struct是值拷贝;class是引用拷贝
5.immutable变量:
swift的可变内容和不可变内容用var和let来甄别,如果初始为let的变量再去修改会发生编译错误。struct遵循这一特性;class不存在这样的问题
6.mutating function:
struct 和 class 的差別是 struct 的 function 要去改变 property 的值的时候要加上 mutating,而 class 不用。
7.继承:
struct不可以继承,class可以继承
8.struct比class更轻量:
struct分配在栈中,class分配在堆中
9.总结:
static 可以在类、结构体、或者枚举中使用。而 class 只能在类中使用。
static 可以修饰存储属性,static 修饰的存储属性称为静态变量(常量)。而 class 不能修饰存储属性。
static 修饰的计算属性不能被重写。而 class 修饰的可以被重写。
static 修饰的静态方法不能被重写。而 class 修饰的类方法可以被重写。
class 修饰的计算属性被重写时,可以使用 static 让其变为静态属性。
class 修饰的类方法被重写时,可以使用 static 让方法变为静态方法
class关键字指定的类方法可以被子类重写,但是用static关键字指定的类方法是不能被子类重写的
使用场景
在Swift中,结构体(Struct)和类(Class)都可以用来定义属性和方法来创建复杂的数据类型。但是,根据特定的场景和需求,选择使用结构体还是类有以下几个考虑点:
1、 当你需要一个轻量级的数据载体,并且数据的拷贝或值传递是可预期的行为时,应该优先考虑使用结构体。结构体实例在代码中传递时总是被拷贝,这有利于保证数据的不可变性和安全性。
2、 如果你要表示的数据结构需要利用继承来避免代码重复,或者需要在运行时检查和解释类型的实例,那么应该使用类。类支持继承,多态和类型转换,而结构体不支持。
3、 对于小型的数据结构,结构体由于其值类型的特性,在性能上通常优于类。在数组和字典这样的集合类型中使用结构体可以获得更好的性能。
4、 当你的数据结构需要通过网络传输或者需要与外部系统进行交互时,使用遵循Codable协议的结构体可以简化序列化和反序列化的过程。
5、 使用结构体可以避免内存泄漏和循环引用问题,这是因为结构体作为值类型,不会形成引用计数。
总的来说,如果数据结构比较简单,不需要用到继承,且期望通过值传递来确保数据安全,那么结构体是更好的选择。而对于需要利用面向对象特性的复杂数据模型,类则是更合适的选择。
3.Swift是面向对象还是函数式编程语言?
Swift 既是面向对象的,又是函数式的编程语言。
说 Swift 是 Object-oriented,是因为 Swift 支持类的封装、继承、和多态,从这点上来看与 Java 这类纯面向对象的语言几乎毫无差别。
说 Swift 是函数式编程语言,是因为 Swift 支持 map, reduce, filter, flatmap 这类去除中间状态、数学函数式的方法,更加强调运算结果而不是中间过程。
4.Swift为什么将String,Array,Dictionary设计成值类型?
主要围绕安全性、性能优化和可预测性。
安全性
- 避免意外的共享状态:值类型在传递时会被复制,因此每个变量持有的是自己的副本。这样,当你修改一个变量时,不会意外地影响到其他变量。而引用类型(类)则不同,多个变量可能指向同一个实例,修改其中一个就会影响所有引用该实例的变量。这种特性在并发编程中尤其容易引发问题,比如多个线程同时修改同一个数组.
- 线程安全:由于值类型在修改时是独立的,所以在单线程中修改值类型不需要考虑其他线程同时修改的问题(因为每个线程都有自己的副本)。当然,如果多个线程共享同一个变量,仍然需要同步,但值类型的设计减少了意外共享的可能性.
性能优化
- 写时复制(Copy-on-Write, COW):虽然值类型在赋值或传递时默认会复制,但Swift通过写时复制技术优化了像String、Array和Dictionary这样的大数据结构的性能。这意味着在复制时并不会立即复制数据,而是多个变量共享同一份数据,直到其中一个变量需要修改数据时,才会进行实际的复制。这样,在只读操作下,复制的开销几乎为零,只有在需要修改时才进行复制,从而平衡了安全性和性能.
- 栈上分配:值类型通常存储在栈上(除非被捕获到闭包中或者是一个类的属性等特殊情况),而栈的分配和释放比堆(引用类型通常分配在堆上)要快得多,因为栈的操作只是移动栈指针,而堆分配需要寻找合适的内存块并管理引用计数
可预测性
- 值语义:值类型的行为更符合我们对“值”的直觉。例如,当你将一个整数赋值给另一个变量,然后修改其中一个,另一个不会改变。同样,将数组赋值给另一个变量,修改其中一个数组不会影响另一个。这种一致性使得代码更容易理解和推理。
- 函数式编程的支持:值类型天然支持不可变性(通过let声明),并且可以避免副作用。函数式编程风格鼓励使用不可变数据,这有助于减少程序中的错误,并使代码更易于测试和维护。
5.Swift中mutating关键字的作用?
mutating用于函数的最前面,用于告诉编译器这个方法会改变自身.
swift增强了结构体和枚举的使用,结构体和枚举也可以有构造方法和实例方法,但结构体和枚举是值类型,如果我们要在实例方法中修改当前类型的实例属性的值或当前对象实例的值,必须在func前面添加mutating关键字,表示当前方法将会将会修改它相关联的对象实例的实例属性值.
协议中,当在结构体或者枚举实现协议方法时,若对自身属性作修改,需要将协议的方法声明为mutating,对类无影响.
在扩展中,同样若对自身属性作修改,需要将方法声明为mutating
struct Person { var name: String { mutating get { return store } } }
6.请简述Swift中throws 和 rethrows的区别?
throws:标记函数可能会抛出异常,调用函数的地方需要处理可能抛出的异常。
enum DivideError: Error { case EqualZeroError; } func divide(_ a: Double, _ b: Double) throws -> Double { guard b != Double(0) else { throw DivideError.EqualZeroError } return a / b } func split(pieces: Int) throws -> Double { return try divide(1, Double(pieces)) }
rethrows:函数本身不会抛出异常,如果作为参数的闭包抛出了异常,则会继续往上抛出异常,可以理解为传递throw。
func processNumber(a: Double, b: Double, function: (Double, Double) throws -> Double) rethrows -> Double { return try function(a, b) }
当闭包参数会抛出异常时 使用throws
同时外部方法体返回结果需要 rethrows 异常
rethrows 可以用 throws 替换, 反过来不行
7.讲一讲字符串类型 String 和 NSString 之间有什么区别?
1>.String是值类型(拷贝赋值需要值拷贝),NSString是引用类型(传递指针),性能开销上有差异(常见的比如字符串赋值操作、频繁的创建或销毁字符串)
2>.NSString有特定的api,比如常用获取指定字符串的Range
3>.String是可变的,NSString是不可变字符串
8.平时用过哪些Swift的高阶函数?简述一下map、flatMap、compactMap的区别。
高阶函数是一种可以接受其他函数作为参数或者返回一个函数作为结果的函数。Swift的标准库中包含了许多高阶函数,它们对于操作集合类型(如数组和字典)特别有用。高阶函数通过提供一种更简洁和更声明式的方式来处理数据,可以显著提高代码的可读性和简洁性。以下是 Swift中几个常见的高阶函数例子:
1、 map:接受一个闭包,将集合中的每个元素通过闭包中定义的方式转换,并返回一个新的数组。
//map 是Array类的一个方法,我们可以使用它来对数组的每个元素进行转换 let intArray = [1, 3, 5] let stringArr = intArray.map { return "\($0)" } // ["1", "3", "5"]
2、 filter:接受一个闭包,用闭包定义的方式筛选集合中的元素,返回一个包含符合条件元素的新数组。
//filter 用于选择数组元素中满足某种条件的元素 let filterArr = intArray.filter { return $0 > 1 } //[3, 5]
3、 reduce:接受一个初始值和一个闭包,通过闭包定义的方式将集合中的元素合并成一个单一的值。
//reduce 把数组元素组合计算为一个值 let result = intArray.reduce(0) { return $0 + $1 }
4、map:作用于数组中的每个元素,闭包返回一个变换后的元素,接着将所有这些变换后的元素组成一个新的数组。
flatMap:功能和map类似,区别在于flatMap可以去nil,能够将可选类型(optional)转换为非可选类型(non-optionals),把数组中存有数组的数组 一同打开变成一个新的数组(数组降维)
1) flatMap的第一个作用和map一样,对一个集合类型的所有元素做一个映射操作,但是可以过滤为nil的情况 例如: let array = [1,2,5,6,7,nil] let array_map = array.map { $0 } //[Optional(1), Optional(2), Optional(5), Optional(6), Optional(7), nil] let array_flatmap = array_map.flatMap { $0 } //[1, 2, 5, 6, 7] 2) 第二种情况可以进行“降维”操作 let array = [["1", "2"],["3", "4"]] let array_map = array.map { $0 } //[["1", "2"], ["3", "4"]] let array_flatmap = array_map.flatMap { $0 } //["1", "2", "3", "4"]
这些高阶函数提供了强大的工具,使得在Swift中处理序列和集合数据更加灵活和高效。
9.Swift协议中能使用泛型作为参数吗?
在Swift的协议中不能直接使用泛型作为参数,但是我们可以通过Associated Type来实现泛型作为函数参数的功能
10.Swift中如何阻止方法,属性,下标被子类改写?
1.增加关键字
final
class Parent { // 禁止重写方法 final func criticalMethod() { print("Parent's logic") } // 禁止重写属性 final var immutableValue: Int { return 42 } // 禁止重写下标 final subscript(index: Int) -> Int { return index * 2 } } class Child: Parent { // 以下尝试重写会编译报错: // override func criticalMethod() { ... } // override var immutableValue: Int { ... } // override subscript(index: Int) -> Int { ... } }
禁止整个类被继承
final class UltimateClass { // 此类不能被继承 // ... }
2.增加
static关键字(
隐式final
)
作用:用于值类型(
struct
/enum
)或类的静态成员,隐式不可重写。特点:1.在类中,
static
等同于final class;2.
子类无法重写,但可以定义同名静态成员(独立存在).struct MathUtils { static func sqrt(_ x: Double) -> Double { ... } // 不可重写 } class NetworkManager { static let timeout = 10.0 // 隐式 final,不可重写 }
11.Swift中 inout的作用?
swift中需要对函数中参数进行修改,需要用到inout 关键字,调用函数时加&
通俗的讲,就是使用inout关键字修饰的值,在接下来的方法中可以修改
12.dynamic framework 和static framework的区别是什么?
Swift中的动态库(Dynamic Libraries)和静态库(Static Libraries)都是代码复用的方式,它们允许开发者将代码封装起来供其他项目引用,但在链接和分发应用时有着本质的区别:
1、 静态库:在编译时,静态库的代码会被整合到最终的可执行文件中。每个使用静态库的应用都会有一份库的拷贝,这意味着静态库的更新需要重新编译应用。
2、 动态库:与静态库不同,动态库在应用运行时被加载。这意味着多个应用可以共享同一份动态库的拷贝,减少了应用的体积。当动态库更新时,不需要重新编译使用它的应用,只需替换动态库文件即可。
3、 内存占用:使用静态库会增加应用的总体积,因为库的代码被整合进了应用。而动态库虽然可以减少单个应用的体积,但如果有多个应用同时运行并使用同一动态库,它们将共享这份库的内存拷贝。
4、 兼容性和版本控制:动态库更易于管理和更新,因为它们是独立于应用外的。但这也带来了版本兼容性问题,需要确保应用与动态库的兼容性。
5、 安全性和隐私:静态库被编译进应用中,更不易被替换或篡改。而动态库由于是在运行时加载,可能面临被替换的风险,但也使得安全更新更加容易实施。
根据应用的需求和分发策略,开发者可以选择使用静态库或动态库,它们各有优势和劣势。
13.在Swift中如何防止父类方法被重写?如何让子类必须重写父类指定的方法?
在Swift中防止父类方法被重写:可以在父类方法前加上
final
关键字。让子类必须重写父类指定方法:可以在父类中定义
required
方法
14.简述Swift的静态派发和动态派发?
静态派发
很显然静态派发是一种更高效的方法,因为静态派发免去了查表操作。
不过静态派发是有条件的,方法内部的代码必须对编译器透明,并且在运行时不能被更改,这样编译器才能帮助我们。
Swift 中的值类型不能被继承,也就是说值类型的方法实现不能被修改或者被复写,因此值类型的方法满足静态派发的要求。
默认静态派发,如果需要满足动态派发,需要 dymanic修饰
动态派发
动态派发是一种运行时决定方法调用的机制。动态派发是指在
运行时
确定要调用的方法或属性,而不是在编译时确定。这使得 Swift 能够支持一些面向对象编程的特性,例如继承、多态和封装。在Swift中,动态派发主要通过虚拟派发表实现,这涉及到引用类型如类(class)。动态派发允许Swift在运行时选择响应消息的最终实现,这为方法重写和多态提供了基础。1、 当你调用一个类的方法时,Swift运行时会查找这个类的虚拟派发表,找到对应方法的实际实现地址,然后跳转到这个地址执行方法。
2、 由于动态派发的存在,Swift可以在运行时而非编译时决定调用哪个方法的实现,这增加了程序的灵活性,但也可能略微降低性能。
3、 Swift中默认情况下类的方法是动态派发的。然而,通过使用final关键字标记方法或类,可以阻止方法被重写,从而允许编译器优化调用,采用更快的静态派发。
4. 在 Swift 中,所有的类都默认继承自
NSObject
类,当我们在子类中重写父类的方法时,编译器会为我们生成一个方法表,这个表中保存了方法名和对应的实现。在运行时,当我们调用一个方法时,会根据对象的实际类型在方法表中查找对应的实现。动态派发是面向对象编程中的一个关键概念,它使得子类可以定制或改变继承而来的行为。
15.Swift中的协议和继承有什么区别?
协议定义了一个蓝图,规定了遵循协议的类型必须实现的方法和属性,但不提供这些方法和属性的具体实现。协议可以被枚举、结构体和类遵循。
继承允许一个类继承另一个类的特性,如方法和属性。子类可以重写父类中的方法和属性来提供特定的实现。继承仅限于类之间的关系。
协议支持多重继承,即一个类型可以遵循多个协议,而继承则是单一继承,一个类只能继承自另 一个类。协议适用于定义一组应该被不同类型实现的接口,而继承更多的是为了代码的复用和扩展已有的类行为。
16.Swift 中的错误处理机制如何工作?
Swift 中的错误处理机制允许程序在运行时检测和响应错误条件。Swift 提供了强大的错误处理模型,包括抛出、捕捉和传递错误。
1、 定义错误类型:首先,通过实现Error协议来定义可能发生的错误类型。这些错误类型通常是枚举,用来表示不同的错误情况。
2、 抛出错误:使用throw关键字来抛出一个错误。这通常发生在函数内部,当函数遇到无法解决的问题时,它会抛出一个错误,以表示失败。
3、 标记抛出错误的函数:在函数声明中使用throws关键字来标记这个函数可能会抛出错误。这意味着调用这个函数的代码需要处理这些错误。
4、 捕捉和处理错误:使用do-catch语句来捕捉和处理错误。在do代码块中调用可能抛出错误的代码。如果在do块中抛出了错误,控制流会转移到catch块,让你有机会响应和处理错误。
5、 使用try、try?和try!来调用抛出错误的函数,分别表示:检查错误并使用do-catch处理、将结果转换为可选值(如果发生错误则结果为nil)、断言调用不会抛出错误(如果实际抛出错误,则会触发运行时错误)。
这种错误处理机制使得Swift代码能够优雅地处理错误和异常情况,提高了程序的健壮性和可维护性。
17.简述Swift的Copy On Write机制?
copy on write, 写时复制,简称COW,它通过浅拷贝(shallow copy)只复制引用而避免复制值;当的确需要进行写入操作时,首先进行值拷贝,在对拷贝后的值执行写入操作,这样减少了无谓的复制耗时。
写时复制:每当数组被改变,它首先检查它对存储缓冲区 的引用是否是唯一的,或者说,检查数组本身是不是这块缓冲区的唯一拥有者。如果是,那么 缓冲区可以进行原地变更;也不会有复制被进行。不过,如果缓冲区有一个以上的持有者 (如本 例中),那么数组就需要先进行复制,然后对复制的值进行变化,而保持其他的持有者不受影响。
在Swift提供一个函数isKnownUniquelyReferenced,能检查一个类的实例是不是唯一的引用,如果是,说明实例没有被共享
eg: isKnownUniquelyReferenced(&object)
//arrayA是一个数组,为值类型 let arrayA = [1,2,3]; //arrayB这个时候与arrayA在内存中是同一个东西,内存中并没有生成新的数组 let arrayB = arrayA //arrayB被修改了,此时arrayB在内存中变成一个新的数组,而不是原来的arrayA arrayB.append(4) //总结:上面的代码可以看出,复制的数组和原数组共享同一个地址,直到其中之一发生变化.这样设计使得值类型可以多次复制而无需耗费多余内存,只有变化的时候才会增加开销.因此内存的使用更加高效.
18.Swift中autoclosure的作用?
自动闭包,将参数自动封装为闭包参数
19.Swift中closure与OC中block的区别?
1、closure是匿名函数、block是一个结构体对象
2、都能捕获变量
3、closure通过逃逸闭包来在block内部修改变量,block 通过 __block 修饰符
20.Swift 中属性观察器有哪些,它们各自有什么用途?
Swift中的属性观察器包括willSet和didSet,它们用于监控属性值的变化,从而可以在属性的值即将更改和已经更改时执行自定义操作。这两个观察器的用途如下:
1、 willSet观察器在属性的值即将更改之前被调用。它使你可以读取即将被设定的新值,并可以执行一些自定义代码。willSet观察器可以带有一个默认参数名newValue,如果你不指定参数名,可以直接使用newValue来访问新的属性值。
2、 didSet观察器在属性的值已经更改后立即被调用。它使你可以读取已经被更改的旧值,并可以基于新值执行一些自定义代码。didSet观察器可以带有一个默认参数名oldValue,如果你不指定参数名,可以直接使用oldValue来访问旧的属性值。
21.Swift 中值类型和引用类型有什么区别?
在Swift中,基本的分类型为值类型和引用类型,它们之间的主要区别在于如何存储和传递。
1、 值类型:每次赋值或传递时,都会进行拷贝操作,创建一个新的独立实例。基本数据类型(如Int、String、Array等)和结构体(struct)、枚举(enum)是值类型。因此对一个变量操作不可能影响另一个变量。
2、 引用类型:赋值或传递时,不拷贝实例本身而是其引用或指针。因此,多个变量可以引用同一个实例。类(class)是引用类型。因此对一个变量操作可能影响另一个变量所引用的对象。
值类型的特性使得它在多线程环境下工作时更加安全,因为每个实例都有自己的独立拷贝,避免了意外修改。而引用类型允许多个引用或者变量共享同一个实例,适合执行更复杂的数据结构和逻辑。
22.Swift 中的编译时多态性和运行时多态性有何区别?
编译时多态性和运行时多态性是面向对象编程中的两种多态性形式,它们在Swift语言中也有所体现:
1、 编译时多态性(也称为静态多态性)主要通过方法重载和泛型实现。在编译时,编译器根据调用的参数类型和数量决定使用哪个具体的方法或函数。泛型也是编译时多态性的一个例子,它允许函数或类型与任何数据类型一起工作,类型检查发生在编译时。
2、 运行时多态性(也称为动态多态性)在Swift中主要通过继承和协议来实现。它允许在运行时决定调用哪个对象的哪个方法,这依赖于对象的实际类型。在Swift中,类的继承关系和协议的实现提供了运行时多态性,使得同一接口可以有多个实现,具体使用哪个实现在运行时通过动态派发来决定。
编译时多态性提供了更好的性能,因为方法调用的解析在编译时完成;而运行时多态性提供了更高的灵活性,允许更加动态的行为,但可能会略微牺牲性能。
23.什么时候使用 @objc
原理:
OC 是基于运行时,遵循了 KVC 和动态派发,而 Swift 是静态语言,为了追求性能,在编译时就已经确定,而不需要在运行时的,在 Swift 类型文件中,为了解决这个问题,需要暴露给 OC 使用的任何地方(类,属性,方法等)的生命前面加上 @objc 修饰符
如果用 Swift 写的 class 是继承 NSObject 的话, Swift 会默认自动为所有非 private 的类和成员加上@objc
使用地方:
与OC 的交互部分
KOV 监听、动态方法查找等都需要
协议可选方法等
24.如何自定义下标获取
extension Demo {
subscript(index: Int) -> Int {
get {
// 返回一个适当的 Int 类型的值
}
set(newValue) {
// 执行适当的赋值操作
}
}
}
25.定义静态方法时关键字 static 和 class 有什么区别
非class类型 一般 统一用 static 例如 枚举 结构体
protocol中 使用 static ,实现协议的 枚举 结构体 用 static
class 中使用 class static 都可以
26.Swift 中的闭包捕获列表是什么,它如何帮助管理内存?
闭包捕获列表在Swift中用于解决闭包内部捕获外部变量可能导致的循环引用问题,进而帮助管理内存。当闭包捕获到一个类实例的强引用时,容易形成一个循环强引用,导致内存泄漏。捕获列表定义了闭包内捕获的外部变量或常量如何被捕获,可以通过指定为弱引用(weak)或无主引用(unowned)来避免循环强引用。
1、 使用捕获列表可以明确闭包内捕获的引用类型,通过将类实例的引用声明为weak或unowned,这表明闭包内部的引用不会增加实例的引用计数。
2、 弱引用(weak)适用于引用可能会变为nil的情况。使用弱引用时,变量类型必须是可选的。
3、 无主引用(unowned)适用于引用保证在闭包和实例之间引用时不会被销毁的情况。无主引用不会把引用的实例变为可选类型。
4、 在定义闭包时,捕获列表被放在闭包的参数列表之前,用方括号[]包围,里面列出需要捕获的变量或常量前面加上weak或unowned标记。
通过使用捕获列表,开发者可以更安全地在闭包中引用外部变量或常量,有效地管理内存,防止内存泄漏的发生。
27.一个函数的参数类型只要是数字(Int、Float)都可以,要怎么表示。
Int、Float 都有一个协议
func myMethod<T>(_ value: T) where T: Numeric {
print(value + 1)
}
或者 ExpressibleByIntegerLiteral 协议也行
28.为什么数组索引越界会崩溃,而字典用下标取值时 key 没有对应值的话返回的是 nil 不会崩溃。
struct Array<Element> {
subscript(index: Int) -> Element
}
struct Dictionary<Key: Hashable, Value> {
subscript(key: Key) -> Value?
}
29.swift中struct作为数据模型的优缺点?
优点:
1.安全性:因为 Struct 是用值类型传递的,它们没有引用计数。
2.内存:由于他们没有引用数,他们不会因为循环引用导致内存泄漏。
3.速度:值类型通常来说是以栈的形式分配的,而不是用堆。因此他们比 Class 要快很多!
4.拷贝:Objective-C 里拷贝一个对象,你必须选用正确的拷贝类型(深拷贝、浅拷贝),而值类型的拷贝则非常轻松!
5.线程安全:值类型是自动线程安全的。无论你从哪个线程去访问你的 Struct ,都非常简单。
缺点:
1.Objective-C与swift混合开发:OC调用的swift代码必须继承于NSObject。
2.继承:struct不能相互继承。
3.NSUserDefaults:Struct 不能被序列化成 NSData 对象
30.Swift 下的 KVO , KVC
KVO, KVC 都是Objective-C 运行时的特性, Swift 是不具有的, 想要使用, 必须要继承 NSObject, 自然, 继承都没有的结构体也是没有 KVO, KVC 的.
KVC:Swift 下的 KVC 用起来很简单, 只要继承 NSObject 就行了
KVO:KVO 就稍微麻烦一些了,由于 Swift 为了效率, 默认禁用了动态派发, 因此想用 Swift 来实现 KVO, 除了继承NSObject,还需要将想要观测的对象标记为 dynamic.
31.进程与线程的区别,什么可以共用什么不能共用
进程是资源分配的最小单位
线程是资源调度的最小单位
一个进程可以包含多个线程
可以共用内存空间、独立的全局变量和静态变量等
但是不能共用栈空间以及相关的局部变量。
32.堆与栈的区别
空间
栈空间小,由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等
堆空间大,一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表
缓存方式
栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放
堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定
数据结构
栈是一种先进后出的数据结构。比如入栈,最后入栈的界面在最顶层显示
堆可以被看成是一棵树,如:堆排序
33.虚拟地址怎么映射到物理地址
操作系统会根据地址映射算法,把虚拟地址转换成物理地址。通常情况下,虚拟地址会首先被分割成不同的段,通过段号和偏移地址来确定虚拟地址,然后在内存中找到相应的地址,最终将其映射到物理地址。
34.OC和Swift枚举的区别?
功能性
Objective-C:枚举基本上是带有命名的整数值,功能较为简单。
Swift:枚举支持关联值、原始值、方法和计算属性,功能强大,灵活性高。
类型安全
Objective-C:使用
NS_ENUM
和NS_OPTIONS
宏可以提高类型安全性,但仍然有限。Swift:枚举是第一类类型,提供强类型检查,减少错误。
模式匹配
Objective-C:没有原生的模式匹配功能。
Swift:通过
switch
语句和模式匹配,可以非常简洁地处理不同的枚举情况。定义方法和属性
Objective-C:枚举不能定义方法和属性。
Swift:枚举可以定义实例方法、类型方法和计算属性。
35.Swift和OC中的 protocol 有什么不同
Swift 和 OC 中的
protocol
在语法和功能上有许多相似之处,但 Swift 提供了更多高级特性,如关联类型、泛型、协议扩展和更强大的类型系统,使得 Swift 的协议编程更加灵活和强大。理解这些区别有助于在两种语言中编写更加健壮和可维护的代码。可选方法
在 OC 中,协议可以定义可选方法和必需方法。可选方法通过
@optional
关键字标记在 Swift 中,协议默认只有必需方法。如果需要定义可选方法,协议必须继承自
NSObjectProtocol
,并且可选方法需要使用@objc
标记属性和方法要求
在 OC 中,协议可以定义方法和属性要求,但属性通常通过方法来表示
在 Swift 中,协议可以直接定义属性要求,并且可以指定属性是只读还是读写
关联类型和泛型
Objective-C 中的协议不支持关联类型和泛型。协议只能定义方法和属性的要求,而不能对类型进行参数化
Swift 中的协议支持关联类型和泛型,这使得协议更加灵活和强大。关联类型使用
associatedtype
关键字定义扩展
在 Objective-C 中,无法直接为协议添加扩展实现,只能通过类别(category)为类添加方法
Swift 支持为协议添加默认实现(扩展),使得协议更强大和灵活
36.Swift Any
、AnyObject
和Generics
区别?
特性 | Any | AnyObject | 泛型(Generics) |
类型范围 | 所有类型(值/引用) | 仅引用类型(Class) | 编译时指定的任意类型 |
类型安全 | 需运行时转换 | 需运行时转换 | 编译时保证 |
性能 | 有运行时开销 | 有运行时开销 | 无额外开销(静态派发) |
主要用途 | 动态类型存储 | Objective-C 交互 | 类型安全的抽象代码 |
典型场景 | JSON 解析、混合数组 | 代理模式、弱引用集合 | 集合、算法、网络层封装 |
如何选择?
优先使用泛型:
需要类型安全、高性能和代码复用(如
Array<T>
,Result<T, Error>
)。谨慎使用
Any
/AnyObject
:
仅在必须处理未知类型或与 Objective-C 交互时使用(如
UserDefaults
存储混合数据)。避免滥用
Any
:
失去编译时检查,容易引发运行时崩溃(如错误强制转换
as!
)。
37.try、try?与try!
try:手动捕捉异常
try?:系统帮我们处理,出现异常返回nil;没有异常返回对应的对象
try!:直接告诉系统,该方法没有异常。如果出现异常程序会crash
38.Swift中访问控制权限open, public, internal, fileprivate, private 的区别?
访问级别 | 描述 | 同一模块内可见 | 不同模块内可见 | 示例 |
open | 最高访问级别,允许实体被定义模块外的其他模块访问,也可以被继承和重写。 | V | V | |
public | 权限仅次于 open,和 open 唯一的区别是: 不允许其他模块进行继承、重写 | V | X | |
internal(默认) | 默认访问级别,允许实体在定义模块内部任何地方访问,但不能被定义模块外的其他模块访问.可以继承和重写 | V | X | |
fileprivate | 修饰的对象只允许在当前的文件中访问 | V | X | |
private | 最低级别访问权限,只允许在定义的作用域内访问 | V | X |
39.Swift中的方法调用有哪些形式?
在 Swift 中,方法调用(Method Dispatch)的形式主要有 静态派发(Static Dispatch) 和 动态派发(Dynamic Dispatch) 两种,具体表现取决于方法的定义方式(如值类型/引用类型、
final
/dynamic
修饰等)
1.静态派发(Static Dispatch)
特点:
在 编译时 确定调用的具体方法,性能更高(无运行时查找开销)。
适用于:值类型(
struct
/enum
)、final
类/方法、static
方法。示例:
struct Math { // 结构体方法:静态派发 func add(_ a: Int, _ b: Int) -> Int { return a + b } } final class Calculator { // final 类方法:静态派发 func multiply(_ a: Int, _ b: Int) -> Int { return a * b } } let math = Math() math.add(2, 3) // 编译时直接确定调用 let calc = Calculator() calc.multiply(2, 3) // 编译时直接确定调用
2. 动态派发(Dynamic Dispatch)
特点:
在 运行时 通过虚表(vtable)或消息机制(objc_msgSend)查找方法,灵活性高但稍慢。
适用于:类的非
final
方法、协议方法(存在协议见证表)。2.1 虚表派发(VTable Dispatch)
适用场景:Swift 类的非
final
方法。机制:每个类维护一个虚表(vtable),存储方法实现的指针。
class Animal { func makeSound() { print("Some sound") } // 动态派发(虚表) } class Dog: Animal { override func makeSound() { print("Bark!") } // 运行时决定调用 } let animal: Animal = Dog() animal.makeSound() // 输出 "Bark!"(运行时通过虚表查找)
2.2 消息派发(Message Dispatch)
适用场景:标记为
@objc
或dynamic
的方法(兼容 Objective-C 运行时)。机制:通过
objc_msgSend
动态查找方法实现(类似 Objective-C)。class Cat: NSObject { @objc func jump() { print("Jump!") } // 消息派发 } let cat = Cat() cat.jump() // 通过 Objective-C 运行时消息机制调用
3. 协议派发(Protocol Witness Dispatch)
特点:
协议方法的调用通过 协议见证表(Protocol Witness Table) 动态派发。
若协议方法被
extension
默认实现,且未在遵守类型中重写,可能优化为静态派发。示例:
protocol Drawable { func draw() // 动态派发(除非有默认实现且未重写) } struct Circle: Drawable { func draw() { print("Drawing a circle") } // 协议见证表派发 } let shape: Drawable = Circle() shape.draw() // 运行时通过协议见证表调用
4. 特殊形式:内联优化(Inline Optimization)
特点:
编译器可能将短小的方法内联(直接替换为方法体),完全消除调用开销。
常见于
private
/fileprivate
方法或标记为@inline(__always)
的方法。@inline(__always) func debugLog(_ message: String) { #if DEBUG print(message) #endif } debugLog("Test") // 可能被内联为 print("Test")
如何影响派发方式?
优先静态派发:使用
struct
、final
或private
修饰方法。强制动态派发:添加
@objc
或dynamic
关键字。协议优化:避免在协议扩展中添加默认实现(除非确定需要)。
Swift 的方法调用形式灵活多样,选择取决于:
性能需求:优先静态派发(如高频调用的工具方法)。
灵活性需求:动态派发(如多态、协议抽象)。
兼容性需求:消息派发(与 Objective-C 交互)。