← 返回
未分类

Ios Dev Skill

iOS/macOS 开发技能(SwiftUI + UIKit)与 Swift 6.3 语言权威参考合集。覆盖 MVVM/Cocoa 架构、 网络层(URLSession/async-await)、状态管理(@State/@Published/Combine)、依赖注入、 Navigation、性能优化、单元测试/...
iOS/macOS 开发技能(SwiftUI + UIKit)与 Swift 6.3 语言权威参考合集。覆盖 MVVM/Cocoa 架构、 网络层(URLSession/async-await)、状态管理(@State/@Published/Combine)、依赖注入、 Navigation、性能优化、单元测试/...
yhongm
未分类 clawhub v1.0.1 1 版本 100000 Key: 无需
★ 0
Stars
📥 373
下载
💾 0
安装
1
版本
#latest

概述

iOS 开发技能

iOS/macOS 应用开发技能,基于 SwiftUI + UIKit。

核心架构

MVVM 架构(SwiftUI)

┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│    View     │ ←── │  ViewModel  │ ←── │   Model    │
│  (SwiftUI)  │     │ (ObservableObject) │   │   (Struct) │
└─────────────┘     └──────────────┘     └─────────────┘
     @StateObject          @Published           @State
     @ObservedObject       @State               let

数据流向

  1. View 订阅 ViewModel 的 @Published 属性
  2. ViewModel 处理业务逻辑,调用 Model
  3. Model 定义数据结构
  4. 状态变化自动触发 View 重新渲染

UIKit 架构对比

模式适用场景复杂度
-----------------------
MVC简单页面
MVVM中等复杂度
MVP需要解耦 View
VIPER大型模块化
Coordinator导航复杂

iOS 生命周期与状态管理

App 生命周期

状态触发时机典型用途
------------------------
didFinishLaunchingApp 启动完成初始化配置
sceneWillEnterForeground从后台恢复刷新数据
sceneDidBecomeActive获得焦点恢复动画
sceneWillResignActive失去焦点暂停任务
sceneDidEnterBackground进入后台保存状态

SwiftUI 状态修饰符

@State       // 值类型,值语义
@StateObject  // 引用类型,ObservableObject
@ObservedObject // 外部提供的 ObservableObject
@EnvironmentObject // 环境注入的共享状态
@Published   // 属性观察器,自动触发更新

状态管理对比

方式适用场景生命周期
------------------------
@State视图私有随视图
@StateObject模型对象随视图
@EnvironmentObject跨视图共享随 App
@AppStorageUserDefaults持久

数据持久化

方案对比

方案适用数据量类型线程安全
--------------------------------
UserDefaults< 1MBKey-Value
FileManager任意文件
PropertyListEncoder中等结构化
SQLite.swift大型关系型
Core Data超大型对象图
Realm超大型对象

UserDefaults

@AppStorage("username") var username = ""
@AppStorage("theme") var theme = "light"

SQLite.swift

import SQLite

let db = try Connection("db.sqlite3")
let users = Table("users")
let id = Expression<Int64>("id")
let name = Expression<String>("name")

try db.run(users.insert(name <- "Alice"))
for user in try db.prepare(users) {
    print(user[name])
}

网络与 API

URLSession 封装

enum APIError: Error {
    case invalidURL, noData, decodingError, networkError(Error)
}

func fetch<T: Decodable>(_ type: T.Type, from url: String) async throws -> T {
    guard let url = URL(string: url) else { throw APIError.invalidURL }
    let (data, _) = try await URLSession.shared.data(from: url)
    do {
        return try JSONDecoder().decode(T.self, from: data)
    } catch {
        throw APIError.decodingError
    }
}

SwiftUI 网络图片

AsyncImage(url: URL(string: "https://...")) { phase in
    switch phase {
    case .empty: ProgressView()
    case .success(let image): image.resizable()
    case .failure: Image(systemName: "photo")
    @unknown default: EmptyView()
    }
}

App 分发与发布

构建配置

配置用途代码签名
---------------------
Debug开发调试Development
ReleaseApp StoreDistribution
Ad Hoc测试分发Distribution

发布检查清单

  • [ ] App Icon 1024×1024
  • [ ] 启动屏(Splash Screen)
  • [ ] Info.plist 权限说明
  • [ ] 隐私政策 URL
  • [ ] TestFlight / App Store Connect 上传
  • [ ] App Store 截图(6.7 / 6.5 / 5.5 英寸)

常用权限声明

权限Info.plist Key
---------------------
相机NSCameraUsageDescription
照片NSPhotoLibraryUsageDescription
位置NSLocationWhenInUseUsageDescription
通知推送证书配置

SwiftUI 基础

页面结构

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

struct ContentView: View {
    @State private var count = 0

    var body: some View {
        VStack(spacing: 20) {
            Text("Count: \(count)")
                .font(.largeTitle)
            Button("Increment") { count += 1 }
                .buttonStyle(.borderedProminent)
        }
    }
}

组件层级

Window → ViewController → View → Subviews
NavigationController → ViewController → TableView → Cell
TabBarController → ViewController × N → NavigationController

状态管理

状态类型SwiftUIUIKit生命周期
----------------------------------
本地临时@Statelocal var视图存在期间
页面级@Stateview property页面存在期间
应用级@AppStorageUserDefaults跨页面持久化
全局共享@StateObjectSingleton应用生命周期

SwiftUI 状态装饰器

// @State — 值类型,组件私有
@State private var text = "Hello"

// @Binding — 双向绑定
@Binding var isPresented: Bool

// @StateObject — 引用类型,视图拥有
@StateObject private var viewModel = ViewModel()

// @ObservedObject — 引用类型,外部传入
@ObservedObject var viewModel: ViewModel

// @EnvironmentObject — 环境注入的全局状态
@EnvironmentObject var authService: AuthService

// @AppStorage — 持久化
@AppStorage("username") var username = ""

UIKit 状态

// 局部变量
class ViewController: UIViewController {
    private var data: [String] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        data = loadData()
    }
}

// 视图属性
class ViewController: UIViewController {
    var initialData: [String] = []
}

// UserDefaults
UserDefaults.standard.string(forKey: "username")

