¥
立即购买

iOS组件README文档生成器

17 浏览
1 试用
0 购买
Dec 5, 2025更新

本提示词专为iOS开发者设计,能够根据特定功能或组件信息,生成结构完整、内容专业的README文档章节。通过系统化的分析流程,确保输出内容技术准确、逻辑清晰,涵盖功能描述、使用说明、代码示例等核心要素,帮助开发者快速创建高质量的文档,提升项目可维护性和团队协作效率。适用于新功能介绍、组件文档编写、技术方案说明等多种iOS开发场景。

AsyncImageCache

一个支持异步图片加载、内存/磁盘双缓存与失效策略的轻量组件。适用于瀑布流、Feed、头像等高频图片场景,提供预取与缓存清理 API,同时支持 SwiftUI 与 UIKit 集成。目标平台 iOS 13+。

功能概述

AsyncImageCache 基于 URLSession 和 NSCache 实现高性能图片加载与缓存:

  • 异步下载并自动解码优化,降低主线程阻塞与内存峰值
  • 内存/磁盘双缓存,支持自定义缓存容量与过期策略
  • 可配置最大并发下载数、占位图与重试策略
  • 提供预取、单条目移除、过期清理与全量清理 API
  • 可直接用于 SwiftUI 与 UIKit,简化列表/瀑布流/头像等场景的图片处理

适用场景:

  • 列表类(Feed/社交/商城)中密集的网络图片展示
  • 瀑布流布局的大图缩略显示
  • 头像、封面等频繁复用的图片
  • 需要可控缓存策略与线程安全的图片加载组件

特性列表

  • 异步图片下载:基于 URLSession,支持网络重试
  • 双缓存:NSCache 内存缓存 + 磁盘缓存,可配置容量与过期时间
  • 失效策略:自动判定磁盘缓存是否过期并清理
  • 解码优化:CGImageSource 下采样解码,降低内存占用
  • 并发控制:可配置最大并发下载数
  • 占位图:SwiftUI 与 UIKit 均支持占位图
  • 预取接口:批量预取图片,提升滚动体验
  • 缓存管理:清理内存缓存、清理过期磁盘缓存、移除单条目
  • 线程安全:磁盘操作串行队列,NSCache 原生线程安全
  • 集成友好:支持 SwiftPM,适配 iOS 13+,SwiftUI 与 UIKit 示例

使用方法

集成步骤

  1. 通过 Swift Package Manager 集成

    • Xcode 菜单:File > Add Packages...
    • 输入组件仓库地址(示例):https://your-repo-url/AsyncImageCache.git
    • 选择版本规则(例如:Up to Next Major)
    • 将 AsyncImageCache 添加到目标 Target

    注意:请将示例仓库地址替换为实际仓库 URL。

  2. 最低系统版本 iOS 13+

    • 在项目的 Deployment Info 中将 iOS Minimum Target 设置为 13.0 或更高
  3. 权限与网络

    • 如需加载非 HTTPS 资源,请在 Info.plist 配置 ATS 例外(建议使用 HTTPS)

代码示例

import Foundation
import UIKit
import SwiftUI
import CryptoKit
import ImageIO

// MARK: - Core

public final class AsyncImageCache {
    public struct Configuration {
        public var maxConcurrentDownloads: Int            // 最大并发下载数
        public var memoryCountLimit: Int                  // 内存缓存数量限制(NSCache countLimit)
        public var diskCapacityMB: Int                    // 磁盘容量(MB,超出后依策略清理)
        public var diskExpiration: TimeInterval           // 磁盘缓存过期时间(秒)
        public var retryCount: Int                        // 网络重试次数
        public var retryDelay: TimeInterval               // 重试间隔(秒)
        public var decodePreferredSize: CGSize?           // 下采样解码目标尺寸(像素按屏幕scale换算)

        public static let `default` = Configuration(
            maxConcurrentDownloads: 6,
            memoryCountLimit: 500,
            diskCapacityMB: 512,
            diskExpiration: 60 * 60 * 24 * 7, // 7 天
            retryCount: 2,
            retryDelay: 0.5,
            decodePreferredSize: CGSize(width: 1024, height: 1024)
        )

        public init(
            maxConcurrentDownloads: Int = 6,
            memoryCountLimit: Int = 500,
            diskCapacityMB: Int = 512,
            diskExpiration: TimeInterval = 60 * 60 * 24 * 7,
            retryCount: Int = 2,
            retryDelay: TimeInterval = 0.5,
            decodePreferredSize: CGSize? = CGSize(width: 1024, height: 1024)
        ) {
            self.maxConcurrentDownloads = maxConcurrentDownloads
            self.memoryCountLimit = memoryCountLimit
            self.diskCapacityMB = diskCapacityMB
            self.diskExpiration = diskExpiration
            self.retryCount = retryCount
            self.retryDelay = retryDelay
            self.decodePreferredSize = decodePreferredSize
        }
    }

    public static let shared = AsyncImageCache()

    private let memory = NSCache<NSURL, UIImage>()
    private let diskDirectory: URL
    private let diskQueue = DispatchQueue(label: "AsyncImageCache.disk")
    private let session: URLSession
    private let config: Configuration

    public init(configuration: Configuration = .default) {
        self.config = configuration
        memory.countLimit = configuration.memoryCountLimit

        // Create disk directory
        let caches = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
        self.diskDirectory = caches.appendingPathComponent("AsyncImageCache", isDirectory: true)
        try? FileManager.default.createDirectory(at: diskDirectory, withIntermediateDirectories: true)

        // URLSession with concurrency control
        let sessionConfig = URLSessionConfiguration.default
        sessionConfig.requestCachePolicy = .reloadIgnoringLocalCacheData
        sessionConfig.httpMaximumConnectionsPerHost = configuration.maxConcurrentDownloads
        sessionConfig.waitsForConnectivity = true
        self.session = URLSession(configuration: sessionConfig)
    }

    // Public API

    public func image(for url: URL, bypassCache: Bool = false) async throws -> UIImage {
        let key = url as NSURL

        // Memory cache
        if !bypassCache, let img = memory.object(forKey: key) {
            return img
        }

        // Disk cache
        if !bypassCache, let data = try? readFromDisk(url: url), let img = decodeImage(data: data) {
            memory.setObject(img, forKey: key)
            return img
        }

        // Network
        let data = try await download(url: url)
        if let img = decodeImage(data: data) {
            memory.setObject(img, forKey: key)
            writeToDisk(data: data, url: url)
            return img
        } else {
            throw NSError(domain: "AsyncImageCache", code: -1, userInfo: [NSLocalizedDescriptionKey: "Image decode failed"])
        }
    }

