| name | lang-swift-dev |
| description | Foundational Swift development patterns covering modern Swift syntax, SwiftUI, protocol-oriented programming, and Cocoa Touch frameworks. Use when writing Swift code, building iOS/macOS/watchOS/tvOS applications, working with SwiftUI or UIKit, understanding Swift concurrency, or needing guidance on Swift project structure. |
Swift Development Fundamentals
Foundational Swift patterns and modern language features for Apple platform development. This skill covers core Swift syntax, SwiftUI, UIKit integration, and protocol-oriented design patterns.
Overview
┌─────────────────────────────────────────────────────────────────┐
│ Swift Development Ecosystem │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ │
│ │ lang-swift-dev │ ◄── You are here │
│ │ (foundation) │ │
│ └────────┬─────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ SwiftUI │ │ UIKit │ │ Swift │ │
│ │ Mastery │ │ Advanced │ │ Package │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
This skill covers:
- Core Swift syntax (optionals, closures, generics, protocols)
- SwiftUI basics (views, state management, modifiers)
- Protocol-oriented programming patterns
- Swift concurrency (async/await, actors, tasks)
- UIKit fundamentals and integration
- Xcode project structure and organization
This skill does NOT cover:
- Advanced SwiftUI architecture - see
swiftui-patterns-advanced
- Complex UIKit patterns - see
uikit-advanced-patterns
- Swift Package Manager publishing - see
swift-package-dev
- iOS app distribution and App Store submission
- Combine framework (prefer Swift concurrency for new code)
Quick Reference
| Task | Pattern |
|---|
| Define optional | var name: String? |
| Unwrap safely | if let name = name { } |
| Guard unwrap | guard let name = name else { return } |
| Define protocol | protocol Named { var name: String { get } } |
| Conform to protocol | extension MyType: Named { } |
| Async function | func fetch() async throws -> Data |
| Call async | let data = try await fetch() |
| SwiftUI view | struct ContentView: View { var body: some View { } } |
| State variable | @State private var count = 0 |
Core Swift Patterns
Optionals and Unwrapping
var username: String?
var age: Int? = nil
if let username = username {
print("Hello, \(username)")
} else {
print("No username set")
}
func greet(name: String?) {
guard let name = name else {
print("Name required")
return
}
print("Hello, \(name)")
}
let displayName = username ?? "Guest"
let uppercased = username?.uppercased()
let name = username!
Closures
let multiply = { (a: Int, b: Int) -> Int in
return a * b
}
let add = { a, b in a + b }
[1, 2, 3].map { number in
number * 2
}
[1, 2, 3].map { $0 * 2 }
func makeIncrementer(amount: Int) -> () -> Int {
var total = 0
return {
total += amount
return total
}
}
let incrementByTwo = makeIncrementer(amount: 2)
print(incrementByTwo())
print(incrementByTwo())
func fetchData(completion: @escaping (Result<, >) -> ) {
.main.asyncAfter(deadline: .now() ) {
completion(.success(()))
}
}
Protocols and Protocol-Oriented Programming
protocol Drawable {
func draw()
}
protocol Identifiable {
var id: String { get }
}
struct Circle: Drawable {
func draw() {
print("Drawing circle")
}
}
struct User: Identifiable, Codable {
let id: String
let name: String
}
extension Drawable {
func draw() {
print("Default drawing")
}
func render() {
print("Rendering...")
draw()
}
}
protocol Container {
associatedtype Item
var items: [Item] { get }
mutating func add(_ : )
}
: {
items: [] []
( : ) {
items.append(item)
}
}
Generics
func swap<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element? {
return items.popLast()
}
}
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? {
for (index, element) in array.enumerated() {
if element == value {
return index
}
}
return nil
}
<: , : >(
: ,
:
) -> . ., .: {
container1.items.count container2.items.count {
}
(container1.items, container2.items).allSatisfy { }
}
Enums and Pattern Matching
enum Direction {
case north, south, east, west
}
enum Result<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}
enum HTTPStatus: Int {
case ok = 200
case notFound = 404
case serverError = 500
}
let result: Result<String, Error> = .success("Data")
switch result {
case .success(let value):
print("Success: \(value)")
case .failure(let error):
print("Error: \(error)")
}
if case .success(let value) = result {
print("Got value: \(value)")
}
{
number()
addition(, )
multiplication(, )
}
Property Wrappers
@State private var count = 0
@Published var username = ""
@Environment(\.colorScheme) var colorScheme
@propertyWrapper
struct Clamped<Value: Comparable> {
private var value: Value
private let range: ClosedRange<Value>
var wrappedValue: Value {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
}
struct Game {
@Clamped(0...100) var health
}
game ()
game.health
game.health
Swift Concurrency
Async/Await
func fetchUser(id: String) async throws -> User {
let url = URL(string: "https://api.example.com/users/\(id)")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}
Task {
do {
let user = try await fetchUser(id: "123")
print("Fetched user: \(user.name)")
} catch {
print("Error: \(error)")
}
}
func loadData() async throws -> (User, [Post]) {
async let user = fetchUser(id: "123")
async let posts = fetchPosts(userId: )
(user, posts)
}
(: ) {
line url.lines {
()
}
}
Actors
actor BankAccount {
private var balance: Double = 0
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) -> Bool {
guard balance >= amount else {
return false
}
balance -= amount
return true
}
func getBalance() -> Double {
return balance
}
}
let account = BankAccount()
Task {
await account.deposit(amount: 100)
let balance = await account.getBalance()
print("Balance: \(balance)")
}
Tasks and Task Groups
Task.detached {
await performBackgroundWork()
}
func fetchAllUsers() async throws -> [User] {
try await withThrowingTaskGroup(of: User.self) { group in
for id in 1...10 {
group.addTask {
try await fetchUser(id: String(id))
}
}
var users: [User] = []
for try await user in group {
users.append(user)
}
return users
}
}
let task = Task {
for i in 1...100 {
if Task.isCancelled {
print("Task cancelled at \(i)")
return
}
await doWork(i)
}
}
Task {
try await Task.sleep(nanoseconds: )
task.cancel()
}
SwiftUI Fundamentals
View Basics
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
Text("Hello, World!")
.font(.largeTitle)
.foregroundColor(.blue)
Image(systemName: "star.fill")
.font(.system(size: 50))
Button("Tap Me") {
print("Button tapped")
}
}
.padding()
}
}
State Management
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
.font(.largeTitle)
Button("Increment") {
count += 1
}
}
}
}
class UserViewModel: ObservableObject {
@Published var username = ""
@Published var isLoggedIn = false
func login() {
isLoggedIn = true
}
}
struct LoginView: View {
@StateObject private var viewModel = UserViewModel()
var body: some View {
VStack {
TextField("Username", text: .username)
.textFieldStyle(.roundedBorder)
() {
viewModel.login()
}
viewModel.isLoggedIn {
()
}
}
.padding()
}
}
: {
(\.colorScheme) colorScheme
body: {
()
}
}
Lists and Navigation
struct Item: Identifiable {
let id = UUID()
let title: String
}
struct ItemListView: View {
let items = [
Item(title: "First"),
Item(title: "Second"),
Item(title: "Third")
]
var body: some View {
NavigationView {
List(items) { item in
NavigationLink(destination: DetailView(item: item)) {
Text(item.title)
}
}
.navigationTitle("Items")
}
}
}
struct DetailView: View {
let item: Item
var body: some View {
Text("Detail for \(item.title)")
.navigationTitle(item.title)
}
}
Custom Modifiers and ViewBuilder
struct CardModifier: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 5)
}
}
extension View {
func cardStyle() -> some View {
modifier(CardModifier())
}
}
@ViewBuilder
func conditionalView(showText: Bool) -> some View {
if showText {
Text("Visible")
} else {
Image(systemName: "eye.slash")
}
}
UIKit Integration
UIKit in SwiftUI (UIViewRepresentable)
import UIKit
import SwiftUI
struct TextView: UIViewRepresentable {
@Binding var text: String
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
func makeCoordinator() -> Coordinator {
Coordinator(text: $text)
}
class Coordinator: NSObject, UITextViewDelegate {
@Binding var text: String
init(text: Binding<String>) {
_text = text
}
func textViewDidChange(_ textView: UITextView) {
text = textView.text
}
}
}
SwiftUI in UIKit (UIHostingController)
import UIKit
import SwiftUI
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let swiftUIView = ContentView()
let hostingController = UIHostingController(rootView: swiftUIView)
addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.view.frame = view.bounds
hostingController.didMove(toParent: self)
}
}
Basic UIKit Patterns
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
}
protocol DataSourceDelegate: AnyObject {
func didReceiveData(_ data: String)
}
class DataSource {
weak var delegate: DataSourceDelegate?
func fetchData() {
delegate?.didReceiveData("Data")
}
}
Project Structure
Swift Package Structure
MyPackage/
├── Package.swift
├── Sources/
│ └── MyPackage/
│ ├── MyPackage.swift
│ └── Models/
│ └── User.swift
├── Tests/
│ └── MyPackageTests/
│ └── MyPackageTests.swift
└── README.md
iOS App Structure
MyApp/
├── MyApp/
│ ├── App/
│ │ ├── MyAppApp.swift
│ │ └── ContentView.swift
│ ├── Models/
│ │ └── User.swift
│ ├── Views/
│ │ ├── HomeView.swift
│ │ └── DetailView.swift
│ ├── ViewModels/
│ │ └── UserViewModel.swift
│ ├── Services/
│ │ └── NetworkService.swift
│ ├── Resources/
│ │ └── Assets.xcassets
│ └── Supporting Files/
│ └── Info.plist
└── MyAppTests/
└── MyAppTests.swift
Package.swift Example
import PackageDescription
let package = Package(
name: "MyPackage",
platforms: [
.iOS(.v16),
.macOS(.v13)
],
products: [
.library(
name: "MyPackage",
targets: ["MyPackage"]
),
],
dependencies: [
.package(url: "https://github.com/example/package.git", from: "1.0.0"),
],
targets: [
.target(
name: "MyPackage",
dependencies: []
),
.testTarget(
name: "MyPackageTests",
dependencies: ["MyPackage"]
),
]
)
Common Idioms
Result Builders
@resultBuilder
struct StringBuilder {
static func buildBlock(_ components: String...) -> String {
components.joined(separator: " ")
}
}
@StringBuilder
func makeGreeting() -> String {
"Hello"
"World"
"from"
"Swift"
}
print(makeGreeting())
Codable for JSON
struct User: Codable {
let id: Int
let name: String
let email: String
enum CodingKeys: String, CodingKey {
case id
case name
case email = "email_address"
}
}
let json = """
{
"id": 1,
"name": "John",
"email_address": "john@example.com"
}
""".data(using: .utf8)!
let user = try JSONDecoder().decode(User.self, from: json)
let data = try JSONEncoder().encode(user)
Error Handling
enum NetworkError: Error {
case invalidURL
case noData
case decodingFailed
}
func fetchData(from urlString: String) throws -> Data {
guard let url = URL(string: urlString) else {
throw NetworkError.invalidURL
}
guard let data = try? Data(contentsOf: url) else {
throw NetworkError.noData
}
return data
}
do {
let data = try fetchData(from: "https://api.example.com")
print("Fetched \(data.count) bytes")
} catch NetworkError.invalidURL {
print("Invalid URL")
} catch {
print("Error: \(error)")
}
Troubleshooting
Optional Unwrapping Crashes
Problem: Fatal error: Unexpectedly found nil while unwrapping an Optional value
let name = user.name!
if let name = user.name {
print(name)
}
guard let name = user.name else {
return
}
Retain Cycles in Closures
Problem: Memory leaks from strong reference cycles
class ViewController {
var completion: (() -> Void)?
func setup() {
completion = {
self.doSomething()
}
}
}
class ViewController {
var completion: (() -> Void)?
func setup() {
completion = { [weak self] in
self?.doSomething()
}
}
}
SwiftUI View Not Updating
Problem: View doesn't update when data changes
class ViewModel {
var count = 0
}
class ViewModel: ObservableObject {
@Published var count = 0
}
struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
Text("Count: \(viewModel.count)")
}
}
Actor Reentrancy Issues
Problem: Unexpected state changes in async actor methods
actor Counter {
private var value = 0
func increment() async {
await Task.sleep(1_000_000_000)
value += 1
}
func safeIncrement() async -> Int {
let currentValue = value
await Task.sleep(1_000_000_000)
value = currentValue + 1
return value
}
}
Testing
XCTest Basics
import XCTest
@testable import MyApp
final class UserTests: XCTestCase {
var sut: User!
override func setUp() {
super.setUp()
sut = User(name: "Alice", age: 30)
}
override func tearDown() {
sut = nil
super.tearDown()
}
func testUserInitialization() {
XCTAssertEqual(sut.name, "Alice")
XCTAssertEqual(sut.age, 30)
}
func testUserValidation() {
let invalidUser = User(name: "", age: -1)
let isValid = invalidUser.validate()
XCTAssertFalse(isValid)
}
}
Swift Testing (@Test Macro)
import Testing
@testable import MyApp
struct UserTests {
@Test func userInitialization() {
let user = User(name: "Alice", age: 30)
#expect(user.name == "Alice")
#expect(user.age == 30)
}
@Test("User validation rejects empty names")
func userValidation() {
let user = User(name: "", age: 30)
#expect(!user.validate())
}
@Test("Age validation", arguments: [
(18, true),
(17, false),
(65, true),
(0, false),
(-1, false)
])
func ageValidation(age: Int, expected: Bool) {
let user = User(name: , age: age)
#expect(user.isValidAge() expected)
}
(.tags(.critical))
() {
result performCriticalOperation()
#expect(result )
}
}
{
critical:
integration:
performance:
}
XCTest Assertions
XCTAssertTrue(condition)
XCTAssertFalse(condition)
XCTAssertNil(value)
XCTAssertNotNil(value)
XCTAssertEqual(actual, expected)
XCTAssertNotEqual(actual, unexpected)
XCTAssertIdentical(object1, object2)
XCTAssertGreaterThan(5, 3)
XCTAssertLessThan(3, 5)
XCTAssertGreaterThanOrEqual(5, 5)
XCTAssertLessThanOrEqual(3, 5)
XCTAssertEqual(3.14, 3.14159, accuracy: 0.01)
XCTAssertThrowsError(try riskyOperation()) { error in
XCTAssertEqual(error as? MyError, MyError.notFound)
}
XCTAssertNoThrow(try safeOperation())
XCTFail("Test failed: \(reason)")
Async Testing
func testAsyncFetch() async throws {
let user = try await networkService.fetchUser(id: "123")
XCTAssertEqual(user.id, "123")
XCTAssertNotNil(user.name)
}
func testCompletionHandler() {
let expectation = expectation(description: "Data fetched")
networkService.fetchUser(id: "123") { result in
switch result {
case .success(let user):
XCTAssertEqual(user.id, "123")
expectation.fulfill()
case .failure(let error):
XCTFail("Failed with error: \(error)")
}
}
waitForExpectations(timeout: 5.0)
}
func testMultipleAsync() async throws {
async let user = fetchUser(id: "123")
async let posts = fetchPosts(userId: "123")
let (fetchedUser, fetchedPosts) (user, posts)
(fetchedUser.id, )
(fetchedPosts.isEmpty)
}
() {
user networkService.fetchUser(id: )
#expect(user.id )
}
() {
counter ()
counter.increment()
counter.increment()
value counter.value
(value, )
}
Mocking Protocols
protocol NetworkService {
func fetchUser(id: String) async throws -> User
func updateUser(_ user: User) async throws
}
class MockNetworkService: NetworkService {
var fetchUserCalled = false
var fetchUserCallCount = 0
var userToReturn: User?
var errorToThrow: Error?
func fetchUser(id: String) async throws -> User {
fetchUserCalled = true
fetchUserCallCount += 1
if let error = errorToThrow {
throw error
}
return userToReturn ?? User(id: id, name: "Mock User", age: 30)
}
var updateUserCalled
updatedUser: ?
( : ) {
updateUserCalled
updatedUser user
error errorToThrow {
error
}
}
}
() {
mockService ()
mockService.userToReturn (id: , name: , age: )
viewModel (service: mockService)
viewModel.loadUser(id: )
(mockService.fetchUserCalled)
(mockService.fetchUserCallCount, )
(viewModel.user.name, )
}
Spy Pattern
class SpyNetworkService: NetworkService {
var calls: [(method: String, arguments: Any)] = []
func fetchUser(id: String) async throws -> User {
calls.append((method: "fetchUser", arguments: id))
return User(id: id, name: "Spy User", age: 30)
}
func updateUser(_ user: User) async throws {
calls.append((method: "updateUser", arguments: user))
}
}
func testCallSequence() async throws {
let spy = SpyNetworkService()
let viewModel = UserViewModel(service: spy)
await viewModel.loadUser(id: "123")
await viewModel.updateUserName("Bob")
XCTAssertEqual(spy.calls.count, 2)
XCTAssertEqual(spy.calls[0].method, "fetchUser")
XCTAssertEqual(spy.calls[1].method, )
}
Stub Pattern
class StubNetworkService: NetworkService {
var stubbedUsers: [String: User] = [:]
func fetchUser(id: String) async throws -> User {
guard let user = stubbedUsers[id] else {
throw NetworkError.notFound
}
return user
}
func updateUser(_ user: User) async throws {
}
}
func testWithPresetData() async throws {
let stub = StubNetworkService()
stub.stubbedUsers = [
"123": User(id: "123", name: "Alice", age: 30),
"456": User(id: "456", name: "Bob", age: 25)
]
let user = try stub.fetchUser(id: )
(user.name, )
}
Testing SwiftUI Views
import ViewInspector
struct ContentViewTests: XCTestCase {
func testButtonTap() throws {
var viewModel = ViewModel()
let view = ContentView(viewModel: viewModel)
let button = try view.inspect().find(button: "Increment")
try button.tap()
XCTAssertEqual(viewModel.count, 1)
}
func testTextDisplayed() throws {
let view = ContentView(title: "Hello")
let text = try view.inspect().find(text: "Hello")
XCTAssertNotNil(text)
}
}
func testViewSnapshot() {
let view = ContentView()
assertSnapshot(matching: view, as: .image)
}
UI Testing Basics
import XCTest
final class AppUITests: XCTestCase {
var app: XCUIApplication!
override func setUp() {
super.setUp()
continueAfterFailure = false
app = XCUIApplication()
app.launch()
}
func testLoginFlow() {
let usernameField = app.textFields["Username"]
let passwordField = app.secureTextFields["Password"]
let loginButton = app.buttons["Login"]
usernameField.tap()
usernameField.typeText("alice@example.com")
passwordField.tap()
passwordField.typeText("password123")
loginButton.tap()
let welcomeText = app.staticTexts["Welcome, Alice!"]
XCTAssertTrue(welcomeText.waitForExistence(timeout: 5))
}
func testSwipeToDelete() {
let firstCell = app.cells.firstMatch
XCTAssertTrue(firstCell.waitForExistence(timeout: 2))
firstCell.swipeLeft()
let deleteButton = firstCell.buttons[]
deleteButton.tap()
(firstCell.exists)
}
}
Performance Testing
func testPerformance() {
measure {
_ = heavyComputation()
}
}
func testPerformanceMetrics() {
let options = XCTMeasureOptions()
options.iterationCount = 10
measure(metrics: [XCTClockMetric(), XCTMemoryMetric()], options: options) {
processLargeDataset()
}
}
@Test(.timeLimit(.minutes(1)))
func performanceSensitiveOperation() {
let result = expensiveComputation()
#expect(result != nil)
}
Test Organization
class UserValidationTests: XCTestCase {
func testEmailValidation() { }
func testPasswordValidation() { }
func testAgeValidation() { }
}
class UserPersistenceTests: XCTestCase {
func testSaveUser() { }
func testLoadUser() { }
func testDeleteUser() { }
}
@Suite("User Management")
struct UserManagementTests {
@Suite("Validation")
struct ValidationTests {
@Test func emailValidation() { }
@Test func passwordValidation() { }
}
@Suite("Persistence")
struct PersistenceTests {
@Test func saveUser() { }
@Test func loadUser() { }
}
}
Test Helpers
func assertUserValid(_ user: User, file: StaticString = #file, line: UInt = #line) {
XCTAssertFalse(user.name.isEmpty, "User name is empty", file: file, line: line)
XCTAssertGreaterThan(user.age, 0, "User age is invalid", file: file, line: line)
XCTAssertNotNil(user.email, "User email is nil", file: file, line: line)
}
extension User {
static func fixture(
name: String = "Test User",
age: Int = 30,
email: String = "test@example.com"
) -> User {
User(name: name, age: age, email: email)
}
}
func testUserFeature() {
let user = User.fixture(name: "Alice")
assertUserValid(user)
}
Cross-Cutting Patterns
For cross-language comparison and translation patterns, see:
patterns-concurrency-dev - async/await, actors, task groups
patterns-serialization-dev - Codable, JSON, property lists
patterns-metaprogramming-dev - Reflection, type introspection
References