// Singleton
let shared = NetworkManager.shared

Navigation 导航模式

SwiftUI NavigationStack(推荐)

NavigationStack {
    List(users) { user in
        NavigationLink(destination: UserDetailView(user: user)) {
            Text(user.name)
        }
    }
}

UIKit 导航

// 导航控制器
let nav = UINavigationController(rootViewController: HomeVC())
nav.pushViewController(detailVC, animated: true)
nav.popViewController(animated: true)

// TabBar 切换
UITabBarController()
  ├─ UINavigationController(首页)
  ├─ UINavigationController(发现)
  └─ UINavigationController(我的)

网络层

SwiftUI + async/await

actor NetworkService {
    static let shared = NetworkService()

    func fetch<T: Decodable>(_ type: T.Type, from url: URL) async throws -> T {
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode(T.self, from: data)
    }
}

// 使用
Task {
    let users = try await NetworkService.shared.fetch([User].self, from: url)
}

UIKit + async/await

class NetworkManager {
    static let shared = NetworkManager()

    func request<T: Decodable>(_ type: T.Type, endpoint: String) async throws -> T {
        guard let url = URL(string: endpoint) else {
            throw NetworkError.invalidURL
        }
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode(T.self, from: data)
    }
}

URLSession 完整示例

import Foundation

struct User: Codable, Identifiable {
    let id: String
    let name: String
    let email: String
}

enum NetworkError: Error {
    case invalidURL
    case requestFailed
    case decodingFailed
    case noData
}

class APIClient {
    private let baseURL = "https://api.example.com"
    private let session: URLSession

    init() {
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 30
        self.session = URLSession(configuration: config)
    }

    func get<T: Codable>(_ type: T.Type, path: String) async throws -> T {
        guard let url = URL(string: baseURL + path) else {
            throw NetworkError.invalidURL
        }

        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

        let (data, response) = try await session.data(for: request)

        guard let httpResponse = response as? HTTPURLResponse,
              (200...299).contains(httpResponse.statusCode) else {
            throw NetworkError.requestFailed
        }

        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase

        do {
            return try decoder.decode(T.self, from: data)
        } catch {
            throw NetworkError.decodingFailed
        }
    }

    func post<T: Codable, B: Encodable>(_ type: T.Type, path: String, body: B) async throws -> T {
        guard let url = URL(string: baseURL + path) else {
            throw NetworkError.invalidURL
        }

        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

        let encoder = JSONEncoder()
        encoder.keyEncodingStrategy = .convertToSnakeCase
        request.httpBody = try encoder.encode(body)

        let (data, response) = try await session.data(for: request)

        guard let httpResponse = response as? HTTPURLResponse,
              (200...299).contains(httpResponse.statusCode) else {
            throw NetworkError.requestFailed
        }

        return try JSONDecoder().decode(T.self, from: data)
    }
}

依赖注入

SwiftUI Environment 注入

struct ContentView: View {
    @EnvironmentObject var authService: AuthService
    var body: some View { ... }
}

// App 层级注入
ContentView()
    .environmentObject(AuthService())

UIKit Protocol 注入

protocol NetworkServiceProtocol {
    func fetchUsers() async throws -> [User]
}

class ViewModel {
    let networkService: NetworkServiceProtocol
    init(networkService: NetworkServiceProtocol = NetworkService.shared) {
        self.networkService = networkService
    }
}

Combine 响应式编程

Publisher 与 Subscriber

import Combine

// Publisher
let publisher = PassthroughSubject<String, Never>()

// Subscriber
let cancellable = publisher
    .filter { $0.count > 3 }
    .map { $0.uppercased() }
    .sink { print($0) }

// Future + Promise
func fetchUser(id: Int) -> Future<User, Error> {
    Future { promise in
        // 异步操作
        promise(.success(user))
    }
}

SwiftUI + Combine

@Published var searchText: String = ""

var searchResults: AnyPublisher<[User], Never> {
    $searchText
        .debounce(for: .milliseconds(300), scheduler: RunLoop.main)
        .removeDuplicates()
        .flatMap { query in
            query.isEmpty ? Just([]).eraseToAnyPublisher()
                         : api.search(query: query)
        }
        .receive(on: DispatchQueue.main)
        .eraseToAnyPublisher()
}

### AnyCancellable 内存管理

import Combine

class ViewModel {

private var cancellables = Set()

func bind() {

$searchText

.debounce(for: .milliseconds(300), scheduler: RunLoop.main)

.sink { [weak self] text in

self?.performSearch(text)

}

.store(in: &cancellables)

}

}

// Store in property

class AnotherViewModel {

@Published var data: [Item] = []

private var cancellables = Set()

func subscribe(to api: APIService) {

api.itemsPublisher

.receive(on: DispatchQueue.main)

.sink { [weak self] items in

self?.data = items

}

.store(in: &cancellables)

}

}

// 取消订阅

cancellables.removeAll() // 手动取消所有


### 常用 Operators

| Operator | 用途 |
|----------|------|
| `map` | 转换值 |
| `filter` | 过滤值 |
| `flatMap` | 展平嵌套 Publisher |
| `debounce` | 防抖(搜索场景) |
| `throttle` | 节流(滚动场景) |
| `removeDuplicates` | 去重 |
| `combineLatest` | 合并多个 Publisher |
| `merge` | 合并同类型 Publisher |
| `catch` | 错误处理 |
| `retry` | 重试 |
| `zip` | 配对组合 |

---

# 性能优化

| 场景 | SwiftUI | UIKit |
|------|---------|-------|
| 列表滚动 | LazyVStack | UITableView/UICollectionView |
| 图片缓存 | AsyncImage + 第三方库 | SDWebImage/Kingfisher |
| 预取数据 | .task modifier | UITableViewDataSourcePrefetching |
| 后台任务 | Task.detached | async/await |

### SwiftUI 列表优化

// ✅ 推荐:LazyVStack

List {

ForEach(items) { item in

ItemView(item: item)

}

}

// ❌ 避免:大列表不用 Lazy

VStack {

ForEach(items) { item in // 全部渲染

ItemView(item: item)

}

}


