乐闻世界logo
搜索文章和话题

面试题手册

Swift 中的协议是什么?如何使用协议?

Swift 中的协议是什么?如何使用协议?什么是协议扩展和面向协议编程?Swift 中的协议定义了一组方法和属性的蓝图,类、结构体和枚举可以遵循协议来实现这些要求。协议是 Swift 中实现多态和代码复用的重要机制。协议的基本概念:协议定义了实现者必须提供的属性、方法和其他要求协议本身不实现功能,只是定义接口类型可以遵循多个协议协议可以继承其他协议协议可以作为类型使用定义协议:protocol FullyNamed { var fullName: String { get } func sayHello()}protocol Identifiable { var id: String { get set } static func generateID() -> String}遵循协议:struct Person: FullyNamed { var fullName: String func sayHello() { print("Hello, my name is \(fullName)") }}class Product: Identifiable { var id: String var name: String init(name: String) { self.id = Self.generateID() self.name = name } static func generateID() -> String { return UUID().uuidString }}协议作为类型:func greet(_ person: FullyNamed) { print("Hello, \(person.fullName)") person.sayHello()}let john = Person(fullName: "John Doe")greet(john)协议扩展:为协议提供默认实现可以添加协议未要求的方法和属性可以使用 where 子句进行条件扩展示例: extension FullyNamed { func sayHello() { print("Hello, \(fullName)") } func introduce() { print("I am \(fullName)") } } extension Collection where Element: Equatable { func allEqual() -> Bool { guard let first = first else { return true } return all { $0 == first } } }面向协议编程:使用协议定义抽象接口通过协议扩展提供默认行为使用协议组合实现灵活的功能避免继承的局限性示例: protocol Renderable { func render() } protocol Animatable { func animate() } extension Renderable { func render() { print("Rendering...") } } extension Animatable { func animate() { print("Animating...") } } struct Button: Renderable, Animatable { // 自动获得默认实现 }协议的继承:protocol TextRepresentable { var textualDescription: String { get }}protocol PrettyTextRepresentable: TextRepresentable { var prettyTextualDescription: String { get }}协议组合:protocol Named { var name: String { get }}protocol Aged { var age: Int { get }}struct Person: Named, Aged { var name: String var age: Int}func wishHappyBirthday(to celebrator: Named & Aged) { print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")}最佳实践:使用协议定义清晰的接口通过协议扩展提供默认实现使用协议组合替代多重继承优先使用协议而非继承合理使用关联类型提高灵活性
阅读 0·2月21日 15:06

Swift 中的下标是什么?如何自定义下标?

Swift 中的下标是什么?如何自定义下标?下标可以接受多个参数吗?Swift 中的下标是一种快捷方式,用于访问集合、列表或序列中元素的成员。通过下标,你可以使用方括号语法 [] 来访问和设置值,而不需要调用单独的方法。基本下标语法:struct TimesTable { let multiplier: Int subscript(index: Int) -> Int { return multiplier * index }}let threeTimesTable = TimesTable(multiplier: 3)print(threeTimesTable[6]) // 18下标的特点:使用 subscript 关键字定义可以接受一个或多个参数可以是读写、只读或只写可以有可变参数可以有默认参数值可以抛出错误只读下标:struct Matrix { let rows: Int let columns: Int var grid: [Double] init(rows: Int, columns: Int) { self.rows = rows self.columns = columns self.grid = Array(repeating: 0.0, count: rows * columns) } subscript(row: Int, column: Int) -> Double { get { return grid[(row * columns) + column] } }}读写下标:struct Matrix { let rows: Int let columns: Int var grid: [Double] subscript(row: Int, column: Int) -> Double { get { return grid[(row * columns) + column] } set { grid[(row * columns) + column] = newValue } }}var matrix = Matrix(rows: 2, columns: 2)matrix[0, 0] = 1.0matrix[0, 1] = 2.0print(matrix[0, 0]) // 1.0接受多个参数的下标:struct Chessboard { var board: [[String]] = Array(repeating: Array(repeating: "", count: 8), count: 8) subscript(row: Int, column: Int) -> String { get { return board[row][column] } set { board[row][column] = newValue } } subscript(position: String) -> String { get { let column = position[position.startIndex] let row = Int(position.dropFirst())! return board[row][columnToIndex(column)] } set { let column = position[position.startIndex] let row = Int(position.dropFirst())! board[row][columnToIndex(column)] = newValue } } private func columnToIndex(_ column: Character) -> Int { return Int(column.asciiValue! - Character("a").asciiValue!) }}var chessboard = Chessboard()chessboard["a1"] = "Rook"chessboard[0, 0] = "Queen"类型下标:enum Planet: Int { case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune static subscript(n: Int) -> Planet? { return Planet(rawValue: n) }}let mars = Planet[4]带默认参数的下标:struct DefaultSubscript { var values: [Int] = [] subscript(index: Int, default defaultValue: Int = 0) -> Int { get { return index < values.count ? values[index] : defaultValue } set { if index < values.count { values[index] = newValue } else { values.append(newValue) } } }}最佳实践:使用下标提供简洁的访问语法合理设计下标参数,提高可读性为多维数据提供多参数下标注意下标的性能影响在复杂场景考虑使用方法替代下标
阅读 0·2月21日 15:06

Swift 中的访问控制有哪些级别?open、public、internal、fileprivate、private 有什么区别?

Swift 中的访问控制有哪些级别?open、public、internal、fileprivate、private 有什么区别?Swift 提供了五个访问控制级别,用于控制代码实体(类型、属性、方法等)的可见性和可访问性。这些访问级别帮助开发者实现封装和信息隐藏。访问控制级别:open:最高级别的访问权限可以在定义模块的任何地方访问可以在其他模块中访问和继承可以在其他模块中被重写主要用于类和类成员示例: swift open class OpenClass { open var openProperty: Int = 0 open func openMethod() {} }public:可以在定义模块的任何地方访问可以在其他模块中访问但不能在其他模块中继承或重写适用于不希望被继承或重写的公共 API示例: swift public class PublicClass { public var publicProperty: Int = 0 public func publicMethod() {} }internal(默认):可以在定义模块的任何地方访问不能在其他模块中访问这是默认的访问级别适用于模块内部的实现细节示例: swift class InternalClass { // 默认为 internal var internalProperty: Int = 0 func internalMethod() {} }fileprivate:只能在定义的文件中访问不能在同一个模块的其他文件中访问适用于文件内部的辅助类型和函数示例: swift class FilePrivateClass { fileprivate var filePrivateProperty: Int = 0 fileprivate func filePrivateMethod() {} }private:最低级别的访问权限只能在定义的作用域中访问不能在同一个文件的其他类型中访问适用于类型内部的实现细节示例: class PrivateClass { private var privateProperty: Int = 0 private func privateMethod() {} func publicMethod() { privateMethod() // 可以访问 } }访问控制规则:实体不能比其类型更公开: public class PublicClass { private var property: Int = 0 // 正确 // public var property: Int = 0 // 错误 }元组类型的访问级别是所有成员中最严格的: private var tuple: (Int, String) = (1, "hello") // private函数的访问级别是所有参数和返回值类型中最严格的: private func process(_ data: InternalType) -> PublicType { // 函数访问级别为 private }嵌套类型的访问级别: public class OuterClass { private class InnerClass { // InnerClass 的访问级别为 private } }最佳实践:默认使用 internal,只在需要时使用其他级别使用 private 限制实现细节使用 fileprivate 处理文件内部的辅助功能使用 public 定义公共 API使用 open 定义需要被继承和重写的公共 API遵循最小权限原则,只暴露必要的接口
阅读 0·2月21日 15:06

Swift 中的集合类型有哪些?Array、Set 和 Dictionary 有什么区别和适用场景?

Swift 中的集合类型有哪些?Array、Set 和 Dictionary 有什么区别和适用场景?Swift 提供了三种主要的集合类型:Array(数组)、Set(集合)和 Dictionary(字典)。每种集合类型都有其特定的用途和特点。Array(数组):有序集合可以包含重复元素可以通过索引访问元素示例: var numbers = [1, 2, 3, 4, 5] numbers.append(6) numbers[0] = 10 let first = numbers.first // Optional(10) let count = numbers.count // 6Array 的常用操作:var array = [1, 2, 3]// 添加元素array.append(4)array.insert(0, at: 0)// 删除元素array.remove(at: 0)array.removeLast()// 查找元素let index = array.firstIndex(of: 2)// 排序array.sort()let sorted = array.sorted()// 遍历for (index, element) in array.enumerated() { print("\(index): \(element)")}Set(集合):无序集合不能包含重复元素元素必须是可哈希的示例: var set: Set<Int> = [1, 2, 3, 4, 5] set.insert(6) set.contains(3) // true let count = set.count // 6Set 的常用操作:var set1: Set<Int> = [1, 2, 3, 4, 5]var set2: Set<Int> = [4, 5, 6, 7, 8]// 集合操作let union = set1.union(set2) // {1, 2, 3, 4, 5, 6, 7, 8}let intersection = set1.intersection(set2) // {4, 5}let difference = set1.subtracting(set2) // {1, 2, 3}let symmetricDifference = set1.symmetricDifference(set2) // {1, 2, 3, 6, 7, 8}// 检查关系set1.isSubset(of: set2) // falseset1.isSuperset(of: set2) // falseset1.isDisjoint(with: set2) // false// 添加和删除set1.insert(6)set1.remove(1)Dictionary(字典):无序键值对集合键必须是唯一的键必须是可哈希的示例: var dict = ["name": "John", "age": "30"] dict["email"] = "john@example.com" dict["name"] = "Jane" let name = dict["name"] // Optional("Jane") let count = dict.count // 3Dictionary 的常用操作:var dict = ["name": "John", "age": "30"]// 添加和更新dict["email"] = "john@example.com"dict.updateValue("Jane", forKey: "name")// 删除dict.removeValue(forKey: "age")// 访问if let name = dict["name"] { print(name)}// 遍历for (key, value) in dict { print("\(key): \(value)")}// 获取所有键和值let keys = Array(dict.keys)let values = Array(dict.values)Array、Set 和 Dictionary 的区别:有序性:Array:有序Set:无序Dictionary:无序(键值对)重复元素:Array:可以包含重复元素Set:不能包含重复元素Dictionary:键必须唯一,值可以重复访问方式:Array:通过索引访问Set:通过成员检查访问Dictionary:通过键访问性能:Array:插入和删除 O(n),访问 O(1)Set:插入、删除、查找 O(1)Dictionary:插入、删除、查找 O(1)适用场景:使用 Array 的场景:需要保持元素顺序允许重复元素需要通过索引访问元素示例:待办事项列表、历史记录使用 Set 的场景:需要确保元素唯一性需要快速查找元素是否存在需要集合操作(并集、交集等)示例:标签、用户 ID 集合使用 Dictionary 的场景:需要通过键快速查找值需要存储键值对数据键是唯一的示例:用户信息、配置选项最佳实践:根据需求选择合适的集合类型使用 Array 保持顺序使用 Set 确保唯一性使用 Dictionary 存储键值对注意集合的性能特性
阅读 0·2月21日 15:05

Swift 中的结构体和类有什么区别?何时选择结构体?

Swift 中的结构体和类有什么区别?在什么情况下应该选择结构体而不是类?Swift 中的结构体和类都是用于定义自定义数据类型的构造,但它们在内存管理、赋值行为和使用场景上有重要区别。主要区别:值类型 vs 引用类型:结构体是值类型,赋值时创建副本类是引用类型,赋值时传递引用示例: struct Point { var x: Int var y: Int } class Circle { var center: Point var radius: Int init(center: Point, radius: Int) { self.center = center self.radius = radius } } var point1 = Point(x: 0, y: 0) var point2 = point1 point2.x = 10 print(point1.x) // 0 (point1 不受影响) var circle1 = Circle(center: Point(x: 0, y: 0), radius: 5) var circle2 = circle1 circle2.radius = 10 print(circle1.radius) // 10 (circle1 受影响)继承:类支持继承,可以继承其他类的属性和方法结构体不支持继承示例: class Vehicle { var speed: Int = 0 func accelerate() { speed += 10 } } class Car: Vehicle { var brand: String init(brand: String) { self.brand = brand } }类型转换:类支持类型检查和转换(is、as)结构体不支持类型转换示例: swift let vehicle: Vehicle = Car(brand: "Toyota") if let car = vehicle as? Car { print(car.brand) }引用计数:类使用 ARC(自动引用计数)管理内存结构体不需要引用计数,直接在栈上分配类可能导致循环引用,需要使用 weak 或 unowned析构器:类可以定义析构器(deinit)结构体不能定义析构器示例: class FileHandler { let fileHandle: FileHandle init(path: String) { self.fileHandle = FileHandle(forReadingAtPath: path)! } deinit { fileHandle.closeFile() } }选择结构体的场景:数据模型主要是值类型数据需要独立存在,不希望被意外修改数据相对较小,复制成本较低不需要继承需要线程安全的操作需要比较值相等性选择类的场景:需要继承和多态需要共享状态数据较大,复制成本高需要控制生命周期和内存管理需要使用标识符比较需要使用析构器最佳实践:优先使用结构体,只有在需要引用类型特性时才使用类使用结构体表示简单的数据模型使用类表示具有身份和行为对象在需要继承时使用类注意类可能导致的循环引用问题
阅读 0·2月21日 15:05

Swift 中的高阶函数有哪些?如何使用 map、filter、reduce?

Swift 中的高阶函数有哪些?如何使用 map、filter、reduce 等函数式编程特性?Swift 提供了丰富的高阶函数,支持函数式编程范式。这些函数可以接受其他函数作为参数或返回函数,使代码更加简洁和声明式。主要的高阶函数:map:对集合中的每个元素应用转换函数返回包含转换后元素的新数组保持原始集合不变示例: let numbers = [1, 2, 3, 4, 5] let doubled = numbers.map { $0 * 2 } // [2, 4, 6, 8, 10] let names = ["alice", "bob", "charlie"] let capitalized = names.map { $0.capitalized } // ["Alice", "Bob", "Charlie"]filter:根据条件筛选集合中的元素返回包含满足条件元素的新数组示例: let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] let evenNumbers = numbers.filter { $0 % 2 == 0 } // [2, 4, 6, 8, 10] let words = ["apple", "banana", "pear", "grape"] let longWords = words.filter { $0.count > 4 } // ["apple", "banana"]reduce:将集合中的元素组合成单个值接受初始值和组合函数示例: let numbers = [1, 2, 3, 4, 5] let sum = numbers.reduce(0) { $0 + $1 } // 15 let product = numbers.reduce(1) { $0 * $1 } // 120 let names = ["Alice", "Bob", "Charlie"] let allNames = names.reduce("") { $0 + $1 + " " } // "Alice Bob Charlie "flatMap:对集合中的每个元素应用转换函数,并展平结果处理嵌套数组或可选值示例: let numbers = [[1, 2], [3, 4], [5, 6]] let flattened = numbers.flatMap { $0 } // [1, 2, 3, 4, 5, 6] let optionalNumbers: [Int?] = [1, nil, 3, nil, 5] let nonNilNumbers = optionalNumbers.flatMap { $0 } // [1, 3, 5]compactMap:类似于 map,但过滤掉 nil 值专门用于处理可选值示例: swift let strings = ["1", "2", "three", "4", "five"] let numbers = strings.compactMap { Int($0) } // [1, 2, 4]forEach:对集合中的每个元素执行操作不返回新值示例: swift let numbers = [1, 2, 3, 4, 5] numbers.forEach { print($0) }sorted:对集合进行排序可以自定义排序规则示例: let numbers = [5, 2, 8, 1, 9] let ascending = numbers.sorted { $0 < $1 } // [1, 2, 5, 8, 9] let descending = numbers.sorted { $0 > $1 } // [9, 8, 5, 2, 1]链式调用:let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]let result = numbers .filter { $0 % 2 == 0 } .map { $0 * $0 } .reduce(0) { $0 + $1 }// 220 (2² + 4² + 6² + 8² + 10²)最佳实践:优先使用高阶函数而非 for 循环合理使用链式调用提高代码可读性注意性能影响,避免过度嵌套使用有意义的变量名提高代码清晰度在复杂逻辑中使用传统循环提高可维护性
阅读 0·2月21日 15:04

Swift 中的 Codable 是什么?CustomStringConvertible 和 CustomDebugStringConvertible 有什么区别?

Swift 中的 Codable 是什么?如何使用 Codable 进行编码和解码?CustomStringConvertible 和 CustomDebugStringConvertible 有什么区别?Swift 中的 Codable 是一个类型别名,结合了 Encodable 和 Decodable 协议,用于在数据类型和外部表示(如 JSON、Property List)之间进行转换。Codable 的基本用法:struct User: Codable { let id: Int let name: String let email: String}// 编码let user = User(id: 1, name: "John Doe", email: "john@example.com")let encoder = JSONEncoder()if let jsonData = try? encoder.encode(user) { print(String(data: jsonData, encoding: .utf8)!)}// 解码let decoder = JSONDecoder()if let decodedUser = try? decoder.decode(User.self, from: jsonData) { print(decodedUser.name)}自定义编码键:struct User: Codable { let id: Int let name: String let email: String enum CodingKeys: String, CodingKey { case id case name = "full_name" case email = "email_address" }}自定义编码和解码:struct DateWrapper: Codable { let date: Date enum CodingKeys: String, CodingKey { case timestamp } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let timestamp = try container.decode(Double.self, forKey: .timestamp) self.date = Date(timeIntervalSince1970: timestamp) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(date.timeIntervalSince1970, forKey: .timestamp) }}Codable 的注意事项:所有属性必须都是 Codable 类型可选类型自动支持 Codable数组和字典如果元素是 Codable 类型,也自动支持 Codable可以使用 CodingKeys 自定义键名CustomStringConvertible:用于提供自定义的字符串表示实现 description 属性用于 print() 和 String(describing:)示例: struct Point: CustomStringConvertible { let x: Int let y: Int var description: String { return "(\(x), \(y))" } } let point = Point(x: 3, y: 4) print(point) // (3, 4)CustomDebugStringConvertible:用于提供调试时的字符串表示实现 debugDescription 属性用于调试器中的描述优先级高于 CustomStringConvertible示例: struct User: CustomDebugStringConvertible { let id: Int let name: String let password: String var debugDescription: String { return "User(id: \(id), name: \(name))" } } let user = User(id: 1, name: "John", password: "secret") debugPrint(user) // User(id: 1, name: John)CustomStringConvertible 和 CustomDebugStringConvertible 的区别:用途不同:CustomStringConvertible:用于常规字符串表示CustomDebugStringConvertible:用于调试字符串表示实现方法不同:CustomStringConvertible:实现 description 属性CustomDebugStringConvertible:实现 debugDescription 属性调用场景不同:CustomStringConvertible:print()、String(describing:)CustomDebugStringConvertible:debugPrint()、调试器优先级不同:同时实现时,CustomDebugStringConvertible 优先级更高调试时使用 debugDescription常规时使用 description最佳实践:为数据模型实现 Codable使用 CodingKeys 处理键名不匹配为自定义类型实现 CustomStringConvertible为调试实现 CustomDebugStringConvertible在 debugDescription 中隐藏敏感信息
阅读 0·2月21日 15:04

Swift 中的错误处理机制是什么?如何使用 throws、throw、try、catch?

Swift 中的错误处理机制是什么?如何使用 throws、throw、try、catch 处理错误?Swift 提供了一套完整的错误处理机制,允许开发者优雅地处理运行时错误。Swift 的错误处理模型与异常处理类似,但更加类型安全和可控。错误定义:使用 Error 协议定义错误类型可以使用枚举定义具体的错误情况可以关联错误信息示例: enum NetworkError: Error { case invalidURL case requestFailed(Int) case noData case decodingError } enum FileError: Error { case notFound case permissionDenied case corrupted }抛出错误:使用 throws 关键字标记可能抛出错误的函数使用 throw 关键字抛出错误示例: func fetchData(from urlString: String) throws -> Data { guard let url = URL(string: urlString) else { throw NetworkError.invalidURL } let (data, response) = try URLSession.shared.data(from: url) guard let httpResponse = response as? HTTPURLResponse else { throw NetworkError.requestFailed(0) } guard httpResponse.statusCode == 200 else { throw NetworkError.requestFailed(httpResponse.statusCode) } return data }处理错误:使用 try-catch: do { let data = try fetchData(from: "https://api.example.com/data") print("Data received: \(data)") } catch NetworkError.invalidURL { print("Invalid URL") } catch NetworkError.requestFailed(let statusCode) { print("Request failed with status: \(statusCode)") } catch { print("Unexpected error: \(error)") }使用 try?:将错误转换为可选值如果发生错误,返回 nil示例: swift let data = try? fetchData(from: "https://api.example.com/data") if let data = data { print("Data received: \(data)") } else { print("Failed to fetch data") }使用 try!:强制解包,确定不会抛出错误时使用如果抛出错误,会触发运行时错误示例: swift let data = try! fetchData(from: "https://api.example.com/data") print("Data received: \(data)")defer 语句:在代码块退出前执行清理操作无论是否发生错误都会执行示例: func processFile(at path: String) throws { let fileHandle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path)) defer { fileHandle.closeFile() } let data = fileHandle.readDataToEndOfFile() return data }rethrow:将捕获的错误重新抛出用于包装函数示例: func processData(_ urlString: String) throws -> Data { let data = try fetchData(from: urlString) return data }Result 类型:使用 Result<Success, Failure> 类型处理错误更函数式的错误处理方式示例: func fetchDataResult(from urlString: String) -> Result<Data, Error> { guard let url = URL(string: urlString) else { return .failure(NetworkError.invalidURL) } let (data, response) = URLSession.shared.synchronousData(with: url) return .success(data) } switch fetchDataResult(from: "https://api.example.com/data") { case .success(let data): print("Data received: \(data)") case .failure(let error): print("Error: \(error)") }最佳实践:使用枚举定义清晰的错误类型为错误添加关联值提供更多信息使用 try? 处理可恢复的错误使用 defer 确保资源正确释放在适当的地方使用 Result 类型
阅读 0·2月21日 14:23