    public func prefetch(urls: [URL]) async {
        await withTaskGroup(of: Void.self) { group in
            for url in urls {
                group.addTask { _ = try? await self.image(for: url) }
            }
        }
    }

    public func clearMemory() {
        memory.removeAllObjects()
    }

    public func clearDisk(expiredOnly: Bool = true) {
        diskQueue.async {
            let fm = FileManager.default
            let resourceKeys: Set<URLResourceKey> = [.contentModificationDateKey, .totalFileSizeKey]
            guard let files = try? fm.contentsOfDirectory(at: self.diskDirectory, includingPropertiesForKeys: Array(resourceKeys), options: .skipsHiddenFiles) else { return }

            let now = Date()
            var totalBytes = 0

            // Calculate total size and collect expired
            var toRemove: [URL] = []
            for file in files {
                let values = try? file.resourceValues(forKeys: resourceKeys)
                let mod = values?.contentModificationDate ?? now
                let expired = now.timeIntervalSince(mod) > self.config.diskExpiration
                if expired || !expiredOnly {
                    toRemove.append(file)
                }
                totalBytes += (values?.totalFileSize ?? 0)
            }

            // Capacity-based trimming if needed
            let capacityBytes = self.config.diskCapacityMB * 1024 * 1024
            if totalBytes > capacityBytes {
                // Sort by oldest first
                let sorted = files.sorted {
                    let a = (try? $0.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate) ?? now
                    let b = (try? $1.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate) ?? now
                    return (a ?? now) < (b ?? now)
                }
                var bytes = totalBytes
                for file in sorted where bytes > capacityBytes {
                    if let size = (try? file.resourceValues(forKeys: [.totalFileSizeKey]).totalFileSize) {
                        try? fm.removeItem(at: file)
                        bytes -= size ?? 0
                    }
                }
            }

            // Remove expired or all
            toRemove.forEach { try? fm.removeItem(at: $0) }
        }
    }

    public func remove(url: URL) {
        diskQueue.async {
            let fm = FileManager.default
            let path = self.fileURL(for: url)
            try? fm.removeItem(at: path)
        }
        memory.removeObject(forKey: url as NSURL)
    }

    // MARK: - Internals

    private func download(url: URL) async throws -> Data {
        var lastError: Error?
        for attempt in 0...config.retryCount {
            do {
                let (data, resp) = try await session.data(from: url)
                if let http = resp as? HTTPURLResponse, (200..<300).contains(http.statusCode) {
                    return data
                } else {
                    throw NSError(domain: "AsyncImageCache", code: (resp as? HTTPURLResponse)?.statusCode ?? -1, userInfo: [NSLocalizedDescriptionKey: "HTTP error"])
                }
            } catch {
                lastError = error
                if attempt < config.retryCount {
                    try await Task.sleep(nanoseconds: UInt64(config.retryDelay * 1_000_000_000))
                    continue
                }
            }
        }
        throw lastError ?? NSError(domain: "AsyncImageCache", code: -1, userInfo: [NSLocalizedDescriptionKey: "Unknown download error"])
    }

    private func decodeImage(data: Data) -> UIImage? {
        if let target = config.decodePreferredSize {
            let maxPixel = Int(max(target.width, target.height) * UIScreen.main.scale)
            guard let source = CGImageSourceCreateWithData(data as CFData, nil) else { return UIImage(data: data) }
            let options: [NSString: Any] = [
                kCGImageSourceCreateThumbnailFromImageAlways: true,
                kCGImageSourceCreateThumbnailWithTransform: true,
                kCGImageSourceThumbnailMaxPixelSize: maxPixel,
                kCGImageSourceShouldCacheImmediately: true
            ]
            if let cg = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary) {
                return UIImage(cgImage: cg)
            }
        }
        return UIImage(data: data)
    }

    private func readFromDisk(url: URL) throws -> Data? {
        var result: Data?
        diskQueue.sync {
            let fm = FileManager.default
            let path = fileURL(for: url)
            guard fm.fileExists(atPath: path.path) else { return }
            // Expiration check
            let now = Date()
            let mod = (try? fm.attributesOfItem(atPath: path.path)[.modificationDate] as? Date) ?? now
            let expired = now.timeIntervalSince(mod) > config.diskExpiration
            if expired {
                try? fm.removeItem(at: path)
                return
            }
            result = try? Data(contentsOf: path)
        }
        return result
    }

    private func writeToDisk(data: Data, url: URL) {
        diskQueue.async {
            let fm = FileManager.default
            let path = self.fileURL(for: url)
            do {
                try data.write(to: path, options: .atomic)
                // Update modification date to now
                try fm.setAttributes([.modificationDate: Date()], ofItemAtPath: path.path)
            } catch {
                // Ignore disk write errors
            }
        }
    }

    private func fileURL(for url: URL) -> URL {
        let hash = SHA256.hash(data: Data(url.absoluteString.utf8))
        let name = hash.map { String(format: "%02x", $0) }.joined() + ".img"
        return diskDirectory.appendingPathComponent(name)
    }
}

// MARK: - SwiftUI helper

public struct AsyncCachedImage: View {
    private let url: URL?
    private let placeholder: Image
    private let cache: AsyncImageCache

    @State private var image: UIImage?
    @State private var isLoading = false

    public init(url: URL?, placeholder: Image = Image(systemName: "photo"), cache: AsyncImageCache = .shared) {
        self.url = url
        self.placeholder = placeholder
        self.cache = cache
    }

    public var body: some View {
        Group {
            if let ui = image {
                Image(uiImage: ui).resizable().scaledToFill()
            } else {
                placeholder.resizable().scaledToFit().opacity(0.3)
            }
        }
        .onAppear {
            guard !isLoading, let url else { return }
            isLoading = true
            Task {
                defer { isLoading = false }
                if let img = try? await cache.image(for: url) {
                    self.image = img
                }
            }
        }
    }
}

// MARK: - UIKit helper

