Skip to content

Commit 9c9c249

Browse files
authored
Merge pull request #10 from pepicrft/feat/ai-agent-skill
feat: add AI agent skill for Swift Concurrency
2 parents 93b8169 + aa9d579 commit 9c9c249

File tree

14 files changed

+1055
-10
lines changed

14 files changed

+1055
-10
lines changed

AGENTS.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,31 @@ The site is designed for deployment on:
145145

146146
The `_redirects` file handles language-based routing using the Accept-Language header.
147147

148+
## AI Agent Skill
149+
150+
The project includes a **SKILL.md** file at `src/SKILL.md` that packages the Swift Concurrency knowledge for use with AI coding agents (Claude Code, Cursor, etc.).
151+
152+
### Keeping SKILL.md in Sync
153+
154+
When updating content in the language files (especially `src/en/index.md`), ensure that significant changes are reflected in `src/SKILL.md`:
155+
156+
- New concepts or mental models
157+
- Updated best practices or recommendations
158+
- New common mistakes or pitfalls
159+
- Changes to the "Office Building" analogy
160+
- Updates related to new Swift versions (e.g., Swift 6.2 Approachable Concurrency)
161+
162+
The skill file should remain a condensed, actionable reference. It does not need to mirror every detail, but should capture the essential guidance that helps developers write correct concurrent Swift code.
163+
164+
### Skill Distribution
165+
166+
The SKILL.md file is served as a static asset at `https://fuckingapproachableswiftconcurrency.com/SKILL.md`. Users can:
167+
168+
1. Download it directly and place it in their agent's skills directory
169+
2. Reference it in their agent configuration
170+
3. Use it as a personal skill (`~/.claude/skills/swift-concurrency/SKILL.md`)
171+
4. Use it as a project skill (`.claude/skills/swift-concurrency/SKILL.md`)
172+
148173
## Credits
149174