Swift 中的内存管理机制是什么?如何避免循环引用?

Swift 中的内存管理机制是什么?什么是 ARC(自动引用计数)?如何避免循环引用?Swift 使用自动引用计数(ARC)来自动管理应用程序的内存。ARC 会跟踪和管理应用程序使用的内存,并在实例不再需要时自动释放内存。ARC 的工作原理:每次创建类实例的新引用时,ARC 会增加该实例的引用计数当引用被移除时,ARC 会减少引用计数当引用计数降为零时,ARC 会释放该实例的内存ARC 只适用于类类型的实例,结构体和枚举是值类型,不参与引用计数强引用循环:两个或多个类实例相互持有强引用,导致引用计数永远不会降为零常见场景:类之间的相互引用、闭包捕获类实例示例: class Person { var apartment: Apartment? } class Apartment { var tenant: Person? } let person = Person() let apartment = Apartment() person.apartment = apartment apartment.tenant = person解决循环引用的方法:弱引用:使用 weak 关键字声明不会增加引用计数引用的对象被释放后,弱引用会自动变为 nil必须声明为可选类型适用于引用可能为 nil 的情况示例: swift class Apartment { weak var tenant: Person? }无主引用:使用 unowned 关键字声明不会增加引用计数引用对象被释放后,无主引用不会自动变为 nil不能声明为可选类型适用于引用对象生命周期更长的情况示例: class Customer { let creditCard: CreditCard init(creditCard: CreditCard) { self.creditCard = creditCard } } class CreditCard { unowned let customer: Customer }闭包中的循环引用:使用捕获列表 [weak self] 或 [unowned self]示例: swift class HTMLElement { let name: String lazy var asHTML: () -> String = { [weak self] in guard let self = self else { return "" } return "<\(self.name)>" } }最佳实践:在类属性中使用 weak 或 unowned 避免强引用循环在闭包中使用捕获列表处理循环引用使用 weak 当引用可能为 nil 时使用 unowned 当引用对象生命周期更长时使用 Instruments 工具检测内存泄漏
阅读 0·2月21日 14:23