### UIKit 列表优化

// UICollectionView 预加载

func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {

for indexPath in indexPaths {

let item = items[indexPath.item]

imageLoader.prefetch(url: item.imageURL)

}

}


---

# 测试策略

单元测试 → ViewModel / Service 逻辑

UI 测试 → View 渲染 + 交互(XCUITest)

集成测试 → 模块间交互

快照测试 → UI 视觉回归(swift-snapshot-testing)


### Swift 单元测试示例

import XCTest

final class UserViewModelTests: XCTestCase {

var viewModel: UserListViewModel!

var mockService: MockNetworkService!

override func setUp() {

super.setUp()

mockService = MockNetworkService()

viewModel = UserListViewModel(networkService: mockService)

}

func testLoadUsersSuccess() async {

// Given

mockService.users = [User(id: "1", name: "John")]

// When

await viewModel.loadUsers()

// Then

XCTAssertEqual(viewModel.users.count, 1)

XCTAssertFalse(viewModel.isLoading)

}

}


---

# 完整页面示例

### SwiftUI + MVVM

// Model

struct User: Identifiable, Codable {

let id: String

let name: String

let email: String

}

// ViewModel

@MainActor

class UserListViewModel: ObservableObject {

@Published var users: [User] = []

@Published var isLoading = false

@Published var error: String?

func loadUsers() async {

isLoading = true

defer { isLoading = false }

do {

users = try await api.fetch(User.self, path: "/users")

} catch {

self.error = error.localizedDescription

}

}

}

// View

struct UserListView: View {

@StateObject private var vm = UserListViewModel()

var body: some View {

NavigationStack {

Group {

if vm.isLoading {

ProgressView()

} else if let error = vm.error {

Text("Error: \(error)")

.foregroundColor(.red)

} else {

List(vm.users) { user in

NavigationLink(destination: UserDetailView(user: user)) {

HStack {

Text(user.name)

Spacer()

Text(user.email)

.foregroundColor(.secondary)

}

}

}

}

}

.navigationTitle("用户")

.task { await vm.loadUsers() }

}

}

}


### UIKit + MVC

import UIKit

import SnapKit

class UserListViewController: UIViewController {

private let tableView = UITableView(frame: .zero, style: .insetGrouped)

private var users: [User] = []

override func viewDidLoad() {

super.viewDidLoad()

setupUI()

loadData()

}

private func setupUI() {

title = "用户"

view.backgroundColor = .systemGroupedBackground

tableView.delegate = self

tableView.dataSource = self

tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")

view.addSubview(tableView)

tableView.snp.makeConstraints { make in

make.edges.equalToSuperview()

}

}

private func loadData() {

Task {

do {

users = try await APIClient.shared.get([User].self, path: "/users")

tableView.reloadData()

} catch {

showError(error)

}

}

}

}

extension UserListViewController: UITableViewDataSource, UITableViewDelegate {

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

return users.count

}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)

let user = users[indexPath.row]

var config = cell.defaultContentConfiguration()

config.text = user.name

config.secondaryText = user.email

cell.contentConfiguration = config

return cell

}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

tableView.deselectRow(at: indexPath, animated: true)

let user = users[indexPath.row]

let detailVC = UserDetailViewController(user: user)

navigationController?.pushViewController(detailVC, animated: true)

}

}


---

# Swift 语言权威参考

> 来源:The Swift Programming Language (6.3)
> https://docs.swift.org/swift-book/documentation/the-swift-programming-language/
> CC BY 4.0 License

## 类型系统

### 基本类型

| 类型 | 说明 | 示例 |
|------|------|------|
| Int | 整数 | `42`, `-7` |
| Double | 64位浮点 | `3.14159` |
| Float | 32位浮点 | `3.14` |
| Bool | 布尔 | `true` / `false` |
| String | 字符串 | `"Hello"` |
| Character | 单字符 | `"A"` |

### 类型推断与注解

let inferred = 42 // Int

let annotated: Int = 42 // 显式 Int

let pi: Double = 3.14 // Double


### 类型别名

typealias AudioSample = UInt16

typealias Callback = (Int, String) -> Void


## Optional 可选类型

### 定义与解包

// 定义

var serverResponse: String? = nil

var response: String! = nil // 隐式解包

// 解包方式

if let value = optional {

print(value)

}

// guard 解包

func process(_ value: String?) {

guard let value = value else { return }

print(value)

}

// ?? 操作符

let name = optional ?? "default"

// 链式调用

let upper = optional?.uppercased()


### Optional 模式

// switch 模式匹配

switch optional {

case .some(let value):

print(value)

case .none:

print("nil")

}

// 问号链式调用

person?.address?.city


##  Tuple 元组

// 定义

let httpError = (404, "Not Found")

let (code, message) = httpError

let onlyCode = httpError.0

// 命名

let success = (code: 200, message: "OK")

success.code

success.message

// 返回多值

func getUser() -> (name: String, age: Int) {

return ("Alice", 30)

}


## 集合类型

### Array

// 创建

var arr = [Int]() // 空

var arr2 = Array(repeating: 0, count: 5) // [0,0,0,0,0]

let literals = [1, 2, 3] // 字面量

// 操作

arr.append(4)

arr.insert(0, at: 0)

arr.remove(at: 0)

arr.removeLast()

arr.first

arr.last

// 遍历

for item in arr { }

for (i, v) in arr.enumerated() { }


### Set

// 创建

var set = Set()

let genres: Set = ["Rock", "Jazz"]

// 操作

set.insert("Pop")

set.remove("Rock")

set.contains("Jazz")

// 集合运算

a.union(b) // 并集

a.intersection(b) // 交集

a.subtracting(b) // 差集

a.symmetricDifference(b) // 对称差集

// 关系

a.isSubset(of: b)

a.isSuperset(of: b)

a.isDisjoint(with: b)


### Dictionary

// 创建

var dict = [String: Int]()

let capitals = ["CN": "Beijing", "JP": "Tokyo"]

// 操作

dict["key"] = "value"

dict["key"] = nil // 删除

dict.updateValue("v", forKey: "k") // 返回旧值

// 安全访问