public extension UIImageView {
    func setImage(
        from url: URL?,
        placeholder: UIImage? = nil,
        cache: AsyncImageCache = .shared
    ) {
        self.image = placeholder
        guard let url else { return }
        Task { [weak self] in
            guard let self else { return }
            if let img = try? await cache.image(for: url) {
                // 更新到主线程
                await MainActor.run { self.image = img }
            }
        }
    }
}

// MARK: - Usage Examples

// SwiftUI usage:
/*
struct FeedCell: View {
    let avatarURL: URL?
    var body: some View {
        HStack {
            AsyncCachedImage(url: avatarURL, placeholder: Image(systemName: "person.crop.circle"))
                .frame(width: 40, height: 40)
                .clipShape(Circle())
            Text("Username")
            Spacer()
        }
    }
}
*/

// UIKit usage:
/*
class AvatarCell: UICollectionViewCell {
    let avatar = UIImageView()

    override init(frame: CGRect) {
        super.init(frame: frame)
        avatar.contentMode = .scaleAspectFill
        avatar.clipsToBounds = true
        avatar.layer.cornerRadius = 20
        contentView.addSubview(avatar)
        avatar.frame = CGRect(x: 10, y: 10, width: 40, height: 40)
    }

    required init?(coder: NSCoder) { fatalError() }

    func configure(url: URL?) {
        avatar.setImage(from: url, placeholder: UIImage(systemName: "person.crop.circle"))
    }
}
*/

// Prefetch & cache management:
/*
let urls: [URL] = [...] // 即将显示的图片链接
Task { await AsyncImageCache.shared.prefetch(urls: urls) }

// 清理内存缓存
AsyncImageCache.shared.clearMemory()

// 清理过期磁盘缓存
AsyncImageCache.shared.clearDisk(expiredOnly: true)

// 移除特定条目
if let u = URL(string: "https://example.com/image.jpg") {
    AsyncImageCache.shared.remove(url: u)
}
*/

注意事项

  • 并发与去重:示例实现控制了最大并发,但未对同一 URL 的并发请求去重。高并发下建议在内部增加去重/合并(如共享任务或批处理)。
  • 列表复用与取消:UIKit 列表中 cell 复用时应考虑取消上一次任务或引入可取消句柄,以避免图片错位。示例中为简化展示未包含取消逻辑。
  • 下采样解码:为降低内存占用,建议根据显示尺寸设置 decodePreferredSize(按屏幕 scale 转换为像素)。过小会影响清晰度,过大会增加内存。
  • 过期策略:磁盘过期通过文件修改时间判定;当超过 diskExpiration 时读取会自动删除。可按业务调整时长。
  • 磁盘容量:超过 diskCapacityMB 时按最早修改时间回收,策略可根据需要扩展为 LRU。
  • 网络重试:默认进行有限次重试并等待 delay,若服务端返回非 2xx 状态码会抛出错误。
  • ATS 与协议:建议使用 HTTPS。若使用 HTTP,请在 Info.plist 配置 NSAppTransportSecurity。
  • Swift 工具链:示例使用 async/await(Xcode 13+)。若需支持更旧工具链,可将 API 改为回调或 Combine。
  • 图片版权与安全:请确保图片来源合法,必要时校验 MIME 类型与数据大小。

相关链接

以上内容为 AsyncImageCache 的基础 README 章节,可根据实际仓库结构补充更多扩展接口与高级用法说明。

ThemeKit for SwiftUI

基于 SwiftUI Environment 的主题管理组件,统一管理颜色、字体、圆角等视觉变量,支持动态深色模式与高对比显示,可运行时切换、持久化与远端配置。适用于多品牌皮肤与大型项目的视觉一致性建设。目标平台:iOS 14+,集成复杂度:简单。

功能概述

ThemeKit 通过 SwiftUI 的 Environment 和 EnvironmentObject 将主题作为一等公民贯穿视图层,提供统一的设计令牌(Design Tokens)访问方式。开发者可在不入侵业务代码的前提下,按品牌或场景定义主题包并在运行时切换;同时支持将用户选择持久化,并可接入远端配置以实现灰度与个性化方案。

适用场景:

  • 多品牌或多皮肤项目的视觉规范落地与切换
  • 统一颜色、字体、圆角等设计令牌的访问和管理
  • 动态适配系统深色模式与高对比设置
  • 需要远端下发主题配置的大型应用

特性列表

  • 基于 SwiftUI Environment 的轻量集成与全局传递
  • 统一的 Design Tokens:颜色、字体、圆角等
  • 动态深色模式与高对比显示自动适配
  • 运行时主题切换与全局刷新
  • 多种持久化策略(示例含 UserDefaults)
  • 远端配置兼容(可从 JSON 解码为主题包)
  • 协议化设计与扩展点,方便自定义与扩展
  • 适配 iOS 14+,示例不依赖 async/await

使用方法

集成步骤

  1. 引入
    • 通过 Swift Package Manager 引入组件,或直接将源码文件添加到项目中。
  2. 在 App 根视图注入主题
    • 创建 ThemeManager 并作为 EnvironmentObject 注入
    • 将当前主题通过自定义 EnvironmentKey 注入到视图层
  3. 在业务视图中通过 @Environment 访问主题令牌

代码示例

import SwiftUI

// MARK: - Design Tokens

struct ColorSet {
    let light: Color
    let dark: Color
    let highContrastLight: Color?
    let highContrastDark: Color?

    func resolved(colorScheme: ColorScheme, contrast: AccessibilityContrast) -> Color {
        switch (colorScheme, contrast) {
        case (.light, .increased):
            return highContrastLight ?? light
        case (.dark, .increased):
            return highContrastDark ?? dark
        case (.light, _):
            return light
        case (.dark, _):
            return dark
        @unknown default:
            return light
        }
    }
}

struct Colors {
    let primary: ColorSet
    let background: ColorSet
    let text: ColorSet
}

struct Typography {
    let title: Font
    let body: Font
    let caption: Font
}

struct CornerRadius {
    let small: CGFloat
    let medium: CGFloat
    let large: CGFloat
}

// MARK: - Theme Protocol

protocol Theme {
    var id: String { get }
    var colors: Colors { get }
    var typography: Typography { get }
    var cornerRadius: CornerRadius { get }
}

