Claude-skill-registry ios-reviewer
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/ios-reviewer" ~/.claude/skills/majiayu000-claude-skill-registry-ios-reviewer && rm -rf "$T"
manifest:
skills/data/ios-reviewer/SKILL.mdsource content
iOS Reviewer Skill
Purpose
Reviews iOS Swift code for SwiftUI, UIKit, Combine, and Swift Concurrency best practices.
When to Use
- iOS Swift project code review
- "SwiftUI", "UIKit", "Combine", "async/await" mentions
- iOS performance, memory management inspection
- Projects with
or.xcodeproj.xcworkspace
Project Detection
or.xcodeproj
exists.xcworkspace
with iOS platformPackage.swift
existsInfo.plist
files present*.swift
Workflow
Step 1: Analyze Project
**Swift**: 5.9+ **iOS Target**: 15.0+ **UI Framework**: SwiftUI / UIKit **Architecture**: MVVM / TCA / Clean Architecture **Package Manager**: SPM / CocoaPods / Carthage
Step 2: Select Review Areas
AskUserQuestion:
"Which areas to review?" Options: - Full iOS pattern check (recommended) - SwiftUI view patterns - UIKit lifecycle/memory - Combine/async-await usage - Architecture patterns multiSelect: true
Detection Rules
SwiftUI Patterns
| Check | Recommendation | Severity |
|---|---|---|
| Heavy computation in body | Move to ViewModel or task modifier | HIGH |
| Missing @State/@StateObject distinction | Use @StateObject for owned objects | HIGH |
| ObservableObject without @Published | Add @Published to observed properties | HIGH |
| Missing EnvironmentObject injection | Ensure parent provides object | MEDIUM |
| Large View body | Extract to smaller Views | MEDIUM |
// BAD: Heavy computation in body struct MyView: View { var body: some View { let result = expensiveCalculation() // Called every render Text(result) } } // GOOD: Move to ViewModel or use task struct MyView: View { @StateObject var viewModel = MyViewModel() var body: some View { Text(viewModel.result) .task { await viewModel.calculate() } } } // BAD: @State for reference type struct MyView: View { @State var viewModel = MyViewModel() // Won't observe changes } // GOOD: @StateObject for reference type struct MyView: View { @StateObject var viewModel = MyViewModel() } // BAD: Missing @Published class MyViewModel: ObservableObject { var data: [String] = [] // Changes won't trigger view update } // GOOD: Add @Published class MyViewModel: ObservableObject { @Published var data: [String] = [] }
UIKit Patterns
| Check | Issue | Severity |
|---|---|---|
| Strong delegate reference | Retain cycle risk | CRITICAL |
| Missing removeObserver | Memory leak | HIGH |
| Force unwrap IBOutlet | Crash risk | HIGH |
| viewDidLoad network call | UX issue | MEDIUM |
| Main thread UI update missing | Undefined behavior | CRITICAL |
// BAD: Strong delegate class MyViewController: UIViewController { var delegate: MyDelegate? // Strong reference! } // GOOD: Weak delegate class MyViewController: UIViewController { weak var delegate: MyDelegate? } // BAD: Missing removeObserver override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, ...) } // GOOD: Remove in deinit deinit { NotificationCenter.default.removeObserver(self) } // BAD: Force unwrap IBOutlet @IBOutlet var titleLabel: UILabel! func setup() { titleLabel.text = "Hello" // Crash if not connected } // GOOD: Safe unwrap @IBOutlet weak var titleLabel: UILabel? func setup() { titleLabel?.text = "Hello" }
Combine Patterns
| Check | Recommendation | Severity |
|---|---|---|
| Missing store in cancellables | Subscription lost | CRITICAL |
| receive(on:) missing for UI | Threading issue | HIGH |
| PassthroughSubject without type erasure | Exposes implementation | MEDIUM |
| Chain without error handling | Silent failures | MEDIUM |
// BAD: Missing store class MyViewModel { func subscribe() { publisher.sink { value in print(value) } // Immediately cancelled! } } // GOOD: Store in cancellables class MyViewModel { private var cancellables = Set<AnyCancellable>() func subscribe() { publisher .sink { value in print(value) } .store(in: &cancellables) } } // BAD: Missing receive(on:) for UI publisher .sink { self.label.text = $0 } // May not be on main thread .store(in: &cancellables) // GOOD: Explicit main thread publisher .receive(on: DispatchQueue.main) .sink { self.label.text = $0 } .store(in: &cancellables)
Swift Concurrency (async/await)
| Check | Recommendation | Severity |
|---|---|---|
| Blocking call in async context | Use async alternative | HIGH |
| Missing @MainActor for UI | Threading issue | HIGH |
| Task without cancellation handling | Resource leak | MEDIUM |
| Unstructured Task in SwiftUI | Use .task modifier | MEDIUM |
// BAD: Blocking in async func fetchData() async -> Data { return URLSession.shared.dataTask(...) // Blocking! } // GOOD: Async alternative func fetchData() async throws -> Data { let (data, _) = try await URLSession.shared.data(from: url) return data } // BAD: Missing @MainActor for UI class MyViewModel: ObservableObject { @Published var items: [Item] = [] func load() async { items = await fetchItems() // May not be on main thread! } } // GOOD: @MainActor @MainActor class MyViewModel: ObservableObject { @Published var items: [Item] = [] func load() async { items = await fetchItems() // Guaranteed main thread } } // BAD: Unstructured Task in SwiftUI struct MyView: View { var body: some View { Text("Hello") .onAppear { Task { await load() } // Not cancelled on disappear } } } // GOOD: Use .task modifier struct MyView: View { var body: some View { Text("Hello") .task { await load() } // Auto-cancelled } }
Memory Management
| Check | Problem | Solution |
|---|---|---|
| Strong self in closure | Retain cycle | [weak self] or [unowned self] |
| Circular reference | Memory leak | Break cycle with weak |
| Large image in memory | OOM risk | Use thumbnails, purge cache |
| Uncancelled Task | Resource leak | Store and cancel |
// BAD: Strong self in closure class MyViewController: UIViewController { func setup() { service.fetch { result in self.update(result) // Strong capture! } } } // GOOD: Weak self class MyViewController: UIViewController { func setup() { service.fetch { [weak self] result in self?.update(result) } } }
Response Template
## iOS Code Review Results **Project**: [name] **Swift**: 5.9 | **iOS Target**: 15.0+ **UI Framework**: SwiftUI/UIKit **Files Analyzed**: X ### SwiftUI Patterns | Status | File | Issue | |--------|------|-------| | HIGH | Views/HomeView.swift | Heavy computation in body (line 45) | | MEDIUM | Views/ProfileView.swift | Large view body, extract components | ### UIKit/Memory | Status | File | Issue | |--------|------|-------| | CRITICAL | Controllers/DetailVC.swift | Strong delegate reference (line 23) | | HIGH | Controllers/ListVC.swift | Missing removeObserver in deinit | ### Combine/Async | Status | File | Issue | |--------|------|-------| | CRITICAL | Services/DataService.swift | Missing cancellable store | | HIGH | ViewModels/MainVM.swift | Missing @MainActor | ### Recommended Actions 1. [ ] Add [weak self] to closures 2. [ ] Use @StateObject for owned ObservableObjects 3. [ ] Add @MainActor to ViewModels 4. [ ] Replace Task with .task modifier
Best Practices
- SwiftUI: Prefer small, composable Views with proper state ownership
- UIKit: Always use weak delegates, clean up observers
- Combine: Store subscriptions, handle threading explicitly
- Concurrency: Use structured concurrency, @MainActor for UI
- Memory: Use weak captures, profile with Instruments
Integration
skill: General Swift code qualitycode-reviewer
skill: iOS XCTest generationtest-generator
skill: iOS security checkssecurity-scanner
Notes
- Based on Swift 5.9+, iOS 15+
- Supports SwiftUI and UIKit projects
- Includes Swift Concurrency patterns (async/await)