if let value = dict["key"] { }

// 遍历

for (k, v) in dict { }

for key in dict.keys { }

for value in dict.values { }


## 函数

### 定义与调用

func greet(name: String) -> String {

return "Hello, \(name)"

}

greet(name: "World")

// 参数标签

func greet(to name: String) -> String {

return "Hello, \(name)"

}

greet(to: "World")

// 默认参数

func greet(_ name: String = "World") -> String {

return "Hello, \(name)"

}

// 可变参数

func sum(_ numbers: Int...) -> Int {

return numbers.reduce(0, +)

}

sum(1, 2, 3, 4, 5)

// inout 参数

func swap(_ a: inout Int, _ b: inout Int) {

let temp = a

a = b

b = temp

}


### 函数类型

var mathFunc: (Int, Int) -> Int = { $0 + $1 }

// 作为参数

func apply(_ op: (Int, Int) -> Int, _ a: Int, _ b: Int) -> Int {

return op(a, b)

}

apply(mathFunc, 3, 4)

// 作为返回值

func choose(_ op: Bool) -> (Int, Int) -> Int {

return op ? { $0 + $1 } : { $0 - $1 }

}


### 嵌套函数

func outer() -> () -> Int {

var count = 0

func inner() -> Int {

count += 1

return count

}

return inner

}


## 闭包

### 基本语法

// 完整语法

{ (params) -> ReturnType in

statements

}

// 类型推断

{ a, b in a + b }

// 无参数

{ () -> Int in 42 }

// 返回类型推断

{ $0 + $1 }


### 尾随闭包

// 不用尾随

arr.map({ (x: Int) -> Int in x * 2 })

// 尾随闭包

arr.map { $0 * 2 }

// 最后一个参数是闭包

arr.map { x in x * 2 }


### @escaping

var handlers: [() -> Void] = []

func withEscaping(_ handler: @escaping () -> Void) {

handlers.append(handler)

}

func withoutEscaping(_ handler: () -> Void) {

handler()

}

// 逃逸闭包需要显式 self

class MyClass {

var x = 10

func test() {

withEscaping { self.x = 20 } // 必须显式

withoutEscaping { x = 30 } // 可省略

}

}


### 捕获

func makeCounter() -> () -> Int {

var count = 0

return {

count += 1

return count

}

}

let counter = makeCounter()

counter() // 1

counter() // 2


## 枚举

enum Direction {

case north, south, east, west

}

let dir: Direction = .north

// 关联值

enum Result {

case success(Data)

case failure(Error)

}

// 方法

enum Device {

case phone, tablet

func description() -> String {

switch self {

case .phone: return "iPhone"

case .tablet: return "iPad"

}

}

}

// 原始值

enum ASCIIControl: Character {

case tab = "\t"

case newline = "\n"

}


## 结构体与类

### 对比

| 特性 | struct | class |
|------|--------|-------|
| 类型 | 值类型 | 引用类型 |
| 继承 | ❌ | ✅ |
| 初始化 | 自动生成 | 手动 |
| 析构 | — | ✅ |
| 引用计数 | — | ✅ |

struct Point {

var x: Double

var y: Double

// 自动生成 memberwise init

}

class Person {

var name: String

var age: Int

init(name: String, age: Int) {

self.name = name

self.age = age

}

}


## 属性

### 存储属性

struct FixedRange {

var start: Int

let end: Int // 常量

}


### 计算属性

struct Rect {

var origin: Point

var size: Size

var center: Point {

get {

Point(x: origin.x + size.width/2,

y: origin.y + size.height/2)

}

set {

origin.x = newValue.x - size.width/2

origin.y = newValue.y - size.height/2

}

}

}


### 属性包装器

@propertyWrapper

struct SmallNumber {

private var number: Int

var value: Int {

get { min(number, 12) }

set { number = newValue }

}

init() { number = 0 }

init(wrappedValue: Int) { number = min(wrappedValue, 12) }

}

@SmallNumber var value: Int // 使用


### 属性观察者

class StepCounter {

var totalSteps: Int = 0 {

willSet { print("will set to \(newValue)") }

didSet { print("did set from \(oldValue)") }

}

}


### 懒加载

class DataManager {

lazy var importer = DataImporter() // 首次访问时才创建

}


## 方法

### 实例方法

class Counter {

var count = 0

func increment() { count += 1 }

func increment(by amount: Int) { count += amount }

func reset() { count = 0 }

}


### 静态/类方法

struct MathUtils {

static func sqrt(_ n: Double) -> Double { ... }

}

MathUtils.sqrt(16)

// 类方法(可被重写)

class Animal {

class func info() { print("Animal") }

}


## 下标

struct TimesTable {

subscript(index: Int) -> Int {

return index * multiplier

}

}

let table = TimesTable(multiplier: 3)

table[6] // 18

// 多维下标

struct Matrix {

subscript(row: Int, col: Int) -> Double {

get { return grid[row * 3 + col] }

set { grid[row * 3 + col] = newValue }

}

}


## 继承

class Vehicle {

var speed = 0

func describe() -> String { "speed: \(speed)" }

}

class Bicycle: Vehicle {

var hasBasket = false

override func describe() -> String {

// 调用父类

return super.describe() + ", basket: \(hasBasket)"

}

}

// final 类不可被继承

final class FinalClass { }


## 初始化与析构

### 初始化

class ShoppingListItem {

var name: String

var quantity: Int = 1

var completed: Bool = false

init(name: String, quantity: Int = 1) {

self.name = name

self.quantity = quantity

}

}

// 可失败初始化

struct Animal {

let species: String

init?(species: String) {

if species.isEmpty { return nil }

self.species = species

}

}


### 析构

class FileHandler {

var file: FileHandle

init() { file = open(...) }

deinit {

file.close()

}

}


# 错误处理

## 错误处理

// 定义错误

enum NetworkError: Error {

case badURL

case noData

case decodingFailed

}

// 抛出

func fetch() throws -> Data {

guard let url = URL(string: "...") else {

throw NetworkError.badURL

}

return try Data(contentsOf: url)

}

// 处理

