Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
  • 第 11 章:构建可复用的协议化框架

第 11 章:构建可复用的协议化框架

11.3 实战案例:实现一个可复用的日志系统

日志系统是软件开发中不可或缺的工具,用于记录运行时信息、调试问题或分析性能。一个设计良好的日志系统应该支持多种输出方式(如控制台、文件、网络)、日志级别过滤,并且易于扩展。Swift 的协议化编程(Protocol-Oriented Programming, POP)为构建这样的系统提供了理想的工具。本节将通过一个实战案例,展示如何设计和实现一个可复用的日志系统。

设计目标

我们的日志系统需要满足以下要求:

  1. 灵活性:支持多种日志输出方式(控制台、文件等)。
  2. 可配置性:允许设置日志级别(如调试、信息、错误)。
  3. 模块化:通过协议解耦核心逻辑与具体实现。
  4. 复用性:可以在不同项目中直接使用或扩展。

实现步骤

1. 定义核心协议

首先,定义日志系统的核心接口:

// 日志级别枚举
enum LogLevel: Int, Comparable {
    case debug = 0
    case info = 1
    case warning = 2
    case error = 3
    
    static func < (lhs: LogLevel, rhs: LogLevel) -> Bool {
        lhs.rawValue < rhs.rawValue
    }
}

// 日志输出协议
protocol LogOutput {
    func log(_ message: String, level: LogLevel)
}

// 日志管理协议
protocol LogManaging {
    mutating func addOutput(_ output: LogOutput)
    func log(_ message: String, level: LogLevel)
}
  • LogLevel 定义日志级别,并实现 Comparable 以便比较。
  • LogOutput 抽象日志输出的行为。
  • LogManaging 定义日志管理的核心功能。

2. 实现日志管理器

创建一个日志管理器,作为系统的核心组件:

struct Logger: LogManaging {
    private var outputs: [LogOutput]
    private let minimumLevel: LogLevel
    
    init(minimumLevel: LogLevel = .debug) {
        self.outputs = []
        self.minimumLevel = minimumLevel
    }
    
    mutating func addOutput(_ output: LogOutput) {
        outputs.append(output)
    }
    
    func log(_ message: String, level: LogLevel) {
        guard level >= minimumLevel else { return }
        let timestamp = DateFormatter.localizedString(from: Date(), dateStyle: .short, timeStyle: .medium)
        let formattedMessage = "[\(timestamp)] [\(level)] \(message)"
        outputs.forEach { $0.log(formattedMessage, level: level) }
    }
}
  • Logger 管理多个 LogOutput,并根据 minimumLevel 过滤日志。
  • 日志消息自动添加时间戳和级别信息。

3. 实现具体日志输出

提供几种常见的日志输出实现:

// 控制台输出
struct ConsoleLogOutput: LogOutput {
    func log(_ message: String, level: LogLevel) {
        print(message)
    }
}

// 文件输出
struct FileLogOutput: LogOutput {
    private let fileURL: URL
    
    init(fileURL: URL) {
        self.fileURL = fileURL
    }
    
    func log(_ message: String, level: LogLevel) {
        let data = (message + "\n").data(using: .utf8)
        try? data?.append(to: fileURL)
    }
}

// 扩展 Data 以支持文件追加
extension Data {
    func append(to url: URL) throws {
        if FileManager.default.fileExists(atPath: url.path) {
            let fileHandle = try FileHandle(forWritingTo: url)
            fileHandle.seekToEndOfFile()
            fileHandle.write(self)
            fileHandle.closeFile()
        } else {
            try write(to: url)
        }
    }
}
  • ConsoleLogOutput 将日志输出到控制台。
  • FileLogOutput 将日志追加到文件中,支持持久化。

4. 提供便捷接口

通过扩展为常用场景提供默认实现和便捷方法:

extension LogManaging {
    // 默认调试日志
    func debug(_ message: String) {
        log(message, level: .debug)
    }
    
    // 默认信息日志
    func info(_ message: String) {
        log(message, level: .info)
    }
    
    // 默认警告日志
    func warning(_ message: String) {
        log(message, level: .warning)
    }
    
    // 默认错误日志
    func error(_ message: String) {
        log(message, level: .error)
    }
}
  • 这些方法简化了日志的使用,开发者无需手动指定级别。

5. 使用示例

以下是如何在项目中使用这个日志系统的示例:

// 初始化日志系统
var logger = Logger(minimumLevel: .info) // 只记录 info 及以上级别
let consoleOutput = ConsoleLogOutput()
let fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("app.log")
let fileOutput = FileLogOutput(fileURL: fileURL)

logger.addOutput(consoleOutput)
logger.addOutput(fileOutput)

// 记录日志
logger.debug("This will not be logged due to minimum level.") // 被过滤
logger.info("App started successfully.")
logger.warning("Low battery warning.")
logger.error("Failed to load data.")

// 检查文件内容
if let logContent = try? String(contentsOf: fileURL) {
    print("Log file content:\n\(logContent)")
}

输出示例(控制台):

[3/18/25, 10:30:45 AM] [info] App started successfully.
[3/18/25, 10:30:46 AM] [warning] Low battery warning.
[3/18/25, 10:30:47 AM] [error] Failed to load data.

文件 app.log 中会保存相同的日志内容。

实现优势

  1. 模块化:日志管理器(Logger)和输出(LogOutput)通过协议解耦,独立开发和测试。
  2. 可扩展性:新增输出方式(如网络上传)只需实现 LogOutput 协议。
  3. 灵活性:支持动态添加输出,并通过 minimumLevel 控制日志粒度。
  4. 复用性:该系统可以直接嵌入任何 Swift 项目,只需配置输出和级别。

扩展建议

  • 异步支持:将 log 方法改为异步执行,避免阻塞主线程。
  • 格式化选项:允许用户自定义日志格式。
  • 网络输出:添加一个 NetworkLogOutput,将日志上传到服务器。

例如,异步日志的简单扩展:

extension Logger {
    func logAsync(_ message: String, level: LogLevel) {
        DispatchQueue.global(qos: .background).async {
            self.log(message, level: level)
        }
    }
}

小结

通过这个实战案例,我们实现了一个可复用的日志系统,利用协议化编程实现了核心逻辑与具体输出的分离。这种设计方法不仅满足了灵活性和模块化的需求,还为未来的扩展留下了空间。开发者可以将此系统直接应用于项目,或根据需要添加新的输出方式和功能。

Last Updated:: 3/18/25, 4:45 PM