150175
- Original content and mental models inspired by [Matt Massicotte's blog](https://www.massicotte.org/)

eleventy.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export default function(eleventyConfig) {
66
eleventyConfig.addPassthroughCopy("src/css");
77
eleventyConfig.addPassthroughCopy("src/images");
88
eleventyConfig.addPassthroughCopy("src/_redirects");
9+
eleventyConfig.addPassthroughCopy("src/SKILL.md");
910

1011
// Add language data globally
1112
eleventyConfig.addGlobalData("languages", {

src/SKILL.md

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
---
2+
name: swift-concurrency
3+
description: Expert guidance on Swift Concurrency concepts. Use when working with async/await, Tasks, actors, MainActor, Sendable, isolation domains, or debugging concurrency compiler errors. Helps write safe concurrent Swift code.
4+
---
5+
6+
# Swift Concurrency Skill
7+
8+
This skill provides expert guidance on Swift's concurrency system based on the mental models from [Fucking Approachable Swift Concurrency](https://fuckingapproachableswiftconcurrency.com).
9+
10+
## Core Mental Model: The Office Building
11+
12+
Think of your app as an office building where **isolation domains** are private offices with locks:
13+
14+
- **MainActor** = Front desk (handles all UI interactions, only one exists)
15+
- **actor** types = Department offices (Accounting, Legal, HR - each protects its own data)
16+
- **nonisolated** code = Hallways (shared space, no private documents)
17+
- **Sendable** types = Photocopies (safe to share between offices)
18+
- **Non-Sendable** types = Original documents (must stay in one office)
19+
20+
You can't barge into someone's office. You knock (`await`) and wait.
21+
22+
## Async/Await
23+
24+
An `async` function can pause. Use `await` to suspend until work finishes:
25+
26+
```swift
27+
func fetchUser(id: Int) async throws -> User {
28+
let (data, _) = try await URLSession.shared.data(from: url)
29+
return try JSONDecoder().decode(User.self, from: data)
30+
}
31+
```
32+
33+
For parallel work, use `async let`:
34+
35+
```swift
36+
async let avatar = fetchImage("avatar.jpg")
37+
async let banner = fetchImage("banner.jpg")
38+
return Profile(avatar: try await avatar, banner: try await banner)
39+
```
40+
41+
## Tasks
42+
43+
A `Task` is a unit of async work you can manage:
44+
45+
```swift
46+
// SwiftUI - cancels when view disappears
47+
.task { avatar = await downloadAvatar() }
48+
49+
// Manual task creation
50+
Task { await saveProfile() }
51+
52+
// Parallel work with TaskGroup
53+
try await withThrowingTaskGroup(of: Void.self) { group in
54+
group.addTask { avatar = try await downloadAvatar() }
55+
group.addTask { bio = try await fetchBio() }
56+
try await group.waitForAll()
57+
}
58+
```
59+
60+
Child tasks in a group: cancellation propagates, errors cancel siblings, waits for all to complete.
61+
62+
## Isolation Domains
63+
64+
Swift asks "who can access this data?" not "which thread?". Three isolation domains:
65+
66+
### 1. MainActor
67+
68+
For UI. Everything UI-related should be here:
69+
70+
```swift
71+
@MainActor
72+
class ViewModel {
73+
var items: [Item] = [] // Protected by MainActor
74+
}
75+
```
76+
77+
### 2. Actors
78+
79+
Protect their own mutable state with exclusive access:
80+
81+
```swift
82+
actor BankAccount {
83+
var balance: Double = 0
84+
func deposit(_ amount: Double) { balance += amount }
85+
}
86+
87+
await account.deposit(100) // Must await from outside
88+
```
89+
90+
### 3. Nonisolated
91+
92+
Opts out of actor isolation. Cannot access actor's protected state:
93+
94+
```swift
95+
actor BankAccount {
96+
nonisolated func bankName() -> String { "Acme Bank" }
97+
}
98+
let name = account.bankName() // No await needed
99+
```
100+
101+
## Approachable Concurrency (Swift 6.2+)
102+
103+
Two build settings that simplify the mental model:
104+
105+
- **SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor**: Everything runs on MainActor unless you say otherwise
106+
- **SWIFT_APPROACHABLE_CONCURRENCY = YES**: nonisolated async functions stay on caller's actor
107+
108+
```swift
109+
// Runs on MainActor (default)
110+
func updateUI() async { }
111+
112+
// Runs on background (opt-in)
113+
@concurrent func processLargeFile() async { }
114+
```
115+
116+
## Sendable
117+
118+
Marks types safe to pass across isolation boundaries:
119+
120+
```swift
121+
// Sendable - value type, each gets a copy
122+
struct User: Sendable {
123+
let id: Int
124+
let name: String
125+
}
126+
127+
// Non-Sendable - mutable class state
128+
class Counter {
129+
var count = 0
130+
}
131+
```
132+
133+
Automatically Sendable:
134+
- Structs/enums with only Sendable properties
135+
- Actors (protect their own state)
136+
- @MainActor types (MainActor serializes access)
137+
138+
For thread-safe classes with internal synchronization:
139+
140+
```swift
141+
final class ThreadSafeCache: @unchecked Sendable {
142+
private let lock = NSLock()
143+
private var storage: [String: Data] = [:]
144+
}
145+
```
146+
147+
## Isolation Inheritance
148+
149+
With Approachable Concurrency, isolation flows from MainActor through your code:
150+
151+
- **Functions**: Inherit caller's isolation unless explicitly marked
152+
- **Closures**: Inherit from context where defined
153+
- **Task { }**: Inherits actor isolation from creation site
154+
- **Task.detached { }**: No inheritance (rarely needed)
155+
156+
## Common Mistakes to Avoid
157+
158+
### 1. Thinking async = background
159+
160+
```swift
161+
// Still blocks main thread!
162+
@MainActor func slowFunction() async {
163+
let result = expensiveCalculation() // Synchronous = blocking
164+
}
165+
// Fix: Use @concurrent for CPU-heavy work
166+
```
167+
168+
### 2. Creating too many actors
169+
170+
Most things can live on MainActor. Only create actors when you have shared mutable state that can't be on MainActor.
171+
172+
### 3. Making everything Sendable
173+
174+
Not everything needs to cross boundaries. Step back and ask if data actually moves between isolation domains.
175+
176+
### 4. Using MainActor.run unnecessarily
177+
178+
```swift
179+
// Unnecessary
180+
await MainActor.run { self.data = data }
181+
182+
// Better - annotate the function
183+
@MainActor func loadData() async { self.data = await fetchData() }
184+
```
185+
186+
### 5. Blocking the cooperative thread pool
187+
188+
Never use DispatchSemaphore, DispatchGroup.wait() in async code. Risks deadlock.
189+
190+
### 6. Creating unnecessary Tasks
191+
192+
```swift
193+
// Bad - unstructured
194+
Task { await fetchUsers() }
195+
Task { await fetchPosts() }
196+
197+
// Good - structured concurrency
198+
async let users = fetchUsers()
199+
async let posts = fetchPosts()
200+
await (users, posts)
201+
```
202+
203+
## Quick Reference
204+
205+
| Keyword | Purpose |
206+
|---------|---------|
207+
| `async` | Function can pause |
208+
| `await` | Pause here until done |
209+
| `Task { }` | Start async work, inherits context |
210+
| `Task.detached { }` | Start async work, no context |
211+
| `@MainActor` | Runs on main thread |
212+
| `actor` | Type with isolated mutable state |
213+
| `nonisolated` | Opts out of actor isolation |
214+
| `Sendable` | Safe to pass between isolation domains |
215+
| `@concurrent` | Always run on background (Swift 6.2+) |
216+
| `async let` | Start parallel work |
217+
| `TaskGroup` | Dynamic parallel work |
218+
219+
## When the Compiler Complains
220+
221+
Trace the isolation: Where did it come from? Where is code trying to run? What data crosses a boundary?
222+
223+
The answer is usually obvious once you ask the right question.
224+
225+
## Further Reading
226+
227+
- [Matt Massicotte's Blog](https://www.massicotte.org/) - The source of these mental models
228+
- [Swift Concurrency Documentation](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/)
229+
- [WWDC21: Meet async/await](https://developer.apple.com/videos/play/wwdc2021/10132/)
230+
- [WWDC21: Protect mutable state with actors](https://developer.apple.com/videos/play/wwdc2021/10133/)

src/ar/index.md

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,13 @@ func fetchAll() async {
650650
| `async let` | ابدأ عملاً متوازياً |
651651
| `TaskGroup` | عمل متوازي ديناميكي |
652652

653-
## قراءة إضافية
653+
</div>
654+
</section>
655+
656+
<section id="further-reading">
657+
<div class="container">
658+
659+
## [قراءة إضافية](#further-reading)
654660

655661
<div class="resources">
656662
<h4>مدونة Matt Massicotte (موصى به بشدة)</h4>
@@ -677,3 +683,75 @@ func fetchAll() async {
677683

678684
</div>
679685
</section>
686+
687+
<section id="ai-skill">
688+
<div class="container">
689+
690+
## [مهارة وكيل الذكاء الاصطناعي](#ai-skill)
691+
692+
هل تريد أن يفهم مساعد البرمجة بالذكاء الاصطناعي Swift Concurrency؟ نقدم ملف **[SKILL.md](/SKILL.md)** الذي يحزم هذه النماذج الذهنية لوكلاء الذكاء الاصطناعي مثل Claude Code و Codex و Amp و OpenCode وغيرها.
693+
694+
<div class="tip">
695+
<h4>ما هي المهارة؟</h4>
696+
697+
المهارة هي ملف markdown يعلّم وكلاء البرمجة بالذكاء الاصطناعي معرفة متخصصة. عندما تضيف مهارة Swift Concurrency إلى وكيلك، فإنه يطبق هذه المفاهيم تلقائياً عند مساعدتك في كتابة كود Swift غير متزامن.
698+
</div>
699+
700+
### كيفية الاستخدام
701+
702+
اختر وكيلك ونفّذ الأوامر:
703+
704+
<div class="code-tabs">
705+
<div class="code-tabs-nav">
706+
<button class="active">Claude Code</button>
707+
<button>Codex</button>
708+
<button>Amp</button>
709+
<button>OpenCode</button>
710+
</div>
711+
<div class="code-tab-content active">
712+
713+
```bash
714+
# مهارة شخصية (جميع مشاريعك)
715+
mkdir -p ~/.claude/skills/swift-concurrency
716+
curl -o ~/.claude/skills/swift-concurrency/SKILL.md https://fuckingapproachableswiftconcurrency.com/SKILL.md
717+
# مهارة المشروع (هذا المشروع فقط)
718+
mkdir -p .claude/skills/swift-concurrency
719+
curl -o .claude/skills/swift-concurrency/SKILL.md https://fuckingapproachableswiftconcurrency.com/SKILL.md
720+
```
721+
722+
</div>
723+
<div class="code-tab-content">
724+
725+
```bash
726+
# تعليمات عامة (جميع مشاريعك)
727+
curl -o ~/.codex/AGENTS.md https://fuckingapproachableswiftconcurrency.com/SKILL.md
728+
# تعليمات المشروع (هذا المشروع فقط)
729+
curl -o AGENTS.md https://fuckingapproachableswiftconcurrency.com/SKILL.md
730+
```
731+
732+
</div>
733+
<div class="code-tab-content">
734+
735+
```bash
736+
# تعليمات المشروع (موصى به)
737+
curl -o AGENTS.md https://fuckingapproachableswiftconcurrency.com/SKILL.md
738+
```
739+
740+
</div>
741+
<div class="code-tab-content">
742+
743+
```bash
744+
# قواعد عامة (جميع مشاريعك)
745+
mkdir -p ~/.config/opencode
746+
curl -o ~/.config/opencode/AGENTS.md https://fuckingapproachableswiftconcurrency.com/SKILL.md
747+
# قواعد المشروع (هذا المشروع فقط)
748+
curl -o AGENTS.md https://fuckingapproachableswiftconcurrency.com/SKILL.md
749+
```
750+
751+
</div>
752+
</div>
753+
754+
تتضمن المهارة تشبيه مبنى المكاتب، وأنماط العزل، ودليل Sendable، والأخطاء الشائعة، وجداول المرجع السريع. سيستخدم وكيلك هذه المعرفة تلقائياً عند العمل مع كود Swift Concurrency.
755+
756+
</div>
757+
</section>

src/css/style.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,15 @@ p {
400400
border-bottom: 2px solid transparent;
401401
margin-bottom: -2px;
402402
transition: color 0.15s, border-color 0.15s;
403+
display: inline-flex;
404+
align-items: center;
405+
gap: 0.4rem;
406+
}
407+
408+
.code-tabs-nav button svg {
409+
width: 1em;
410+
height: 1em;
411+
flex-shrink: 0;
403412
}
404413

405414
.code-tabs-nav button:hover {

0 commit comments

Comments
 (0)