do {

let data = try fetch()

} catch NetworkError.badURL {

print("Bad URL")

} catch {

print("Error: \(error)")

}

// try? 转换 Optional

let data = try? fetch()

// try! 强制解包(危险)

let data = try! fetch()


# 协议与泛型

## 协议

### 定义与遵循

protocol ExampleProtocol {

var simpleDescription: String { get }

mutating func adjust()

}

// 遵循

struct SimpleStructure: ExampleProtocol {

var simpleDescription: String = "A simple structure"

mutating func adjust() { }

}

// 类遵循

class SimpleClass: ExampleProtocol {

var simpleDescription: String = "A simple class"

func adjust() { }

}


### 协议扩展

extension Collection {

func allEven() -> [Element] where Element: Numeric {

return self.filter { ($0 as? Int ?? 0) % 2 == 0 }

}

}


## 泛型

### 函数泛型

func swapTwoValues(_ a: inout T, _ b: inout T) {

let temp = a

a = b

b = temp

}

swapTwoValues(&x, &y)


### 类型约束

func findIndex(of valueToFind: T, in array: [T]) -> Int? {

for (index, value) in array.enumerated() {

if value == valueToFind { return index }

}

return nil

}


### 泛型类型

struct Stack {

var items: [Element] = []

mutating func push(_ item: Element) { items.append(item) }

mutating func pop() -> Element { items.removeLast() }

}


### where 子句

func allItemsMatch

(_ someContainer: C1, _ anotherContainer: C2) -> Bool

where C1.Element == C2.Element, C1.Element: Equatable

{

// ...

}


## 访问控制

| 修饰符 | 范围 |
|--------|------|
| open | 任意模块,可被继承 |
| public | 任意模块 |
| internal | 模块内(默认) |
| fileprivate | 当前文件 |
| private | 当前作用域 |

public class PublicClass {

private var privateVar = 0

fileprivate var fileVar = 0

}


## 扩展

extension Int {

var isEven: Bool { return self % 2 == 0 }

func repetitions(_ task: () -> Void) {

for _ in 0..

}

}

5.isEven // true

3.repetitions { print("Hello") }


## 类型转换

// 类型检查

if item is String {

print("String")

}

// 向下转型

if let str = item as? String {

print(str)

}

// 强制转型(危险)

let str = item as! String

// Any 和 AnyObject

var things: [Any] = []

things.append(42)

things.append("string")


## 嵌套类型

struct ChessBoard {

enum Piece {

case king, queen, rook, bishop, knight, pawn

}

var board: [[Piece?]]

}

let piece: ChessBoard.Piece = .king


# Swift 6 并发

## Swift 6 Concurrency

### async/await

func fetchData() async throws -> Data { ... }

Task {

do {

let data = try await fetchData()

} catch {

print(error)

}

}

// async let 并行

async let first = fetchData()

async let second = anotherFetch()

let results = await [first, second]


### Task

// 创建

let task = Task { await doWork() }

let result = await task.value

task.cancel()

// TaskGroup

await withTaskGroup(of: Data.self) { group in

for url in urls {

group.addTask { await fetch(url) }

}

var results: [Data] = []

for await data in group {

results.append(data)

}

}


### Actor

actor SafeCounter {

private var count = 0

func increment() { count += 1 }

func getCount() -> Int { count }

}

// MainActor

@MainActor

func updateUI() {

// UI 更新

}


### Sendable

struct User: Sendable { let id: String }

actor SafeLogger: Sendable {

// actor 自动 Sendable

}

---

## 避坑指南

### 常见错误

| 错误做法 | 正确做法 |
|---------|---------|
| ❌ 在主线程执行网络请求 | ✅ async/await 自动后台执行 |
| ❌ 不处理网络错误 | ✅ always try-catch + user feedback |
| ❌ @State 用于引用类型 | ✅ @StateObject 用于 class |
| ❌ 不用 LazyVStack 处理大列表 | ✅ 懒加载避免性能问题 |
| ❌ 硬编码 URL | ✅ Configuration/Environment |

### SwiftUI 陷阱

- ⚠️ **@State 复制语义** — @State 修饰的 struct 是值语义,修改会触发重渲染
- ⚠️ **@StateObject 只初始化一次** — 不能在 body 中创建
- ⚠️ **onAppear vs task** — task 可取消,onAppear 不行

### UIKit 陷阱

- ⚠️ **循环引用** — delegate/closure 记得用 [weak self]
- ⚠️ **主线程 UI** — UI 更新必须在主线程
- ⚠️ **Memory Leak** — 及时清理 NotificationCenter 观察者

---

## 来源

> 来源:Apple Developer Documentation(2026-04-23 访问)
> - SwiftUI: https://developer.apple.com/documentation/swiftui
> - UIKit: https://developer.apple.com/documentation/uikit
> - Xcode: https://developer.apple.com/documentation/xcode
> - Combine: https://developer.apple.com/documentation/combine
>
> 更新频率:随 Xcode/iOS 版本迭代

---

# 持久化

### UserDefaults

适用于:小量配置、用户偏好、简单状态

// SwiftUI

@AppStorage("username") var username = ""

@AppStorage("isDarkMode") var isDarkMode = false

// 代码直接访问

UserDefaults.standard.string(forKey: "username")

UserDefaults.standard.set("value", forKey: "key")


### SQLite(生产推荐)

适用于:结构化数据、离线存储、查询性能

import SQLite

class DatabaseManager {

static let shared = DatabaseManager()

private var db: Connection?

// 表定义

private let users = Table("users")

private let id = SQLite.Expression("id")

private let name = SQLite.Expression("name")

private let email = SQLite.Expression("email")

init() {

do {

let path = NSSearchPathForDirectoriesInDomains(

.documentDirectory, .userDomainMask, true

).first!

db = try Connection("\(path)/sqlite.db")

try createTables()

} catch {

print("Database error: \(error)")

}

}

private func createTables() throws {

try db?.run(users.create(ifNotExists: true) { t in

t.column(id, primaryKey: .autoincrement)

t.column(name)

t.column(email)

})

}

// CRUD

func insertUser(_ user: User) throws {

let insert = users.insert(

name <- user.name,

email <- user.email

)

try db?.run(insert)

}

func fetchUsers() throws -> [User] {

guard let db = db else { return [] }

return try db.prepare(users).map { row in

User(

id: row[id],

name: row[name],

email: row[email]

)

}

}

func deleteUser(_ userId: Int64) throws {

let user = users.filter(id == userId)

try db?.run(user.delete())

}

}


