MST

星途 面试题库

面试题:Swift访问控制在框架设计中的应用及优化

假设你正在设计一个大型的Swift框架,其中包含多个子模块和复杂的类层次结构。请描述你如何运用访问控制和封装原则来确保框架的安全性、可维护性和易用性。如何平衡框架内部实现的封装性与外部使用者对必要接口的访问需求?请结合实际场景,给出一些具体的策略和示例代码。
47.7万 热度难度
编程语言Swift

知识考点

AI 面试

面试题答案

一键面试

1. 访问控制

Swift 提供了四种访问控制级别:openpublicinternalfileprivateprivate

  • openpublic:用于框架对外公开的接口,允许框架外部的代码访问和继承(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 任务等细节。
  • 数据封装:通过将属性设为私有,提供公共的访问方法(如 gettersetter)来控制对数据的访问。例如,NetworkRequest 类可以提供一个 publictimeout 属性来设置超时时间,同时在 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 类,提高了代码的灵活性和可维护性。