// 示例主题:系统默认主题
struct SystemTheme: Theme {
    let id = "system"
    let colors = Colors(
        primary: ColorSet(
            light: Color.blue,
            dark: Color.blue.opacity(0.85),
            highContrastLight: Color(hue: 0.61, saturation: 1.0, brightness: 0.95),
            highContrastDark: Color(hue: 0.61, saturation: 1.0, brightness: 0.85)
        ),
        background: ColorSet(
            light: Color(UIColor.systemBackground),
            dark: Color(UIColor.systemBackground),
            highContrastLight: Color.white,
            highContrastDark: Color.black
        ),
        text: ColorSet(
            light: Color.primary,
            dark: Color.primary,
            highContrastLight: Color.black,
            highContrastDark: Color.white
        )
    )
    let typography = Typography(
        title: .system(.title, design: .rounded).weight(.semibold),
        body: .system(.body, design: .default),
        caption: .system(.caption, design: .default)
    )
    let cornerRadius = CornerRadius(small: 6, medium: 10, large: 16)
}

// 示例主题:品牌 A
struct BrandATheme: Theme {
    let id = "brandA"
    let colors = Colors(
        primary: ColorSet(
            light: Color(hex: "#0055FF"),
            dark: Color(hex: "#77A8FF"),
            highContrastLight: Color(hex: "#0033CC"),
            highContrastDark: Color(hex: "#99BBFF")
        ),
        background: ColorSet(
            light: Color(hex: "#F7F9FC"),
            dark: Color(hex: "#0B0F14"),
            highContrastLight: Color.white,
            highContrastDark: Color.black
        ),
        text: ColorSet(
            light: Color(hex: "#0A1F44"),
            dark: Color(hex: "#E6EDF7"),
            highContrastLight: Color.black,
            highContrastDark: Color.white
        )
    )
    let typography = Typography(
        title: .system(size: 24, weight: .bold, design: .rounded),
        body: .system(size: 17, weight: .regular, design: .default),
        caption: .system(size: 13, weight: .regular, design: .default)
    )
    let cornerRadius = CornerRadius(small: 4, medium: 8, large: 12)
}

// MARK: - Environment Key

private struct ThemeKey: EnvironmentKey {
    static let defaultValue: Theme = SystemTheme()
}

extension EnvironmentValues {
    var theme: Theme {
        get { self[ThemeKey.self] }
        set { self[ThemeKey.self] = newValue }
    }
}

// MARK: - Persistence Strategy

enum PersistenceStrategy {
    case none
    case userDefaults(key: String)

    func save(themeID: String) {
        switch self {
        case .none: break
        case .userDefaults(let key):
            UserDefaults.standard.set(themeID, forKey: key)
        }
    }

    func restoredThemeID() -> String? {
        switch self {
        case .none: return nil
        case .userDefaults(let key):
            return UserDefaults.standard.string(forKey: key)
        }
    }
}

// MARK: - Theme Manager

final class ThemeManager: ObservableObject {
    @Published private(set) var current: Theme
    private let persistence: PersistenceStrategy

    init(defaultTheme: Theme = SystemTheme(),
         persistence: PersistenceStrategy = .none) {
        self.persistence = persistence
        if let restoredID = persistence.restoredThemeID(),
           let restoredTheme = ThemeManager.resolveTheme(id: restoredID) {
            self.current = restoredTheme
        } else {
            self.current = defaultTheme
        }
    }

    func apply(_ theme: Theme) {
        current = theme
        persistence.save(themeID: theme.id)
    }

    // 注册或从远端解析后的主题可在此处扩展解析
    static func resolveTheme(id: String) -> Theme? {
        switch id {
        case "system": return SystemTheme()
        case "brandA": return BrandATheme()
        default: return nil
        }
    }

    // MARK: - Remote Config (示例)
    struct RemoteThemeDTO: Decodable {
        struct ColorPair: Decodable {
            let light: String
            let dark: String
            let highContrastLight: String?
            let highContrastDark: String?
        }
        struct ColorsDTO: Decodable {
            let primary: ColorPair
            let background: ColorPair
            let text: ColorPair
        }
        struct CornerDTO: Decodable {
            let small: CGFloat
            let medium: CGFloat
            let large: CGFloat
        }

        let id: String
        let colors: ColorsDTO
        let cornerRadius: CornerDTO
        // 字体远端下发通常涉及命名与尺寸,示例省略或采用系统默认
    }

    func loadRemoteTheme(from url: URL,
                         completion: @escaping (Result<Theme, Error>) -> Void) {
        URLSession.shared.dataTask(with: url) { data, _, error in
            if let error = error {
                completion(.failure(error)); return
            }
            guard let data = data else {
                completion(.failure(NSError(domain: "ThemeKit", code: -1,
                                            userInfo: [NSLocalizedDescriptionKey: "Empty data"])))
                return
            }
            do {
                let dto = try JSONDecoder().decode(RemoteThemeDTO.self, from: data)
                let theme = Self.theme(from: dto)
                completion(.success(theme))
            } catch {
                completion(.failure(error))
            }
        }.resume()
    }

    private static func theme(from dto: RemoteThemeDTO) -> Theme {
        let colors = Colors(
            primary: ColorSet(
                light: Color(hex: dto.colors.primary.light),
                dark: Color(hex: dto.colors.primary.dark),
                highContrastLight: dto.colors.primary.highContrastLight.map(Color.init(hex:)),
                highContrastDark: dto.colors.primary.highContrastDark.map(Color.init(hex:))
            ),
            background: ColorSet(
                light: Color(hex: dto.colors.background.light),
                dark: Color(hex: dto.colors.background.dark),
                highContrastLight: dto.colors.background.highContrastLight.map(Color.init(hex:)),
                highContrastDark: dto.colors.background.highContrastDark.map(Color.init(hex:))
            ),
            text: ColorSet(
                light: Color(hex: dto.colors.text.light),
                dark: Color(hex: dto.colors.text.dark),
                highContrastLight: dto.colors.text.highContrastLight.map(Color.init(hex:)),
                highContrastDark: dto.colors.text.highContrastDark.map(Color.init(hex:))
            )
        )
        let corners = CornerRadius(
            small: dto.cornerRadius.small,
            medium: dto.cornerRadius.medium,
            large: dto.cornerRadius.large
        )
        // 远端未指定字体时采用系统默认
        let typography = Typography(
            title: .system(.title, design: .rounded).weight(.semibold),
            body: .system(.body, design: .default),
            caption: .system(.caption, design: .default)
        )
        return GenericTheme(id: dto.id, colors: colors, typography: typography, cornerRadius: corners)
    }
}