### Core Data(苹果官方)

适用于:复杂对象图、大量关系数据、Apple 生态深度集成

// Core Data Stack

class CoreDataManager {

static let shared = CoreDataManager()

lazy var persistentContainer: NSPersistentContainer = {

let container = NSPersistentContainer(name: "Model")

container.loadPersistentStores { _, error in

if let error = error {

fatalError("Core Data failed: \(error)")

}

}

container.viewContext.automaticallyMergesChangesFromParent = true

return container

}()

var viewContext: NSManagedObjectContext {

persistentContainer.viewContext

}

func save() {

let context = viewContext

if context.hasChanges {

do {

try context.save()

} catch {

print("Save error: \(error)")

}

}

}

}

// SwiftUI 集成

struct ContentView: View {

@Environment(\.managedObjectContext) var viewContext

@FetchRequest(

sortDescriptors: [NSSortDescriptor(keyPath: \User.name, ascending: true)],

animation: .default

)

var users: FetchedResults

var body: some View {

List(users, id: \.self) { user in

Text(user.name ?? "Unknown")

}

}

}


---

# Swift Concurrency 深度

### Sendable 协议

确保数据可以安全跨并发域传递。

// ✅ 可Sendable的类型

struct User: Sendable {

let id: String

let name: String

// 值类型默认 Sendable

}

// ⚠️ Class 需要手动实现

final class SafeClass: @unchecked Sendable {

// 不做线程安全假设,仅用于已知安全的场景

}

// ❌ 不可Sendable

class UnsafeClass {

var cache = [String: Any]() // 包含可变状态

}


### MainActor

确保代码在主线程执行,用于 UI 更新。

// 方法级别

@MainActor

func updateUI() {

// 自动在主线程执行

self.username = "New Name"

}

// 类级别(所有方法默认主线程)

@MainActor

class ViewModel: ObservableObject {

@Published var items: [Item] = []

// 隐式 @MainActor

func loadItems() async {

let fetched = await network.fetchItems()

self.items = fetched // 安全

}

}

// 非隔离函数访问主线程数据

nonisolated func describe(_ vm: ViewModel) {

// ❌ 不能访问 @Published

// ✅ 可以访问 Sendable 属性

print("Description")

}


### TaskGroup

并发执行多个任务。

// 并发下载

func fetchAllImages(urls: [URL]) async throws -> [Data] {

try await withThrowingTaskGroup(of: Data.self) { group in

for url in urls {

group.addTask {

let (data, _) = try await URLSession.shared.data(from: url)

return data

}

}

var results: [Data] = []

for try await data in group {

results.append(data)

}

return results

}

}

// 带取消

func fetchWithCancel(urls: [URL]) async {

await withTaskGroup(of: Data?.self) { group in

for url in urls {

group.addTask {

try? await Task.sleep(nanoseconds: 1_000_000_000)

guard !Task.isCancelled else { return nil }

let (data, _) = try await URLSession.shared.data(from: url)

return data

}

}

}

}


### Task 取消

// 检查取消

func performWork() async throws {

for item in items {

try Task.checkCancellation() // 抛出 CancellationError

// 处理 item

}

}

// withTaskCancellationHandler

try await withTaskCancellationHandler {

try await longRunningWork()

} onCancel: {

cleanup()

}

// 传递取消

struct DetailView: View {

@State private var data: Data?

@Environment(\.dismiss) var dismiss

var body: some View {

Button("加载") {

Task {

data = await fetchData()

}

}

.onDisappear {

// 视图消失时自动取消

}

}

}


---

# UIKit 进阶

### UICollectionView

生产级列表/网格首选。

class CollectionViewController: UIViewController {

private var collectionView: UICollectionView!

private var dataSource: UICollectionViewDiffableDataSource!

enum Section: Hashable {

case main

case featured

}

override func viewDidLoad() {

super.viewDidLoad()

setupCollectionView()

configureDataSource()

applySnapshot()

}

private func setupCollectionView() {

// Compositional Layout

let config = UICollectionLayoutConfiguration(

-interSectionSpacing: 16

)

let layout = UICollectionViewCompositionalLayout(

sectionProvider: { sectionIndex, environment in

let itemSize = NSCollectionLayoutSize(

widthDimension: .fractionalWidth(0.5),

heightDimension: .fractionalHeight(1.0)

)

let item = NSCollectionLayoutItem(layoutSize: itemSize)

item.contentInsets = NSDirectionalEdgeInsets(

top: 8, leading: 8, bottom: 8, trailing: 8

)

let groupSize = NSCollectionLayoutSize(

widthDimension: .fractionalWidth(1.0),

heightDimension: .absolute(180)

)

let group = NSCollectionLayoutGroup.horizontal(

layoutSize: groupSize, subitems: [item]

)

let section = NSCollectionLayoutSection(group: group)

section.contentInsets = NSDirectionalEdgeInsets(

top: 0, leading: 16, bottom: 16, trailing: 16

)

return NSCollectionLayoutSection(section: section)

},

configuration: config

)

collectionView = UICollectionView(

frame: view.bounds,

collectionViewLayout: layout

)

collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

collectionView.delegate = self

view.addSubview(collectionView)

}

private func configureDataSource() {

let cellRegistration = UICollectionView.CellRegistration<

UICollectionViewCell, Item

> { cell, indexPath, item in

var config = UIListContentConfiguration.cell()

config.text = item.title

config.secondaryText = item.subtitle

config.image = UIImage(systemName: item.icon)

cell.contentConfiguration = config

}

dataSource = UICollectionViewDiffableDataSource(

collectionView: collectionView

) { collectionView, indexPath, item in

collectionView.dequeueConfiguredReusableCell(

using: cellRegistration, for: indexPath, item: item

)

}

}

private func applySnapshot() {

var snapshot = NSDiffableDataSourceSnapshot()

snapshot.appendSections([.main])

snapshot.appendItems(items)

dataSource.apply(snapshot, animatingDifferences: true)

}

}

