swift-最新面试题二
2024 Swift 最新面试题及答案基础部分Swift访问控制Swift Any、AnyObject和Generics区别Swift final 关键字 ⽤过没 主要⽤来⼲嘛Swift 的 static和class 区别Swift 的 extension ⽤过没都能⼲嘛Swift 中的 Optional 是什么如何安全解包Swift 有什么⽅案能监听属性值的变化不使用ObjC的KVO进阶部分说一下Swift 高阶函数使用过Swift 使用过zip函数吗用来干嘛题目3114. 替换字符可以得到的最晚时间示例实现代码基础部分Swift访问控制访问级别描述同一模块内可见不同模块内可见示例open最高访问级别允许实体被定义模块外的其他模块访问也可以被继承和重写。✅✅用于公共框架和库的公共接口如一个开源框架的公共API。public允许实体被定义模块外的其他模块访问但不能被继承和重写。✅❌用于公共接口的内部实现如一个库的具体实现细节。internal默认访问级别允许实体在定义模块内部任何地方访问但不能被定义模块外的其他模块访问。✅❌用于应用程序或框架的内部实现如应用程序的各个组件之间的交互。fileprivate限制实体只能在其定义的文件内部访问。✅❌用于实现细节和私有功能如一个类的内部细节只对该类的其他成员可见。private限制实体只能在其定义的作用域类、结构体、枚举或扩展内部访问。✅❌用于实现细节和私有功能如一个结构体内部的数据成员只能在该结构体的方法中使用。示例// ModuleA.swift open class MyOpenClass {} public class MyPublicClass {} internal class MyInternalClass {} fileprivate class MyFilePrivateClass {} private class MyPrivateClass {} // ModuleB.swift import ModuleA // 在 ModuleB.swift 中可以访问 MyOpenClass、MyPublicClass 和 MyInternalClass // 但不能访问 MyFilePrivateClass 和 MyPrivateClassSwiftAny、AnyObject和Generics区别这里是关于Any、AnyObject和泛型的一些区别AnyAny是Swift中的⼀种类型擦除type erasure的概念是⼀个协议protocol。使用Any声明的变量可以存储任何类型的值包括值类型和引⽤类型。Any是Swift中的一个特殊类型用于表示任意类型的实例。使⽤Any时Swift编译器会放弃类型检查因此需要⼩⼼使⽤避免类型错误。例如var anyValue: Any anyValue 42 print(anyValue) // 输出: 42 anyValue Hello, Swift print(anyValue) // 输出: Hello, Swift anyValue [1, 2, 3] print(anyValue) // 输出: [1, 2, 3]AnyObjectAnyObject是Swift中的⼀个协议protocol表示任何类(引⽤类型)类型的实例。使用AnyObject声明的变量可以存储任何类类型的实例但不能存储结构体、枚举或其他类型的实例。AnyObject是一个协议类型所有类都隐式地遵循了AnyObject协议。例如var anyObjectValue: AnyObject class MyClass {} anyObjectValue MyClass() print(anyObjectValue) // 输出: MyClass class YourClass {} anyObjectValue YourClass() print(anyObjectValue) // 输出: YourClassGenerics泛型是Swift中的⼀种编程技术⽤于编写可以处理任意类型的代码。泛型允许在编译时编写灵活的代码以便在使用时指定类型。使用泛型可以编写更加灵活和可重用的代码而不需要在编译时知道确切的类型。泛型可以用于函数、方法、类、结构体和枚举等地方使得这些实体可以与任意类型一起工作。泛型代码在编译时会进⾏类型检查避免了使⽤Any时的类型不确定性问题。例如func swapTwoValuesT(_ a: inout T, _ b: inout T) { let temp a a b b temp } var intValue1 42 var intValue2 10 swapTwoValues(intValue1, intValue2) print(intValue1: \(intValue1), intValue2: \(intValue2)) // 输出: intValue1: 10, intValue2: 42 var stringValue1 Hello var stringValue2 World swapTwoValues(stringValue1, stringValue2) print(stringValue1: \(stringValue1), stringValue2: \(stringValue2)) // 输出: stringValue1: World, stringValue2: Hello简而言之Any和AnyObject是用于处理不同类型的实例的机制而泛型则是一种编写灵活、可重用代码的机制。Any可以表示任何类型的实例而AnyObject只能表示类类型的实例。Generics 允许在编译时编写灵活的代码以处理不同类型的数据。Swiftfinal关键字 ⽤过没 主要⽤来⼲嘛final关键字在 Swift 中用于限制类、属性和方法的继承和重写。具体来说final有以下用途限制类的继承通过在类的定义前加上final关键字可以防止其他类继承该类。这样做可以确保类的实现不会被修改或扩展从而提高代码的安全性和稳定性。final class FinalClass {} // OtherClass: FinalClass // 编译错误FinalClass 不能被继承限制属性的重写通过在属性的定义前加上final关键字可以防止子类重写该属性。这样做可以确保父类的属性在子类中保持不变。class ParentClass { final var finalProperty: Int 42 } class ChildClass: ParentClass { // override var finalProperty: Int // 编译错误finalProperty 不能被重写 }限制方法的重写通过在方法的定义前加上final关键字可以防止子类重写该方法。这样做可以确保父类的方法在子类中保持不变。class ParentClass { final func finalMethod() { print(ParentClass final method) } } class ChildClass: ParentClass { // override func finalMethod() { // 编译错误finalMethod 不能被重写 // print(ChildClass override method) // } }总之final关键字用于限制类、属性和方法的继承和重写从而提高代码的安全性和稳定性。特别是在框架或库中希望确保类的⾏为不被修改时⾮常有⽤。Swift 的static和class区别static和class关键字都可以用来声明类型级别的属性和方法静态属性和方法的使用方式static可以用在类、结构体和枚举中用来声明类型级别的属性和方法。在类中static修饰的属性和方法可以被子类继承和重写。class只能用在类中用来声明类型级别的属性和方法。在类中class修饰的属性和方法可以被子类继承但是只有类方法可以被子类重写。重写static无法在子类中重写。class只能用于类方法可以在子类中进行重写。继承static可以被继承。class可以被继承类成员引用static可以直接通过类型名称来访问例如 ClassName.staticMember。class只能通过实例或子类来访问例如 instanceOfClassOrSubclass.classMember。示例class Message { // 使用 static 关键字声明静态属性 static var staticProperty: Int 0 // 使用 class 关键字声明类属性 class var classProperty: Int { get { return 10 } set { print(Setting class property to \(newValue)) } } // 使用 static 关键字声明静态方法 static func staticMethod() { print(This is a static method) } // 使用 class 关键字声明类方法 class func classMethod() { print(This is a class method) } } class Chat: Message { // 重写类属性 override class var classProperty: Int { get { return 100 } set { print(Setting class property to \(newValue)) } } // 重写类方法 override class func classMethod() { print(This is a subclasss class method) } } // 调用静态属性和方法 Message.staticProperty 5 print(Static Property: \(Message.staticProperty)) Message.staticMethod() // 调用类属性和方法 print(Class Property: \(Message.classProperty)) Message.classMethod() // 调用子类的类方法 Chat.classMethod()注如果你需要在子类中重写方法或属性应该使用class关键字。如果不需要重写并且希望在类、结构体或枚举中统一处理类型级别的属性和方法那么可以使用static关键字。Swift 的extension⽤过没都能⼲嘛是的extension在 Swift 中经常被使用。Swift 的extension扩展允许你在不修改原始代码的情况下扩展现有类型的功能包括类、结构体、枚举和协议。使用extension可以实现以下功能添加新的方法可以在已有的类型上添加新的实例方法或类型方法。extension Int { func squared() - Int { return self * self } } let number 5 let squaredNumber number.squared() // 25添加新的计算属性可以在已有的类型上添加新的计算属性。extension Double { var squared: Double { return self * self } } let doubleNumber 3.0 let squaredDouble doubleNumber.squared // 9.0定义新的初始化方法可以在已有的类型上定义新的初始化方法。extension String { init(repeating character: Character, count: Int) { var string for _ in 0..count { string.append(character) } self string } } let repeatedString String(repeating: A, count: 5) // AAAAA遵循协议可以使已有的类型遵循新的协议。protocol CustomProtocol { func customMethod() } extension Int: CustomProtocol { func customMethod() { print(Custom method called on Int) } } let number 10 number.customMethod() // 输出: Custom method called on Int提供默认实现可以在协议的扩展中为协议中的方法提供默认实现。protocol SomeProtocol { func requiredMethod() } extension SomeProtocol { func requiredMethod() { print(Default implementation of required method) } } struct SomeStruct: SomeProtocol {} let someStruct SomeStruct() someStruct.requiredMethod() // 输出: Default implementation of required method总之extension是一种强大的工具用于扩展已有类型的功能使代码更加模块化、易于维护和扩展。Swift 中的 Optional 是什么如何安全解包Optional :Optional 表示一个值可能存在或为 nil。安全解包:if let适合在需要处理nil的情况下解包。guard let适合在函数或方法中提前返回。??适合提供默认值。注切记使用**!**强制解包需谨慎使用Swift 有什么⽅案能监听属性值的变化不使用ObjC的KVO属性观察器属性观察器允许在属性值即将被设置或已经被设置时执行特定的代码。class MyClass { var myProperty: Int 0 { willSet { print(New value will be set: \(newValue)) } didSet { print(Old value was: \(oldValue)) } } } let instance MyClass() instance.myProperty 5 // 输出: // New value will be set: 5 // Old value was: 0Swift 闭包使用闭包可以在属性值发生变化时执行特定的代码。这种方法适用于自定义的类和结构体但不适用于 Swift 的基本类型。class MyClass { var myProperty: Int 0 { didSet { propertyDidChange?(myProperty) } } var propertyDidChange: ((Int) - Void)? } let instance MyClass() instance.propertyDidChange { newValue in print(Property did change: \(newValue)) } instance.myProperty 5 // 输出: Property did change: 5Swift Combine 框架Combine 框架是 Swift 中用于处理异步事件流的框架可以用于监听属性值的变化。import Combine class MyClass { Published var myProperty: Int 0 } let instance MyClass() let cancellable instance.$myProperty.sink { value in print(New value: \(value)) } instance.myProperty 5 // 输出: New value: 5SwiftUI 监听属性值变化在 SwiftUI 中可以使用属性包装器来监听属性值的变化常用的包装器有State、Binding、ObservedObject和EnvironmentObject。struct ContentView: View { State private var count: Int 0 var body: some View { VStack { Text(Count: \(count)) Button(Increment) { count 1 } } } }class MyModel: ObservableObject { Published var count: Int 0 } struct ContentView: View { ObservedObject var model: MyModel var body: some View { VStack { Text(Count: \(model.count)) Button(Increment) { model.count 1 } } } }struct ParentView: View { State private var count: Int 0 var body: some View { VStack { ChildView(count: $count) Button(Increment) { count 1 } } } } struct ChildView: View { Binding var count: Int var body: some View { Text(Count: \(count)) } }class UserData: ObservableObject { Published var username John } struct ContentView: View { EnvironmentObject var userData: UserData var body: some View { VStack { Text(Hello, \(userData.username)!) } } } struct MainView: View { var body: some View { ContentView() .environmentObject(UserData()) } }以上是 Swift 和 SwiftUI 中监听属性值变化的几种方法开发者可以根据具体需求选择适合的方法。进阶部分说一下Swift 高阶函数Swift 中的高阶函数是指那些以其他函数作为参数或者返回值的函数。这些函数通常用于简化代码、提高可读性和功能性。常见的高阶函数包括map、filter、reduce、flatMap、compactMap、allSatisfy等。mapmap函数用于对集合中的每个元素应用一个指定的转换闭包然后返回一个包含转换结果的新集合。函数定义func mapT(_ transform: (Element) throws - T) rethrows - [T]示例let numbers [1, 2, 3, 4, 5] let doubled numbers.map { $0 * 2 } print(doubled) // 输出 [2, 4, 6, 8, 10]filterfilter函数用于从集合中选择满足指定条件的元素并返回一个包含满足条件的元素的新集合。函数定义func filter(_ isIncluded: (Element) throws - Bool) rethrows - [Element]示例let numbers [1, 2, 3, 4, 5] let evenNumbers numbers.filter { $0 % 2 0 } print(evenNumbers) // 输出 [2, 4]reducereduce函数用于将集合中的所有元素组合成单个值并返回该值。函数定义func reduceResult(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws - Result) rethrows - Result示例let numbers [1, 2, 3, 4, 5] let sum numbers.reduce(0, { $0 $1 }) print(sum) // 输出 15flatMapflatMap函数用于对集合中的每个元素应用一个转换闭包并将结果拼接成一个新的集合。函数定义func flatMapSegmentOfResult(_ transform: (Element) throws - SegmentOfResult) rethrows - [SegmentOfResult.Element] where SegmentOfResult : Sequence示例let nestedArray [[1, 2, 3], [4, 5, 6], [7, 8, 9]] let flatArray nestedArray.flatMap { $0 } print(flatArray) // 输出 [1, 2, 3, 4, 5, 6, 7, 8, 9]compactMapcompactMap函数用于对集合中的每个元素应用一个转换闭包并过滤掉结果中的 nil 值。函数定义func compactMapElementOfResult(_ transform: (Element) throws - ElementOfResult?) rethrows - [ElementOfResult]示例let strings [1, 2, 3, hello, 5] let numbers strings.compactMap { Int($0) } print(numbers) // 输出 [1, 2, 3, 5]allSatisfyallSatisfy函数用于检查序列中的所有元素是否都满足指定条件。函数定义func allSatisfy(_ predicate: (Element) throws - Bool) rethrows - Bool示例let numbers [2, 4, 6, 8, 10] let allEven numbers.allSatisfy { $0 % 2 0 } print(allEven) // 输出 true以上就是 Swift 中常用的高阶函数它们可以帮助你更加高效地处理集合中的元素。使用过Swift 使用过zip函数吗用来干嘛zip函数是一种合并两个序列的函数它接受两个序列作为输入并将它们的元素一一对应地组合成元组然后返回一个包含这些元组的序列。在 Swift 中zip 函数通常被归类为集合操作函数或序列操作函数因为它主要用于处理集合类型如数组、字典等或序列类型如数组、字符串等的数据。在刷Leetcode3114. 替换字符可以得到的最晚时间这个题的一个题解就可以使用zip函数和高阶函数allSatisfy来实现代码里面有两种写法其实都是一样的注释掉的那种是隐藏了参数类型当我们调用zip(s, t)时它会将两个序列s和t中的元素一一配对生成一个包含元组的序列。在这里s是输入字符串t是从最晚时间开始逆序生成的时间字符串。例如如果s 1?:?4而t 11:54那么zip(s, t)将生成一个包含元组(1, 1)、(?, 1)、(?, :)、(?, 5)、(4, 4)的序列。接下来我们调用allSatisfy函数来检查这个序列中的所有元素是否满足某个条件。在这里我们使用一个闭包作为参数传递给allSatisfy函数。闭包接受一个元组作为输入参数其中的第一个元素是来自输入字符串s第二个元素是来自时间字符串t。闭包中的条件表达式{ $0 ? || $0 $1 }检查每个元组中的第一个元素是否是问号或者是否与第二个元素相等。这样如果序列中的所有元素都满足这个条件即如果输入字符串中的每个字符要么是问号要么与对应的时间字符串中的字符相等那么allSatisfy函数就会返回true。这表示时间字符串t符合要求我们就返回它。题目3114. 替换字符可以得到的最晚时间给定一个由?和数字组成的字符串time请你将其中的?替换为可以构成的最晚有效时间。其中字符串表示的时间需符合 24 小时制的格式最晚有效时间指的是 23:59。如果无法构成最晚有效时间请返回一个空字符串。字符串中每个?都可以被任何一位数字替换。示例输入time 2?:?0 输出23:50输入time 0?:3? 输出09:39输入time 1?:22 输出19:22输入time ?4:03 输出14:03实现代码func findLatestTimee(_ s: String) - String { // 从最晚时间开始逆序遍历小时和分钟 for h in (0...11).reversed() { for m in (0...59).reversed() { // 将小时和分钟格式化为两位数的字符串 let hour String(format: %02d, h) let minute String(format: %02d, m) // 组合小时和分钟形成时间字符串 t let t hour : minute // 检查时间字符串 t 是否与输入字符串 s 相匹配 // if zip(s, t).allSatisfy({ $0 ? || $0 $1 }) { // // 如果匹配则返回时间字符串 t // return t // } if zip(s, t).allSatisfy({ (element:(Character, Character)) in print(dddd) return element.0 ? || element.0 element.1 }) { return t } } } // 如果找不到匹配的时间字符串则返回空字符串 return } // 测试样例 print(findLatestTimee(2?:?0)) // 输出 23:50 print(findLatestTimee(0?:3?)) // 输出 09:39 print(findLatestTimee(1?:22)) // 输出 19:22 print(findLatestTimee(?4:03)) // 输出 14:03以上是替换字符可以得到的最晚时间的实现代码