// 通用主题封装,便于远端解析
struct GenericTheme: Theme {
    let id: String
    let colors: Colors
    let typography: Typography
    let cornerRadius: CornerRadius
}

// MARK: - Color Hex Support

extension Color {
    init(hex: String) {
        let cleaned = hex.replacingOccurrences(of: "#", with: "")
        var value: UInt64 = 0
        Scanner(string: cleaned).scanHexInt64(&value)

        let r, g, b: Double
        let a: Double = 1.0

        switch cleaned.count {
        case 6:
            r = Double((value & 0xFF0000) >> 16) / 255.0
            g = Double((value & 0x00FF00) >> 8) / 255.0
            b = Double(value & 0x0000FF) / 255.0
        default:
            r = 0; g = 0; b = 0
        }
        self = Color(.sRGB, red: r, green: g, blue: b, opacity: a)
    }
}

// MARK: - App Integration

@main
struct ExampleApp: App {
    @StateObject private var themeManager = ThemeManager(
        defaultTheme: SystemTheme(),
        persistence: .userDefaults(key: "ThemeKit.currentThemeID")
    )

    var body: some Scene {
        WindowGroup {
            ContentView()
                // 运行时切换会触发视图重建,Environment 跟随更新
                .environment(\.theme, themeManager.current)
                .environmentObject(themeManager)
        }
    }
}

// MARK: - Usage in Views

struct ContentView: View {
    @Environment(\.theme) private var theme
    @Environment(\.colorScheme) private var colorScheme
    @Environment(\.accessibilityContrast) private var contrast
    @EnvironmentObject private var themeManager: ThemeManager

