Ios Dev Skill

v1.0.1

iOS/macOS 开发技能(SwiftUI + UIKit)与 Swift 6.3 语言权威参考合集。覆盖 MVVM/Cocoa 架构、 网络层(URLSession/async-await)、状态管理(@State/@Published/Combine)、依赖注入、 Navigation、性能优化、单元测试/...

0· 79·0 current·0 all-time

Install

OpenClaw Prompt Flow

Install with OpenClaw

Best for remote or guided setup. Copy the exact prompt, then paste it into OpenClaw for yhongm/ios-dev-skill.

Previewing Install & Setup.
Prompt PreviewInstall & Setup
Install the skill "Ios Dev Skill" (yhongm/ios-dev-skill) from ClawHub.
Skill page: https://clawhub.ai/yhongm/ios-dev-skill
Keep the work scoped to this skill only.
After install, inspect the skill metadata and help me finish setup.
Use only the metadata you can verify from ClawHub; do not invent missing requirements.
Ask before making any broader environment changes.

Command Line

CLI Commands

Use the direct CLI path if you want to install manually and keep every step visible.

OpenClaw CLI

Bare skill slug

openclaw skills install ios-dev-skill

ClawHub CLI

Package manager switcher

npx clawhub@latest install ios-dev-skill
Security Scan
Capability signals
Crypto
These labels describe what authority the skill may exercise. They are separate from suspicious or malicious moderation verdicts.
VirusTotalVirusTotal
Benign
View report →
OpenClawOpenClaw
Benign
high confidence
Purpose & Capability
Name/description (iOS/macOS, Swift, SwiftUI/ UIKit, architecture, networking, testing, etc.) align with the included files (SKILL.md, README, many reference markdowns). The skill does not request unrelated credentials, binaries, or config paths.
Instruction Scope
SKILL.md contains documentation and trigger rules for iOS/Swift topics. It does not direct the agent to read system files, access hidden endpoints, exfiltrate data, or use environment variables beyond what is declared (none). No vague 'gather any context' instructions are present.
Install Mechanism
No install spec or code files to write/execute — instruction-only. This is the lowest-risk install mechanism and consistent with a documentation/reference skill.
Credentials
The skill declares no required env vars, no credentials, and no config paths. That is proportionate for an authoring/reference skill that provides code examples and guidance.
Persistence & Privilege
Flags: always=false, user-invocable=true, disable-model-invocation=false (normal). The skill does not request persistent privileges or modify other skills. Autonomous invocation is permitted by default but not combined with any broad privileges here.
Assessment
This skill is a packaging of iOS/Swift reference material and appears coherent and low-risk: it is instruction-only, asks for no credentials, and installs nothing. Two practical checks before installing: (1) provenance — the source/homepage is unknown, so verify the content quality and licensing if that matters to you; (2) watch for future updates: if a later version adds install steps or requests API keys/credentials, re-evaluate before granting them. Otherwise it's safe to use as a read-only reference.

Like a lobster shell, security has layers — review code before you run it.

latestvk976f78x8zsvpjf900tsdyvg8585ckd5
79downloads
0stars
2versions
Updated 5d ago
v1.0.1
MIT-0

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 内存管理

```swift
import Combine

class ViewModel {
    private var cancellables = Set<AnyCancellable>()

    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<AnyCancellable>()

    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配对组合

性能优化

场景SwiftUIUIKit
列表滚动LazyVStackUITableView/UICollectionView
图片缓存AsyncImage + 第三方库SDWebImage/Kingfisher
预取数据.task modifierUITableViewDataSourcePrefetching
后台任务Task.detachedasync/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
Double64位浮点3.14159
Float32位浮点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<Int>()
let genres: Set<String> = ["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"
}

结构体与类

对比

特性structclass
类型值类型引用类型
继承
初始化自动生成手动
析构
引用计数
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<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}
swapTwoValues(&x, &y)

类型约束

func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind { return index }
    }
    return nil
}

泛型类型

struct Stack<Element> {
    var items: [Element] = []
    mutating func push(_ item: Element) { items.append(item) }
    mutating func pop() -> Element { items.removeLast() }
}

where 子句

func allItemsMatch<C1: Container, C2: Container>
    (_ 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..<self { task() }
    }
}

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 访问)

更新频率:随 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<Int64>("id")
    private let name = SQLite.Expression<String>("name")
    private let email = SQLite.Expression<String>("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<User>

    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<Section, Item>!

    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<Section, Item>(
            collectionView: collectionView
        ) { collectionView, indexPath, item in
            collectionView.dequeueConfiguredReusableCell(
                using: cellRegistration, for: indexPath, item: item
            )
        }
    }

    private func applySnapshot() {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        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 — TimelineProvider / App Group / Interactive Widget / Lock Screen Widget

国际化与本地化

  • localization.md — NSLocalizedString / String Catalog / 格式化 / RTL / App Store 本地化

Swift Concurrency 权威参考

快速参考

SwiftUI 状态装饰器速查

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

UIKit vs SwiftUI 生命周期

阶段UIKitSwiftUI
创建init@State init
加载视图loadViewbody
视图加载viewDidLoad.task
即将显示viewWillAppear.onAppear
已显示viewDidAppear
即将消失viewWillDisappear.onDisappear
已消失viewDidDisappear
内存警告didReceiveMemoryWarning.onChange

iOS 版本支持速查

API最低版本
NavigationStackiOS 16+
@MainActoriOS 16+ / Swift 5.5+
SwiftUI ChartsiOS 16+
AnyCancellable storeiOS 13+
AsyncSequenceSwift 5.5+
WidgetKitiOS 14+
Live ActivitiesiOS 16.1+
Interactive WidgetiOS 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

网络状态速查

状态SwiftUIUIKit
空闲isLoading = falsestate = .idle
加载中isLoading = truestate = .loading
成功@Published var itemsdelegate?.didFinish
错误@Published var errordelegate?.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 尺寸速查

尺寸宽度高度用途
systemSmall155pt155pt单指标
systemMedium329pt155pt双指标
systemLarge329pt345pt列表卡片
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()

生命周期速查

事件SwiftUIUIKit
视图出现.onAppearviewDidAppear
视图消失.onDisappearviewDidDisappear
应用激活.onReceiveapplicationDidBecomeActive
应用休眠applicationWillResignActive

输出格式规范

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

回复结构

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

示例回复(网络请求)

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

禁用格式

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

Comments

Loading comments...