2025 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 和 MyPrivateClass

Swift AnyAnyObjectGenerics区别

这里是关于 AnyAnyObject 和泛型的一些区别:

  1. Any
    • AnySwift 中的⼀种类型擦除(type erasure)的概念,是⼀个协议(protocol)。
    • 使用 Any 声明的变量可以存储任何类型的值,包括值类型和引⽤类型。
    • AnySwift 中的一个特殊类型,用于表示任意类型的实例。
    • 使⽤ 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]
  1. AnyObject
    • AnyObject Swift 中的⼀个协议( protocol ),表示任何类(引⽤类型)类型的实例。
    • 使用 AnyObject 声明的变量可以存储任何类类型的实例,但不能存储结构体、枚举或其他类型的实例。
    • AnyObject 是一个协议类型,所有类都隐式地遵循了 AnyObject 协议。
    • 例如:
var anyObjectValue: AnyObject

class MyClass {}
anyObjectValue = MyClass()
print(anyObjectValue) // 输出: 

class YourClass {}
anyObjectValue = YourClass()
print(anyObjectValue) // 输出: 

  1. Generics
    • 泛型是 Swift中的⼀种编程技术,⽤于编写可以处理任意类型的代码。
    • 泛型允许在编译时编写灵活的代码,以便在使用时指定类型。
    • 使用泛型可以编写更加灵活和可重用的代码,而不需要在编译时知道确切的类型。
    • 泛型可以用于函数、方法、类、结构体和枚举等地方,使得这些实体可以与任意类型一起工作。
    • 泛型代码在编译时会进⾏类型检查,避免了使⽤ Any 时的类型不确定性问题。
    • 例如:
func swapTwoValues<T>(_ 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

简而言之,AnyAnyObject 是用于处理不同类型的实例的机制,而泛型则是一种编写灵活、可重用代码的机制。Any 可以表示任何类型的实例,而 AnyObject 只能表示类类型的实例。Generics 允许在编译时编写灵活的代码,以处理不同类型的数据。

Swift final 关键字 ⽤过没 主要⽤来⼲嘛

final 关键字在 Swift 中用于限制类、属性和方法的继承和重写。具体来说,final 有以下用途:

  1. 限制类的继承:通过在类的定义前加上 final 关键字,可以防止其他类继承该类。这样做可以确保类的实现不会被修改或扩展,从而提高代码的安全性和稳定性。

    final class FinalClass {}
    // OtherClass: FinalClass // 编译错误,FinalClass 不能被继承
    
  2. 限制属性的重写:通过在属性的定义前加上 final 关键字,可以防止子类重写该属性。这样做可以确保父类的属性在子类中保持不变。

    class ParentClass {
        final var finalProperty: Int = 42
    }
    
    class ChildClass: ParentClass {
        // override var finalProperty: Int // 编译错误,finalProperty 不能被重写
    }
    
  3. 限制方法的重写:通过在方法的定义前加上 final 关键字,可以防止子类重写该方法。这样做可以确保父类的方法在子类中保持不变。

    class ParentClass {
        final func finalMethod() {
            print("ParentClass final method")
        }
    }
    
    class ChildClass: ParentClass {
        // override func finalMethod() { // 编译错误,finalMethod 不能被重写
        //     print("ChildClass override method")
        // }
    }
    

总之,final 关键字用于限制类、属性和方法的继承和重写,从而提高代码的安全性和稳定性。特别是在框架或库中希望确保类的⾏为不被修改时⾮常有⽤。

Swift 的 staticclass 区别

staticclass 关键字都可以用来声明类型级别的属性和方法

  1. 静态属性和方法的使用方式
    • static:可以用在类、结构体和枚举中,用来声明类型级别的属性和方法。在类中,static 修饰的属性和方法可以被子类继承和重写。
    • class:只能用在类中,用来声明类型级别的属性和方法。在类中,class 修饰的属性和方法可以被子类继承,但是只有类方法可以被子类重写。
  2. 重写
    • static:无法在子类中重写。
    • class:只能用于类方法,可以在子类中进行重写。
  3. 继承
    • static:可以被继承。
    • class:可以被继承
  4. 类成员引用
    • static:可以直接通过类型名称来访问,例如 ClassName.staticMember。
    • class:只能通过实例或子类来访问,例如 instanceOfClassOrSubclass.classMember。
  5. 示例
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 subclass's 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 可以实现以下功能:

  1. 添加新的方法:可以在已有的类型上添加新的实例方法或类型方法。
extension Int {
    func squared() -> Int {
        return self * self
    }
}

let number = 5
let squaredNumber = number.squared() // 25
  1. 添加新的计算属性:可以在已有的类型上添加新的计算属性。
extension Double {
    var squared: Double {
        return self * self
    }
}

let doubleNumber = 3.0
let squaredDouble = doubleNumber.squared // 9.0
  1. 定义新的初始化方法:可以在已有的类型上定义新的初始化方法。
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"
  1. 遵循协议:可以使已有的类型遵循新的协议。
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
  1. 提供默认实现:可以在协议的扩展中为协议中的方法提供默认实现。
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 是什么?如何安全解包?

  1. Optional :
  • Optional 表示一个值可能存在或为 nil。
  1. 安全解包:
  • if let:适合在需要处理 nil 的情况下解包。
  • guard let:适合在函数或方法中提前返回。
  • ??:适合提供默认值。

注:切记使用**!**强制解包,需谨慎使用

Swift 有什么⽅案能监听属性值的变化,不使用ObjC的KVO

  1. 属性观察器
    属性观察器允许在属性值即将被设置或已经被设置时执行特定的代码。
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: 0
  1. Swift 闭包
    使用闭包可以在属性值发生变化时执行特定的代码。这种方法适用于自定义的类和结构体,但不适用于 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: 5
  1. Swift 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: 5
  1. SwiftUI 监听属性值变化
    在 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 中的高阶函数是指那些以其他函数作为参数或者返回值的函数。这些函数通常用于简化代码、提高可读性和功能性。常见的高阶函数包括 mapfilterreduceflatMapcompactMapallSatisfy 等。

  1. map

    map 函数用于对集合中的每个元素应用一个指定的转换闭包,然后返回一个包含转换结果的新集合。

    • 函数定义:

      func map<T>(_ 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]
      
  2. filter

    filter 函数用于从集合中选择满足指定条件的元素,并返回一个包含满足条件的元素的新集合。

    • 函数定义:

      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]
      
  3. reduce

    reduce 函数用于将集合中的所有元素组合成单个值,并返回该值。

    • 函数定义:

      func reduce<Result>(_ 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) // 输出 15
      
  4. flatMap

    flatMap 函数用于对集合中的每个元素应用一个转换闭包,并将结果拼接成一个新的集合。

    • 函数定义:

      func flatMap<SegmentOfResult>(_ 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]
      
  5. compactMap

    compactMap 函数用于对集合中的每个元素应用一个转换闭包,并过滤掉结果中的 nil 值。

    • 函数定义:

      func compactMap<ElementOfResult>(_ 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]
      
  6. allSatisfy

    allSatisfy 函数用于检查序列中的所有元素是否都满足指定条件。

    • 函数定义:

      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 函数通常被归类为集合操作函数或序列操作函数,因为它主要用于处理集合类型(如数组、字典等)或序列类型(如数组、字符串等)的数据。
在刷Leetcode 3114. 替换字符可以得到的最晚时间,这个题的一个题解就可以使用zip函数和高阶函数allSatisfy来实现,代码里面有两种写法其实都是一样的,注释掉的那种是隐藏了参数类型

当我们调用 zip(s, t) 时,它会将两个序列 st 中的元素一一配对,生成一个包含元组的序列。在这里,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"

以上是替换字符可以得到的最晚时间的实现代码。

你可能感兴趣的:(移动开发,swift,开发语言,ios)