    var body: some View {
        VStack(spacing: 16) {
            Text("ThemeKit for SwiftUI")
                .font(theme.typography.title)
                .foregroundColor(theme.colors.text.resolved(colorScheme: colorScheme, contrast: contrast))

            RoundedRectangle(cornerRadius: theme.cornerRadius.medium)
                .fill(theme.colors.primary.resolved(colorScheme: colorScheme, contrast: contrast))
                .frame(height: 56)
                .overlay(Text("Primary").font(theme.typography.body).foregroundColor(.white))

            HStack {
                Button("系统主题") { themeManager.apply(SystemTheme()) }
                Button("品牌 A") { themeManager.apply(BrandATheme()) }
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
        .background(theme.colors.background.resolved(colorScheme: colorScheme, contrast: contrast))
    }
}

// MARK: - Remote Theme Load Example

struct RemoteThemeLoaderView: View {
    @EnvironmentObject private var themeManager: ThemeManager
    @State private var loading = false
    @State private var errorMessage: String?

    var body: some View {
        VStack(spacing: 12) {
            Button(loading ? "加载中..." : "从远端加载主题") {
                guard !loading else { return }
                loading = true
                let url = URL(string: "https://example.com/theme.json")!
                themeManager.loadRemoteTheme(from: url) { result in
                    DispatchQueue.main.async {
                        loading = false
                        switch result {
                        case .success(let theme):
                            themeManager.apply(theme)
                        case .failure(let error):
                            errorMessage = error.localizedDescription
                        }
                    }
                }
            }
            if let errorMessage = errorMessage {
                Text(errorMessage).foregroundColor(.red).font(.caption)
            }
        }
        .padding()
    }
}

注意事项

  • Environment 更新机制:将 .environment(\.theme, themeManager.current) 放在根视图或导航根节点处,确保 @Published 变更触发视图重建并传播环境值。
  • 高对比与深色模式:
    • 使用 @Environment(\.accessibilityContrast)@Environment(\.colorScheme) 获取当前设置,颜色应通过 ColorSet.resolved(...) 适配。
  • 字体与动态类型:
    • 示例使用 Font.system,实际项目若需自定义字体,请确保资源可用并与动态类型兼容(PreferredContentSizeCategory)。
  • 远端配置:
    • 建议仅下发安全的设计令牌(颜色、圆角、部分字号)。字体文件分发需考虑授权与体积。
    • 对远端数据做校验与降级策略,避免非法值导致渲染异常。
  • 持久化策略:
    • UserDefaults 适合轻量主题 ID 存储;如需多进程共享可使用 App Group。
  • 与 UIKit/Asset Catalog 的颜色混用:
    • 若使用 Asset Catalog 的动态颜色,确保与 ThemeKit 的动态解析不冲突,避免双重适配导致色彩异常。
  • 性能建议:
    • 保持主题令牌为轻量值类型;避免在 resolved 中执行重计算或 I/O。

相关链接

BiometricAuthManager

封装 LocalAuthentication 的生物识别授权管理器,支持 Face ID / Touch ID 验证及回退密码策略,统一错误映射与提示文案,提供 async/await 与回调双API、Keychain 安全绑定与会话超时控制。适用于敏感操作授权、账号解锁、支付确认等场景。

功能概述

BiometricAuthManager 提供一套一致、可配置的生物识别授权解决方案:

  • 统一封装 LAContext,支持 Face ID / Touch ID。
  • 支持回退策略:系统设备密码(Device Passcode)或自定义应用密码流程。
  • 提供统一的错误码与提示文案,便于展示本地化 UI。
  • 提供 async/await API(iOS 13+ 回溯可用)与回调式 API(iOS 12+)。
  • 结合 Keychain 的 SecAccessControl,安全存储会话令牌,实现“会话超时”与“生物识别绑定”。
  • 内置 UI 提示建议与测试建议,覆盖常见边界场景。

适用场景:

  • 支付确认、转账等高价值操作的二次确认。
  • 应用内“隐私空间”、“敏感信息解锁”等功能的授权。
  • 账号解锁或重登前的快速校验。

特性列表

  • 支持策略:
    • 生物识别优先:Face ID / Touch ID
    • 回退策略:系统设备密码或自定义应用密码
  • 会话超时控制:
    • 可配置超时时长,在窗口期内免再次验证
    • 使用 Keychain + SecAccessControl 绑定设备与生物信息
  • API 友好:
    • async/await(iOS 13+ 回溯)与回调式(iOS 12+)双轨
  • 统一错误映射:
    • 对 LAError 与 OSStatus 进行统一封装,便于 UI 显示
  • 提示文案建议:
    • 根据错误类型返回建议性提示,减少重复判断
  • 可测试性:
    • 上下文注入(LAContext Provider),便于单元测试模拟各类结果

使用方法

集成步骤

  1. 添加源码
    • 将 BiometricAuthManager.swift 与 KeychainStore.swift(示例中合并为单文件展示)加入工程源码。
  2. 配置依赖
    • 在目标的 Build Settings 中确保链接:
      • LocalAuthentication.framework
      • Security.framework
  3. 添加权限声明(Info.plist)
    • Face ID 需要用途描述键:
      • Key: NSFaceIDUsageDescription
      • Value: 例如 “用于验证您的身份以解锁敏感功能”
  4. 编译环境与平台
    • 最低系统:iOS 12+
    • async/await:Xcode 13+ 且 iOS 13+(后向部署由编译器支持)
  5. 本地化
    • 根据产品需要,配置提示文案与错误消息的多语言本地化。

代码示例

import Foundation
import LocalAuthentication
import Security
import UIKit

public enum BiometryType {
    case none, touchID, faceID
}

public enum FallbackStrategy {
    // 不提供回退
    case none
    // 使用系统设备密码(Device Passcode)
    case systemPasscode
    // 自定义回退(例如应用内密码)。当用户选择“输入密码”或生物识别不可用时回调。
    case custom(handler: (_ presenting: UIViewController?) -> Void, title: String? = nil)
}

public struct BiometricAuthConfig {
    // 系统弹窗中的理由文案(Localized Reason)
    public var localizedReason: String
    // 取消按钮标题(可选)
    public var cancelTitle: String?
    // 指纹失败后显示的“输入密码”标题(仅在 .deviceOwnerAuthenticationWithBiometrics 下生效)
    public var fallbackTitle: String?
    // 回退策略
    public var fallback: FallbackStrategy
    // 会话超时时长(秒)。在窗口期内可免验证(基于应用策略)
    public var sessionTimeout: TimeInterval
    // 复用时长(秒)。在规定时间内可复用最近成功验证(依赖 LAContext 复用机制)
    public var allowableReuseDuration: TimeInterval?
    // 是否仅允许生物识别(true 时不触发系统设备密码,仅生物识别)
    public var biometryOnly: Bool

    public init(localizedReason: String,
                cancelTitle: String? = nil,
                fallbackTitle: String? = nil,
                fallback: FallbackStrategy = .systemPasscode,
                sessionTimeout: TimeInterval = 120,
                allowableReuseDuration: TimeInterval? = nil,
                biometryOnly: Bool = false) {
        self.localizedReason = localizedReason
        self.cancelTitle = cancelTitle
        self.fallbackTitle = fallbackTitle
        self.fallback = fallback
        self.sessionTimeout = sessionTimeout
        self.allowableReuseDuration = allowableReuseDuration
        self.biometryOnly = biometryOnly
    }
}

public enum BiometricAuthError: Error {
    case biometryNotAvailable
    case biometryNotEnrolled
    case biometryLockout
    case devicePasscodeNotSet
    case authFailed
    case cancelledByUser
    case cancelledBySystem
    case appCancelled
    case fallbackRequested  // 触发自定义回退
    case keychainFailed(OSStatus)
    case unknown(Error?)

    // 面向用户的建议性提示(示例,可根据产品本地化)
    public var message: String {
        switch self {
        case .biometryNotAvailable:
            return "此设备不支持生物识别。"
        case .biometryNotEnrolled:
            return "未设置 Face ID/Touch ID,请前往设置完成注册。"
        case .biometryLockout:
            return "多次尝试失败,生物识别已锁定,请使用设备密码解锁后重试。"
        case .devicePasscodeNotSet:
            return "未设置设备解锁密码,请先在设置中开启。"
        case .authFailed:
            return "验证失败,请重试。"
        case .cancelledByUser:
            return "已取消验证。"
        case .cancelledBySystem:
            return "系统取消了验证,请重试。"
        case .appCancelled:
            return "验证已被应用取消。"
        case .fallbackRequested:
            return "请使用备用验证方式。"
        case .keychainFailed:
            return "安全存储失败,请重试。"
        case .unknown:
            return "发生未知错误,请重试。"
        }
    }
}

public struct BiometricAuthResult {
    public let biometryType: BiometryType
    public let usedFallback: Bool
}

public protocol LAContextProviding {
    func makeContext() -> LAContext
}

public final class DefaultLAContextProvider: LAContextProviding {
    public init() {}
    public func makeContext() -> LAContext { LAContext() }
}

// Keychain 安全存储(绑定生物识别/设备密码)
final class KeychainStore {
    private let service = "com.example.biometric.session"
    private let account = "BiometricSessionToken"

    // 创建 SecAccessControl:生物识别/用户存在绑定
    // biometryOnly = true -> .biometryCurrentSet
    // biometryOnly = false -> .userPresence(允许设备密码或生物识别)
    private func makeAccessControl(biometryOnly: Bool) throws -> SecAccessControl {
        var error: Unmanaged<CFError>?
        let flags: SecAccessControlCreateFlags = biometryOnly ? .biometryCurrentSet : .userPresence
        guard let ac = SecAccessControlCreateWithFlags(
            kCFAllocatorDefault,
            kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
            flags,
            &error
        ) else {
            throw error?.takeRetainedValue() as Error? ?? BiometricAuthError.keychainFailed(errSecParam)
        }
        return ac
    }

    func ensureTokenExists(biometryOnly: Bool) throws {
        // 若已存在则返回
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account,
            kSecReturnAttributes as String: true
        ]
        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)
        if status == errSecSuccess { return }
        guard status == errSecItemNotFound else { throw BiometricAuthError.keychainFailed(status) }

        // 随机令牌
        var token = [UInt8](repeating: 0, count: 32)
        _ = SecRandomCopyBytes(kSecRandomDefault, token.count, &token)

        // 新增,使用访问控制
        let ac = try makeAccessControl(biometryOnly: biometryOnly)
        let addQuery: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account,
            kSecAttrAccessControl as String: ac,
            kSecValueData as String: Data(token)
        ]
        let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
        guard addStatus == errSecSuccess else { throw BiometricAuthError.keychainFailed(addStatus) }
    }

    // 读取令牌会触发 LA 验证(需要传入 LAContext)
    func readToken(with context: LAContext) throws -> Data {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account,
            kSecUseAuthenticationContext as String: context,
            kSecReturnData as String: true
        ]
        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)
        guard status == errSecSuccess, let data = item as? Data else {
            throw BiometricAuthError.keychainFailed(status)
        }
        return data
    }
}

