mcpField
S
swift

swift

github.com/dralgorhythm开发12956 次安装15 周前更新
代码仓库

关于 swift

面向 Swift 与 iOS 开发的技能指南,涵盖项目配置、语言模式、SwiftUI、并发、测试和代码工具。适合构建及维护 Swift 应用的开发者。

主要功能

  1. 01Swift Package Manager 与 Xcode 项目设置
  2. 02Optional、Result 与协议模式
  3. 03async/await、结构化并发与 Actor
  4. 04SwiftUI 状态和依赖注入
  5. 05Swift Testing 与 XCTest
  6. 06SwiftLint 和 SwiftFormat 配置

适用场景

  1. 01初始化 Swift 可执行程序包
  2. 02编写异步网络请求与并发任务
  3. 03构建带状态的 SwiftUI 界面
  4. 04为服务编写测试并检查代码格式

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dralgorhythm/claude-agentic-framework --skill swift

安装后在 Claude Code 中输入/swift确认已加载。

README 原文English

来自项目 README

README 暂无中文译文,以下为英文原文;中文介绍见上方。

Swift / iOS Development

Project Setup

Swift Package Manager
# Initialize new package
swift package init --type executable

# Add dependencies to Package.swift
# swift package update
Package.swift
// swift-tools-version: 5.10
import PackageDescription

let package = Package(
    name: "MyApp",
    platforms: [.iOS(.v17), .macOS(.v14)],
    products: [
        .library(name: "MyApp", targets: ["MyApp"]),
    ],
    dependencies: [
        // Add dependencies here
    ],
    targets: [
        .target(name: "MyApp", dependencies: []),
        .testTarget(name: "MyAppTests", dependencies: ["MyApp"]),
    ]
)
Xcode Project
# Create new Xcode project via Xcode or:
# Use SwiftUI App template for new projects
# Target iOS 17+ for latest APIs

Type Patterns

Optionals
// Safe unwrapping
if let user = optionalUser {
    print(user.name)
}

// Guard for early exit
guard let user = optionalUser else {
    return
}

// Optional chaining
let name = user?.profile?.displayName ?? "Anonymous"

// Nil coalescing
let displayName = user?.name ?? "Guest"
Result Type
enum NetworkError: Error {
    case invalidURL
    case noData
    case decodingFailed
}

func fetchUser(id: String) async -> Result<User, NetworkError> {
    guard let url = URL(string: "https://api.example.com/users/\(id)") else {
        return .failure(.invalidURL)
    }

    do {
        let (data, _) = try await URLSession.shared.data(from: url)
        let user = try JSONDecoder().decode(User.self, from: data)
        return .success(user)
    } catch {
        return .failure(.decodingFailed)
    }
}

// Usage
switch await fetchUser(id: "123") {
case .success(let user):
    print("Got user: \(user.name)")
case .failure(let error):
    print("Error: \(error)")
}
Protocols & Extensions
protocol Identifiable {
    var id: UUID { get }
}

protocol Displayable {
    var displayName: String { get }
}

extension User: Identifiable, Displayable {
    var displayName: String {
        "\(firstName) \(lastName)"
    }
}

Error Handling

// Throwing functions
func loadConfig() throws -> Config {
    guard let data = FileManager.default.contents(atPath: configPath) else {
        throw ConfigError.fileNotFound
    }
    return try JSONDecoder().decode(Config.self, from: data)
}

// Do-catch
do {
    let config = try loadConfig()
    print(config)
} catch ConfigError.fileNotFound {
    print("Config file missing")
} catch {
    print("Unknown error: \(error)")
}

// Try? for optional result
let config = try? loadConfig()

// Try! only when failure is impossible
let bundledConfig = try! loadBundledConfig()

Async/Await Patterns

Basic Async
func fetchUsers() async throws -> [User] {
    let (data, _) = try await URLSession.shared.data(from: usersURL)
    return try JSONDecoder().decode([User].self, from: data)
}

// Calling async functions
Task {
    do {
        let users = try await fetchUsers()
        await MainActor.run {
            self.users = users
        }
    } catch {
        print("Failed: \(error)")
    }
}
Structured Concurrency
// Parallel execution
async let users = fetchUsers()
async let posts = fetchPosts()
let (userList, postList) = try await (users, posts)