extension CollectionViewController: UICollectionViewDelegate {

func collectionView(

_ collectionView: UICollectionView,

didSelectItemAt indexPath: IndexPath

) {

guard let item = dataSource.itemIdentifier(for: indexPath) else { return }

// 处理选择

}

}


### Auto Layout 完整约束

// NSLayoutConstraint 语法

label.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([

label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),

label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),

label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),

label.heightAnchor.constraint(greaterThanOrEqualToConstant: 44)

])

// 优先级

let highPriority = label.widthAnchor.constraint(equalToConstant: 100)

highPriority.priority = .defaultHigh // 750

let lowPriority = label.widthAnchor.constraint(equalToConstant: 50)

lowPriority.priority = .defaultLow // 250

// 比例约束

label.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)

// 尺寸约束

label.heightAnchor.constraint(equalTo: label.widthAnchor, multiplier: 1.5)


### 键盘处理

class KeyboardViewController: UIViewController {

@IBOutlet weak var scrollView: UIScrollView!

@IBOutlet weak var textField: UITextField!

override func viewDidLoad() {

super.viewDidLoad()

setupKeyboardObservers()

}

private func setupKeyboardObservers() {

NotificationCenter.default.addObserver(

self,

selector: #selector(keyboardWillShow),

name: UIResponder.keyboardWillShowNotification,

object: nil

)

NotificationCenter.default.addObserver(

self,

selector: #selector(keyboardWillHide),

name: UIResponder.keyboardWillHideNotification,

object: nil

)

}

@objc func keyboardWillShow(_ notification: Notification) {

guard let keyboardFrame = notification.userInfo?[

UIResponder.keyboardFrameEndUserInfoKey

] as? CGRect else { return }

let contentInsets = UIEdgeInsets(

top: 0, left: 0,

bottom: keyboardFrame.height, right: 0

)

scrollView.contentInset = contentInsets

scrollView.scrollIndicatorInsets = contentInsets

}

@objc func keyboardWillHide(_ notification: Notification) {

scrollView.contentInset = .zero

scrollView.scrollIndicatorInsets = .zero

}

@objc func dismissKeyboard() {

view.endEditing(true)

}

}

// SwiftUI 版本

struct KeyboardAvoidingView: View {

@State private var text = ""

@FocusState private var isFocused: Bool

var body: some View {

ScrollView {

TextField("输入", text: $text)

.focused($isFocused)

.padding()

.background(Color.gray.opacity(0.2))

}

.onTapGesture {

isFocused = false

}

}

}


---

## Swift Concurrency 避坑

### 常见错误

| 错误做法 | 正确做法 |
|---------|---------|
| ❌ nonisolated 函数访问 @Published | ✅ 用 @MainActor 包装 |
| ❌ 跨线程传递 UIView | ✅ 始终在主线程操作 |
| ❌ Task 不保存引用 | ✅ 存储 Task 以支持取消 |
| ❌ 忘记 CancellationError | ✅ 调用 Task.checkCancellation() |
| ❌ actor 内部用锁 | ✅ actor 天然线程安全 |
| ❌ 传递非 Sendable 闭包 | ✅ 确保闭包捕获值是 Sendable |

### @MainActor 传递规则

@MainActor

class ViewModel {

func update() { / 主线程 / }

}

// ✅ 正确:await 后自动回到主线程

let vm = ViewModel()

Task { @MainActor in

await someAsyncMethod()

vm.update() // 安全

}

// ❌ 错误:非隔离上下文访问

Task {

await someAsyncMethod()

// vm.update() // ❌ 编译错误

}


---

# Widget 开发

- [widget.md](references/widget.md) — TimelineProvider / App Group / Interactive Widget / Lock Screen Widget

# 国际化与本地化

- [localization.md](references/localization.md) — NSLocalizedString / String Catalog / 格式化 / RTL / App Store 本地化

# Swift Concurrency 权威参考

- [swift-concurrency.md](references/swift-concurrency.md) — async/await / TaskGroup / MainActor / Actor / Sendable / Swift 6

## 快速参考

### SwiftUI 状态装饰器速查

| 装饰器 | 作用域 | 父传子 | 创建者 |
|--------|--------|--------|--------|
| @State | 局部 | ❌ | 视图 |
| @Binding | 局部 | ✅ | 视图 |
| @StateObject | 局部 | ❌ | 视图 |
| @ObservedObject | 局部 | ✅ | 父视图 |
| @EnvironmentObject | 全局 | ✅ | 任意 |
| @AppStorage | 全局 | ✅ | UserDefaults |
| @SceneStorage | 全局 | ✅ | Scene |
| @FocusState | 局部 | ✅ | 视图 |
| @ScaledMetric | 局部 | ✅ | 视图 |

### UIKit vs SwiftUI 生命周期

| 阶段 | UIKit | SwiftUI |
|------|-------|---------|
| 创建 | `init` | `@State init` |
| 加载视图 | `loadView` | body |
| 视图加载 | `viewDidLoad` | `.task` |
| 即将显示 | `viewWillAppear` | `.onAppear` |
| 已显示 | `viewDidAppear` | — |
| 即将消失 | `viewWillDisappear` | `.onDisappear` |
| 已消失 | `viewDidDisappear` | — |
| 内存警告 | `didReceiveMemoryWarning` | `.onChange` |

### iOS 版本支持速查

| API | 最低版本 |
|-----|---------|
| NavigationStack | iOS 16+ |
| @MainActor | iOS 16+ / Swift 5.5+ |
| SwiftUI Charts | iOS 16+ |
| AnyCancellable store | iOS 13+ |
| AsyncSequence | Swift 5.5+ |
| WidgetKit | iOS 14+ |
| Live Activities | iOS 16.1+ |
| Interactive Widget | iOS 17+ |

### Auto Layout 速查

// 核心约束

label.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([

label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),

label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),

label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),

label.heightAnchor.constraint(greaterThanOrEqualToConstant: 44)

])

// SnapKit