public final class BiometricAuthManager {
    public static let shared = BiometricAuthManager()

    private let contextProvider: LAContextProviding
    private let keychain = KeychainStore()
    private var lastSuccessAt: Date?
    private let lastAuthQueue = DispatchQueue(label: "biometric.lastAuth.queue")

    public init(contextProvider: LAContextProviding = DefaultLAContextProvider()) {
        self.contextProvider = contextProvider
    }

    public func biometryType() -> BiometryType {
        let ctx = contextProvider.makeContext()
        var error: NSError?
        let _ = ctx.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
        switch ctx.biometryType {
        case .faceID: return .faceID
        case .touchID: return .touchID
        default: return .none
        }
    }

    public func canEvaluateBiometrics() -> Result<BiometryType, BiometricAuthError> {
        let ctx = contextProvider.makeContext()
        var err: NSError?
        let can = ctx.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &err)
        if can { return .success(biometryType()) }
        return .failure(mapLAError(err))
    }

    private func isSessionValid(timeout: TimeInterval) -> Bool {
        return lastAuthQueue.sync {
            guard let last = self.lastSuccessAt else { return false }
            return Date().timeIntervalSince(last) < timeout
        }
    }

    private func markSessionNow() {
        lastAuthQueue.sync { self.lastSuccessAt = Date() }
    }

    // 回调式 API(iOS 12+)
    public func authenticate(config: BiometricAuthConfig,
                             presentingViewController: UIViewController? = nil,
                             completion: @escaping (Result<BiometricAuthResult, BiometricAuthError>) -> Void) {
        // 会话窗口期内免验证(应用级别策略)
        if isSessionValid(timeout: config.sessionTimeout) {
            let result = BiometricAuthResult(biometryType: biometryType(), usedFallback: false)
            return completion(.success(result))
        }

        // 确保 Keychain Token 已建立(访问控制)
        do { try keychain.ensureTokenExists(biometryOnly: config.biometryOnly) }
        catch { return completion(.failure((error as? BiometricAuthError) ?? .unknown(error))) }

        let ctx = contextProvider.makeContext()
        ctx.localizedCancelTitle = config.cancelTitle
        if case .systemPasscode = config.fallback {
            // 使用 .deviceOwnerAuthentication 允许设备密码
            ctx.localizedFallbackTitle = config.fallbackTitle ?? "输入密码"
        } else if case .custom(_, let title) = config.fallback {
            ctx.localizedFallbackTitle = title ?? "其它方式"
        } else {
            ctx.localizedFallbackTitle = "" // 隐藏“输入密码”按钮
        }
        if let reuse = config.allowableReuseDuration {
            ctx.touchIDAuthenticationAllowableReuseDuration = reuse
        }

        // 选择策略
        let policy: LAPolicy = {
            if config.biometryOnly { return .deviceOwnerAuthenticationWithBiometrics }
            switch config.fallback {
            case .systemPasscode: return .deviceOwnerAuthentication
            default: return .deviceOwnerAuthenticationWithBiometrics
            }
        }()

        var canError: NSError?
        guard ctx.canEvaluatePolicy(policy, error: &canError) else {
            return completion(.failure(mapLAError(canError)))
        }

        // 发起验证(读取 Keychain 触发生物识别/密码)
        ctx.evaluatePolicy(policy, localizedReason: config.localizedReason) { [weak self] success, error in
            guard success else {
                // 用户点击“输入密码”按钮,在 .deviceOwnerAuthenticationWithBiometrics 下会回调 .userFallback
                if let laErr = error as? LAError, laErr.code == .userFallback {
                    switch config.fallback {
                    case .custom(let handler, _):
                        DispatchQueue.main.async {
                            handler(presentingViewController)
                            completion(.failure(.fallbackRequested))
                        }
                        return
                    default:
                        // 其它策略已在 policy 中处理
                        break
                    }
                }
                return completion(.failure(mapLAError(error as NSError?)))
            }

            // 尝试读取绑定 Token(确保确实通过了 LA / Passcode)
            do {
                _ = try self?.keychain.readToken(with: ctx)
                self?.markSessionNow()
                let result = BiometricAuthResult(biometryType: self?.biometryType() ?? .none, usedFallback: policy == .deviceOwnerAuthentication)
                completion(.success(result))
            } catch {
                completion(.failure((error as? BiometricAuthError) ?? .unknown(error)))
            }
        }
    }

    // async/await API(iOS 13+ 回溯)
    @available(iOS 13.0, *)
    public func authenticateAsync(config: BiometricAuthConfig,
                                  presentingViewController: UIViewController? = nil) async throws -> BiometricAuthResult {
        try await withCheckedThrowingContinuation { cont in
            self.authenticate(config: config, presentingViewController: presentingViewController) { result in
                switch result {
                case .success(let ok): cont.resume(returning: ok)
                case .failure(let err): cont.resume(throwing: err)
                }
            }
        }
    }
}

// 错误映射
private func mapLAError(_ error: NSError?) -> BiometricAuthError {
    guard let error = error, error.domain == LAError.errorDomain else {
        return .unknown(error)
    }
    let code = LAError.Code(rawValue: error.code) ?? .authenticationFailed
    switch code {
    case .biometryNotAvailable: return .biometryNotAvailable
    case .biometryNotEnrolled:  return .biometryNotEnrolled
    case .biometryLockout:      return .biometryLockout
    case .passcodeNotSet:       return .devicePasscodeNotSet
    case .userCancel:           return .cancelledByUser
    case .systemCancel:         return .cancelledBySystem
    case .appCancel:            return .appCancelled
    case .authenticationFailed: return .authFailed
    case .userFallback:         return .fallbackRequested
    default:                    return .unknown(error)
    }
}

// MARK: - 使用示例(ViewController)
final class PayConfirmViewController: UIViewController {
    private let auth = BiometricAuthManager.shared