// Task groups
func fetchAllUserData(ids: [String]) async throws -> [UserData] {
    try await withThrowingTaskGroup(of: UserData.self) { group in
        for id in ids {
            group.addTask {
                try await fetchUserData(id: id)
            }
        }
        return try await group.reduce(into: []) { $0.append($1) }
    }
}
Actors
actor UserCache {
    private var cache: [String: User] = [:]

    func get(_ id: String) -> User? {
        cache[id]
    }

    func set(_ user: User) {
        cache[user.id] = user
    }
}

// Usage
let cache = UserCache()
await cache.set(user)
let cached = await cache.get("123")

SwiftUI Patterns

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

    var body: some View {
        VStack(spacing: 16) {
            Text("Count: \(count)")
                .font(.title)

            Button("Increment") {
                count += 1
            }

            TextField("Username", text: $username)
                .textFieldStyle(.roundedBorder)
        }
        .padding()
    }
}
Observable ViewModel
@Observable
class UserViewModel {
    var users: [User] = []
    var isLoading = false
    var error: Error?

    func loadUsers() async {
        isLoading = true
        defer { isLoading = false }

        do {
            users = try await userService.fetchAll()
        } catch {
            self.error = error
        }
    }
}

struct UserListView: View {
    @State private var viewModel = UserViewModel()

    var body: some View {
        List(viewModel.users) { user in
            UserRow(user: user)
        }
        .task {
            await viewModel.loadUsers()
        }
        .overlay {
            if viewModel.isLoading {
                ProgressView()
            }
        }
    }
}
Environment & Dependency Injection
// Define environment key
struct UserServiceKey: EnvironmentKey {
    static let defaultValue: UserService = .live
}

extension EnvironmentValues {
    var userService: UserService {
        get { self[UserServiceKey.self] }
        set { self[UserServiceKey.self] = newValue }
    }
}

// Use in view
struct ProfileView: View {
    @Environment(\.userService) private var userService

    var body: some View {
        // Use userService
    }
}

Testing

Swift Testing (Swift 6+)
import Testing

@Suite("UserService Tests")
struct UserServiceTests {
    let service = UserService()

    @Test("creates user with valid email")
    func createUser() async throws {
        let user = try await service.create(email: "[email protected]")
        #expect(user.email == "[email protected]")
    }

    @Test("throws on invalid email")
    func invalidEmail() async {
        await #expect(throws: ValidationError.self) {
            try await service.create(email: "invalid")
        }
    }

    @Test("fetches user by ID", arguments: ["user1", "user2", "user3"])
    func fetchUser(id: String) async throws {
        let user = try await service.fetch(id: id)
        #expect(user.id == id)
    }
}
XCTest (Legacy)
import XCTest
@testable import MyApp

final class UserServiceTests: XCTestCase {
    var service: UserService!

    override func setUp() {
        super.setUp()
        service = UserService()
    }

    func testCreateUser() async throws {
        let user = try await service.create(email: "[email protected]")
        XCTAssertEqual(user.email, "[email protected]")
    }

    func testInvalidEmailThrows() async {
        do {
            _ = try await service.create(email: "invalid")
            XCTFail("Expected error")
        } catch {
            XCTAssertTrue(error is ValidationError)
        }
    }
}

Tooling

# SwiftLint (linting)
brew install swiftlint
swiftlint lint
swiftlint lint --fix

# SwiftFormat (formatting)
brew install swiftformat
swiftformat .
swiftformat . --lint

# Run tests
swift test
xcodebuild test -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 15'

# Build
swift build
xcodebuild build -scheme MyApp

# fastlane (CI/CD)
brew install fastlane
fastlane init
fastlane ios test
fastlane ios beta  # Deploy to TestFlight
.swiftlint.yml
disabled_rules:
  - trailing_whitespace
  - line_length

opt_in_rules:
  - empty_count
  - empty_string

excluded:
  - Pods
  - .build
.swiftformat
--indent 4
--allman false
--wraparguments before-first
--wrapparameters before-first
--self remove
--importgrouping alphabetized

评论

支持 Markdown