view.snp.makeConstraints { make in

make.edges.equalToSuperview().inset(16)

make.height.greaterThanOrEqualTo(100)

make.width.equalToSuperview().multipliedBy(0.5)

}

// 优先级

.widthAnchor.constraint(equalToConstant: 100).priority = .defaultHigh // 750

.widthAnchor.constraint(equalToConstant: 50).priority = .defaultLow // 250

.widthAnchor.constraint(equalToConstant: 0).priority = .required // 1000


### 网络状态速查

| 状态 | SwiftUI | UIKit |
|------|---------|-------|
| 空闲 | `isLoading = false` | `state = .idle` |
| 加载中 | `isLoading = true` | `state = .loading` |
| 成功 | `@Published var items` | `delegate?.didFinish` |
| 错误 | `@Published var error` | `delegate?.didFail` |

### Combine 操作符速查

| 操作符 | 用途 | 示例 |
|--------|------|------|
| `map` | 转换值 | `.map { $0 * 2 }` |
| `filter` | 过滤 | `.filter { $0 > 0 }` |
| `flatMap` | 展平 | `.flatMap { $0.publisher }` |
| `debounce` | 防抖 | `.debounce(for: .milliseconds(300), scheduler: RunLoop.main)` |
| `throttle` | 节流 | `.throttle(for: .seconds(1), scheduler: RunLoop.main, latest: true)` |
| `combineLatest` | 合并 | `Publishers.CombineLatest(a, b)` |
| `merge` | 合并同类型 | `a.merge(with: b)` |
| `catch` | 错误处理 | `.catch { Just(default) }` |
| `retry` | 重试 | `.retry(3)` |
| `zip` | 配对 | `a.zip(b)` |

### Auto Layout 速查

// 核心约束

label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16)

label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16)

label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)

label.heightAnchor.constraint(greaterThanOrEqualToConstant: 44)

// SnapKit

view.snp.makeConstraints { make in

make.edges.equalToSuperview().inset(16)

make.height.greaterThanOrEqualTo(100)

}

// 优先级

.widthAnchor.constraint(equalToConstant: 100).priority = .defaultHigh

.widthAnchor.constraint(equalToConstant: 50).priority = .defaultLow


### Widget 尺寸速查

| 尺寸 | 宽度 | 高度 | 用途 |
|------|------|------|------|
| systemSmall | 155pt | 155pt | 单指标 |
| systemMedium | 329pt | 155pt | 双指标 |
| systemLarge | 329pt | 345pt | 列表卡片 |
| accessoryCircular | — | — | 锁屏圆形 |
| accessoryRectangular | — | — | 锁屏矩形 |

### 常用尺寸速查

| 场景 | 尺寸 |
|------|------|
| 最小点击区域 | 44pt |
| 标准间距 | 16pt |
| 大间距 | 24pt |
| 安全区留边 | 16pt |
| TabBar 高度 | 49pt |
| NavigationBar 高度 | 44pt |
| Widget 圆角 | 20pt |
| 按钮圆角 | 8pt |
| 图片圆角 | 12pt |

### Swift Concurrency 速查

// async/await

func fetch() async throws -> Data

// Task

Task { await fetch() }

Task.detached { await fetch() }

// TaskGroup

await withTaskGroup(of: Data.self) { group in

group.addTask { await fetch() }

}

// MainActor

@MainActor func update() { }

Task { @MainActor in update() }

// Sendable

struct User: Sendable { let id: String }

actor SafeCounter { }

// 取消

Task.checkCancellation()

Task.isCancelled

task.cancel()


### 生命周期速查

| 事件 | SwiftUI | UIKit |
|------|---------|-------|
| 视图出现 | `.onAppear` | `viewDidAppear` |
| 视图消失 | `.onDisappear` | `viewDidDisappear` |
| 应用激活 | `.onReceive` | `applicationDidBecomeActive` |
| 应用休眠 | — | `applicationWillResignActive` |



## 输出格式规范

当使用本技能回答用户问题时,遵循以下格式:

### 回复结构
1. **直接回答** — 一段简洁的话给出核心答案
2. **代码示例** — 提供完整的 SwiftUI/UIKit 代码(如需)
3. **实现要点** — 关键步骤和注意事项
4. **避坑提醒** — 常见错误+正确做法

### 示例回复(网络请求)

> SwiftUI 推荐使用 async/await 处理网络请求。定义一个 `NetworkService` actor 封装 `URLSession`,在 ViewModel 中用 `@Published` 管理状态。示例:定义 `fetch<T: Decodable>` 泛型方法,用 `Task` 调用并更新 `@Published` 属性。错误处理用 `do-catch`,始终给用户反馈。

### 禁用格式
- ❌ 不要显式分层(避免"第一层/第二层/框架分析"等字眼)
- ❌ 不要长篇解释概念,要直接给出实现
- ❌ 不要只给代码片段,要给完整可运行的示例
- ✅ 输出应是一段干净的话 + 完整代码

版本历史

共 1 个版本

  • v1.0.1 当前
    2026-05-07 13:29 安全 安全

安全检测

腾讯云安全 (Keen)

安全,无风险
查看报告

腾讯云安全 (Sanbu)

安全,无风险
查看报告

🔗 相关推荐

Software Manager Skill

yhongm
软件产品经理/软件开发经理技能支持。触发条件包括:需求分析、PRD撰写、产品设计、路线图规划、竞品分析、数据分析;职责与成长路径咨询;产品架构图、流程图、时序图、甘特图等Mermaid图生成;从零到一输出PRD文档等。
★ 0 📥 616

Code Comment

yhongm
所有编码任务的注释规范。任务涉及代码编写、修改、审查、重构时(包括新功能、bug 修复、code review、工具函数或代码片段解释),必须触发此 skill,确保注释简练、纯中文、聚焦 Why、无 AI 味。支持 Java、C++、Ko
★ 0 📥 471
developer-tools

百度搜索

yhongm
使用百度 AI 搜索 API 进行 Web 搜索。优先使用 API 模式,配额不足时自动切换到浏览器模式。支持中文搜索、新闻搜索等功能。
★ 2 📥 2,522