    @IBAction func confirmButtonTapped(_ sender: Any) {
        let config = BiometricAuthConfig(
            localizedReason: "请验证以确认支付",
            cancelTitle: "取消",
            fallbackTitle: "输入设备密码",
            fallback: .systemPasscode,
            sessionTimeout: 120, // 2 分钟内免验证
            allowableReuseDuration: 10, // 系统级复用 10 秒
            biometryOnly: false
        )

        if #available(iOS 13.0, *) {
            Task {
                do {
                    let result = try await auth.authenticateAsync(config: config, presentingViewController: self)
                    self.showAlert(title: "验证成功", message: "方式:\(result.biometryType == .faceID ? "Face ID" : (result.biometryType == .touchID ? "Touch ID" : "未知"))")
                    // TODO: 执行支付
                } catch let err as BiometricAuthError {
                    self.handleError(err)
                } catch {
                    self.showAlert(title: "错误", message: error.localizedDescription)
                }
            }
        } else {
            auth.authenticate(config: config, presentingViewController: self) { [weak self] result in
                DispatchQueue.main.async {
                    switch result {
                    case .success(let ok):
                        self?.showAlert(title: "验证成功", message: "方式:\(ok.biometryType == .faceID ? "Face ID" : (ok.biometryType == .touchID ? "Touch ID" : "未知"))")
                        // TODO: 执行支付
                    case .failure(let e):
                        self?.handleError(e)
                    }
                }
            }
        }
    }

    private func handleError(_ error: BiometricAuthError) {
        switch error {
        case .biometryNotEnrolled, .devicePasscodeNotSet:
            // 引导用户前往设置
            let alert = UIAlertController(title: "需要设置", message: error.message, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "前往设置", style: .default, handler: { _ in
                if let url = URL(string: UIApplication.openSettingsURLString) {
                    UIApplication.shared.open(url)
                }
            }))
            alert.addAction(UIAlertAction(title: "取消", style: .cancel))
            present(alert, animated: true)
        case .fallbackRequested:
            // 如果使用 .custom(handler:),此处一般无需处理;示例中用系统密码,则不会到这里
            break
        default:
            showAlert(title: "验证未完成", message: error.message)
        }
    }

    private func showAlert(title: String, message: String) {
        let a = UIAlertController(title: title, message: message, preferredStyle: .alert)
        a.addAction(UIAlertAction(title: "好的", style: .default))
        present(a, animated: true)
    }
}

注意事项

  • 权限与声明
    • 使用 Face ID 必须在 Info.plist 中添加 NSFaceIDUsageDescription,否则调用会失败。
  • 策略选择
    • 仅生物识别(biometryOnly = true)使用 .deviceOwnerAuthenticationWithBiometrics;允许设备密码回退请使用 .deviceOwnerAuthentication 或在 .deviceOwnerAuthenticationWithBiometrics 下设置 fallback 并处理 .userFallback。
  • Keychain 绑定
    • 使用 kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly + SecAccessControl(.biometryCurrentSet 或 .userPresence) 创建条目以绑定设备与生物信息。
    • 建议使用 ThisDeviceOnly,避免同步到其它设备。
  • 会话策略
    • sessionTimeout 为应用级会话控制,不会触发系统级生物识别;请根据业务风险评估合理配置。
    • allowableReuseDuration 为系统级复用窗口(LAContext),可减少频繁弹窗,但不要过大,避免安全风险。
  • 错误处理
    • biometryLockout 通常需要用户输入设备密码解锁后才能继续使用生物识别。
    • biometryNotEnrolled / passcodeNotSet 需引导用户前往设置。
  • UI 提示
    • localizedReason 应具体且与场景一致,例如“验证以查看密码”、“验证以确认支付”。
    • 避免在系统弹窗前后叠加模态弹窗干扰用户操作。
  • 测试与模拟
    • 模拟器:Features > Face ID/Touch ID,可切换 Enrolled/Not Enrolled、成功/失败、锁定等。
    • 单测:通过注入 LAContextProviding 模拟 canEvaluatePolicy/evaluatePolicy 结果。
    • Keychain:测试前清理测试条目,避免状态污染。
  • 兼容性
    • async/await 需 iOS 13+;iOS 12 使用回调式 API。
    • 设备无生物识别硬件时返回 biometryNotAvailable。

相关链接

示例详情

解决的问题

用最少的输入,快速产出可直接提交到仓库的iOS组件README章节:覆盖功能价值、适用场景、特性亮点、集成步骤、Swift代码示例、注意事项与参考链接。一方面,帮助个人开发者在发布与开源时展现专业水准;另一方面,为团队建立统一的文档标准,减少沟通与评审成本,加速代码合并与成员上手,推动知识沉淀与复用,最终以更低成本实现更高质量的交付与品牌形象。

适用用户

iOS研发工程师

为新UI组件、网络模块等快速产出README;补齐使用示例与集成步骤;在提测或合并前一次性完善文档。

技术负责人/架构师

制定统一文档模板并批量应用到各模块;在评审前生成标准说明,保障交付一致性与可维护性。

开源作者/独立开发者

用清晰的README展示核心价值与上手路径;提供可复制代码示例,降低用户集成门槛,提升项目口碑。

特征总结

针对组件信息一键生成完整README结构,涵盖概述、特性、用法、注意事项与链接。
自动补齐Swift示例代码与集成步骤,示例可直接复制使用,显著减少试错时间。
按目标平台与集成复杂度智能定制内容深度,避免冗余或缺失,生成更贴合场景的说明。
采用清晰层级与专业表达,自动润色措辞,让团队成员快速读懂并按步骤操作。
内置结构规划与校对流程,规避描述不准与逻辑断层,降低后续维护与沟通成本。
支持新功能发布、开源库说明、SDK集成指引等场景,文档一次生成,多处复用。
模板化参数驱动,持续复用项目规范,实现跨仓库文档风格与质量统一。
一键生成初稿并可迭代优化,数分钟完成高质量文档,提升交付效率与团队协作。

如何使用购买的提示词模板

1. 直接在外部 Chat 应用中使用

将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。

2. 发布为 API 接口调用

把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。

3. 在 MCP Client 中配置使用

在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。

AI 提示词价格
¥20.00元
先用后买,用好了再付款,超安全!

您购买后可以获得什么

获得完整提示词模板
- 共 710 tokens
- 4 个可调节参数
{ 功能组件名称 } { 功能详细描述 } { 目标平台版本 } { 集成复杂度 }
获得社区贡献内容的使用权
- 精选社区优质案例,助您快速上手提示词
使用提示词兑换券,低至 ¥ 9.9
了解兑换券 →
限时半价

不要错过!

半价获取高级提示词-优惠即将到期

17
:
23
小时
:
59
分钟
:
59