面试题答案
一键面试1. 访问控制
Swift 提供了四种访问控制级别:open
、public
、internal
和 fileprivate
、private
。
open
和public
:用于框架对外公开的接口,允许框架外部的代码访问和继承(open
允许外部继承,public
不允许)。例如,如果框架是一个网络请求库,NetworkRequest
类用于发起请求,它可以是public
级别的:
public class NetworkRequest {
public let url: URL
public init(url: URL) {
self.url = url
}
public func sendRequest(completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
// 实际发送请求的代码
let task = URLSession.shared.dataTask(with: url, completionHandler: completion)
task.resume()
}
}
internal
:默认访问级别,用于框架内部的类、方法等,在框架内部可以自由访问,但框架外部无法访问。例如,框架内部用于处理请求缓存的RequestCache
类,对外部使用者无意义,可设为internal
:
internal class RequestCache {
private var cache: [URL: Data] = [:]
internal func getCachedData(for url: URL) -> Data? {
return cache[url]
}
internal func cacheData(data: Data, for url: URL) {
cache[url] = data
}
}
fileprivate
:表示只能在定义它的文件内部访问。比如在某个文件中,有一个辅助函数parseResponseData
仅在该文件内部的其他函数中使用,可设为fileprivate
:
fileprivate func parseResponseData(data: Data) -> SomeParsedObject? {
// 解析数据的逻辑
return nil
}
private
:访问范围更窄,只能在定义它的声明的上下文内部访问。例如,NetworkRequest
类内部有一个私有属性requestTimeout
用于控制请求超时时间,外部无需访问:
public class NetworkRequest {
public let url: URL
private var requestTimeout: TimeInterval = 10.0
public init(url: URL) {
self.url = url
}
//...
}
2. 封装原则
- 隐藏实现细节:将框架内部的复杂实现细节封装起来,只暴露给外部必要的接口。比如上述
NetworkRequest
类,外部使用者只需要知道通过sendRequest
方法发起请求,而不需要了解内部如何创建URLSession
任务等细节。 - 数据封装:通过将属性设为私有,提供公共的访问方法(如
getter
和setter
)来控制对数据的访问。例如,NetworkRequest
类可以提供一个public
的timeout
属性来设置超时时间,同时在setter
中进行合法性检查:
public class NetworkRequest {
public let url: URL
private var requestTimeout: TimeInterval = 10.0
public var timeout: TimeInterval {
get {
return requestTimeout
}
set {
if newValue > 0 {
requestTimeout = newValue
}
}
}
public init(url: URL) {
self.url = url
}
public func sendRequest(completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
// 使用 requestTimeout 来配置请求超时
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = requestTimeout
let session = URLSession(configuration: config)
let task = session.dataTask(with: url, completionHandler: completion)
task.resume()
}
}
3. 平衡封装性与外部访问需求
- 提供清晰的接口文档:明确说明框架对外公开的接口及其用途,帮助外部使用者了解如何使用框架,同时让他们无需关心内部实现。
- 使用协议(Protocols):通过定义协议来暴露框架的部分功能,外部使用者可以基于协议来编写代码,而不依赖于具体的类实现。例如,定义一个
NetworkRequestable
协议:
public protocol NetworkRequestable {
var url: URL { get }
func sendRequest(completion: @escaping (Data?, URLResponse?, Error?) -> Void)
}
public class NetworkRequest: NetworkRequestable {
public let url: URL
public init(url: URL) {
self.url = url
}
public func sendRequest(completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
// 实际发送请求的代码
let task = URLSession.shared.dataTask(with: url, completionHandler: completion)
task.resume()
}
}
这样外部使用者可以使用 NetworkRequestable
协议类型来操作请求,而不需要依赖于具体的 NetworkRequest
类,提高了代码的灵活性和可维护性。