챌린지 1-4 · 8 Topics

✏️ 막히는 Swift 문법

컴파일 에러가 나는데 왜 나는지 모른다

let vs var Optional · nil if let / guard let ?? · ! Array · Dictionary · Set map · filter · forEach Closure · [weak self] Fix-it throws · do-catch
시작 전에 던져볼 질문
01
변수에 값이 "없을 수도 있다"는 걸 Swift에서 어떻게 표현하고, 안전하게 꺼내 쓰는가?
검색 → Swift Optional if let guard let
02
for문 없이 배열의 각 요소를 변환하거나 걸러내려면 어떻게 하는가?
검색 → Swift map filter reduce
03
SwiftUI 코드에서 { } 안에 $0 같은 기호가 보이는데, 이게 뭔가?
검색 → Swift closure trailing closure shorthand
Topic 01

let vs var · 타입 추론

상수와 변수, 타입을 명시하는 시점
Swift
let name = "개발자리"     // String 추론
let age: Int = 10         // 명시적 타입
var score = 0             // 변경 가능

score += 10
// name = "다른이름"      // ❌ 컴파일 에러!

print("이름: \(name)")
print("점수: \(score)")
생각해보기
  • let으로 선언한 변수를 나중에 바꾸려고 하면 어떤 에러가 나는가?
  • let x = 42에서 Swift가 타입을 Int로 추론하는 원리는 무엇인가?
  • 타입을 명시적으로 적어야 하는 경우는 구체적으로 언제인가?
let — 상수 (변경 불가)
한 번 할당하면 변경 불가. 컴파일러가 불필요한 변경을 막아줌. 기본적으로 let을 쓰고 필요할 때 var로 바꿈
타입 추론 vs 명시
let x = 42 (Int 추론) vs let x: Int = 42. 명시가 필요한 경우 — 타입이 모호하거나 다른 타입을 원할 때
Topic 02

Optional 완전 정복

nil · if let · guard let · ?? · !
Swift
var nickname: String? = nil

// if let 언래핑
if let name = nickname {
    print("닉네임: \(name)")
} else {
    print("닉네임 없음")
}

// ?? nil 병합
let display = nickname ?? "익명"
print("표시: \(display)")

// 옵셔널 체이닝
let count = nickname?.count
print("글자수: \(count)")
생각해보기
  • StringString?은 완전히 다른 타입인가? 둘의 관계는?
  • if letguard let은 언래핑한 값을 사용할 수 있는 범위(scope)가 어떻게 다른가?
  • ! 강제 언래핑이 crash를 일으키는 정확한 조건은 무엇인가?
  • user?.profile?.name에서 중간에 nil이 하나라도 있으면 결과는?
Optional이란
String? = "값이 있거나 없거나". Optional은 enum: case some(Wrapped), case none
if let name = optionalName
값이 있으면 블록 실행. 없으면 else. 블록 안에서만 언래핑된 값 사용 가능
guard let name = optionalName else { return }
없으면 early return. 이후 코드에서 언래핑된 값을 계속 사용. 들여쓰기 줄이는 효과
?. 옵셔널 체이닝
user?.profile?.name — 중간에 nil이면 전체가 nil. crash 없이 안전하게
?? nil 병합 · ! 강제 언래핑
name ?? "익명" — nil일 때 기본값. ! — nil이면 crash. ! 써도 되는 경우: nil이 절대 아님을 100% 확신할 때만
Topic 03

Array · Dictionary · Set

언제 무엇을 쓰는가
Swift
// Array
var fruits = ["사과", "바나나", "포도"]
fruits.append("딸기")
print(fruits[0])
print(fruits.count)

// Dictionary
let scores: [String: Int] = [
    "수학": 90,
    "영어": 85,
]
print(scores["수학"] ?? 0)

// Set
let tags: Set = ["Swift", "iOS", "Swift"]
print(tags.count)  // 중복 제거
생각해보기
  • array[5]에서 배열 크기가 3이면 어떤 에러가 발생하는가?
  • Dictionary에서 없는 키로 접근하면 crash가 나는가, nil이 나는가?
  • Setcontains()Arraycontains()보다 빠른 이유는 무엇인가?
Array — 순서 있는 목록
인덱스로 접근. indices.contains(i)로 범위 확인 후 접근. array.indices로 안전한 반복
Dictionary — 키-값 쌍
dict["key"] 결과는 항상 Optional. 없는 키일 수 있으니까. dict["key", default: 0]으로 기본값
Set — 중복 없는 집합
순서 없음. contains()가 O(1). 포함 여부 확인이 Array보다 훨씬 빠름. Hashable 필요
Topic 04

고차 함수 — map · filter · forEach

반복 대신 선언적으로
Swift
let numbers = [1, 2, 3, 4, 5]

// map — 변환
let doubled = numbers.map { $0 * 2 }
print(doubled)

// filter — 걸러내기
let evens = numbers.filter { $0 % 2 == 0 }
print(evens)

// compactMap — nil 제거
let strings = ["1", "a", "3"]
let nums = strings.compactMap { Int($0) }
print(nums)

// reduce — 합산
let sum = numbers.reduce(0, +)
print(sum)
생각해보기
  • mapforEach의 차이는 무엇인가? 둘 다 각 요소를 순회하는데 왜 구분하는가?
  • compactMapmap과 어떻게 다른가? 언제 써야 하는가?
  • reduce(0, +)에서 0+는 각각 무엇인가?
map — 변환
names.map { $0.uppercased() } — 각 요소를 변환해 새 배열 반환
filter — 걸러내기
items.filter { $0.isCompleted } — 조건에 맞는 요소만 새 배열로
compactMap — Optional 제거
map + nil 제거. strings.compactMap { Int($0) } — 변환 실패한 nil을 걸러냄
reduce — 합산
items.reduce(0) { $0 + $1.price } — 누적값 계산
Topic 05

클로저 문법 단계별 줄이기

기본 → 후행 → 단축 인자명
Swift
let names = ["Charlie", "Alice", "Bob"]

// 1. 기본 형태
let a = names.sorted(by: {
    (a: String, b: String) -> Bool in
    return a < b
})

// 2. 타입 추론
let b = names.sorted(by: { a, b in a < b })

// 3. 단축 인자명
let c = names.sorted(by: { $0 < $1 })

// 4. 후행 클로저
let d = names.sorted { $0 < $1 }

print(d)
생각해보기
  • { (a: String, b: String) -> Bool in return a < b }를 가장 짧게 줄이면 어떻게 되는가?
  • $0, $1은 무엇을 의미하는가? $2까지 있다면 파라미터가 몇 개인가?
  • 후행 클로저(trailing closure)란 정확히 무엇인가? Button { } 문법이 가능한 이유는?
기본 문법
{ (name: String) -> String in return name.uppercased() }
타입 추론으로 줄이기
{ name in name.uppercased() } — 타입과 return 생략
단축 인자명 $0 · $1
{ $0.uppercased() } — 파라미터 이름까지 생략. 순서대로 $0, $1, ...
후행 클로저
마지막 파라미터가 클로저면 괄호 밖으로. Button { } / .onAppear { } 가 이 형태
Topic 06

[weak self] 캡처 리스트

언제 필요하고 언제 없어도 되는가
Swift
class DataLoader {
    var data: String?

    func load() {
        // ✅ [weak self] 필요
        URLSession.shared.dataTask(
            with: url
        ) { [weak self] data, _, _ in
            guard let self else { return }
            self.data = String(
                data: data!, encoding: .utf8
            )
        }.resume()
    }
}

// ❌ SwiftUI View에서는 불필요
struct MyView: View {
    var body: some View {
        Button("탭") {
            // struct이므로 순환참조 없음
            print("no [weak self] needed")
        }
    }
}
생각해보기
  • 순환 참조(retain cycle)란 무엇이며, 왜 메모리 누수를 일으키는가?
  • [weak self]를 붙이면 self가 어떤 상태가 될 수 있는가?
  • SwiftUI View에서 [weak self]가 필요 없는 이유는 무엇인가?
클로저는 값을 캡처한다
클로저가 self를 참조하면 strong 캡처. self도 클로저를 참조하면 순환 참조 발생
[weak self] 필요한 경우
escape 클로저(비동기 콜백, 타이머 등)에서 self를 참조할 때. [weak self] guard let self else { return }
SwiftUI에서는 불필요
SwiftUI View는 struct — class가 아니므로 순환 참조 없음. map { } filter { } 등에서도 불필요
Topic 07

자주 만나는 에러 메시지 4종

원인과 해결 패턴
Swift
// ❌ 타입 불일치
let num: Int = 42
// let text: String = num

// ✅ 해결
let text: String = String(num)

// ❌ 옵셔널 미언래핑
let name: String? = "Kim"
// print("Hello " + name)

// ✅ 해결
if let name {
    print("Hello " + name)
}
생각해보기
  • "Cannot convert value of type" 에러를 만났을 때 가장 먼저 확인할 것은?
  • Xcode의 Fix-it 제안을 무조건 따르면 안 되는 이유는 무엇인가?
  • String?String이 필요한 곳에 넣으면 어떤 에러가 나는가? 해결 방법 3가지는?
Cannot convert value of type 'X' to expected argument type 'Y'
타입 불일치. Int를 String에 넣으려 할 때. String(number) 로 명시적 변환
Cannot convert value of type 'Int' to
expected argument type 'String'
Value of optional type 'X?' must be unwrapped
Optional을 그대로 사용하려 할 때. if let / guard let / ?? 로 언래핑
Value of optional type 'String?' must be
unwrapped to a value of type 'String'
Referencing operator function '==' requires ... 'Equatable'
커스텀 타입을 == 비교하려 할 때. Equatable 채택 필요
Fix-it 신뢰도
믿어도 되는 경우: 프로토콜 메서드 추가, import 추가. 무시해야 할 경우: 복잡한 타입 문제에서 엉뚱한 제안
Topic 08

Error Handling 기초

throws · try · do-catch · Result
Swift
enum FileError: Error {
    case notFound
    case permissionDenied
}

// 에러를 던질 수 있는 함수
func readFile(name: String) throws -> String {
    guard name != "" else {
        throw FileError.notFound
    }
    return "파일 내용"
}

// do-catch로 에러 처리
do {
    let content = try readFile(name: "")
    print(content)
} catch FileError.notFound {
    print("❌ 파일을 찾을 수 없습니다")
} catch {
    print("❌ 알 수 없는 에러: \(error)")
}

// try? — 에러 시 nil 반환
let content = try? readFile(name: "")

// Result 타입
func safeRead(name: String) -> Result<String, FileError> {
    do {
        return .success(try readFile(name: name))
    } catch let e as FileError {
        return .failure(e)
    } catch {
        return .failure(.notFound)
    }
}
생각해보기
  • throws가 붙은 함수를 try 없이 호출하면 어떻게 되는가?
  • try?try!의 차이는 무엇인가? try!는 언제 써도 되는가?
  • Result<Success, Failure>를 쓰는 것과 throws를 쓰는 것은 어떻게 다른가?
  • 네트워크 응답 파싱 실패를 try?로 처리하면 무엇을 잃는가?
throws / try — 기본 메커니즘
throws가 붙은 함수는 에러를 던질 수 있음. 호출 시 반드시 try를 붙여야 함. 컴파일 타임에 강제
do-catch — 에러 처리 블록
do 블록에서 에러 발생 시 catch로 이동. 구체적 에러 타입별로 처리 분기 가능
try? — Optional 반환
에러 시 nil 반환. 실패 원인을 알 필요 없을 때만 사용. JSON 파싱 실패 원인을 숨기면 디버깅 불가
Result<Success, Failure>
에러를 값으로 다루기. .success(data) / .failure(error). 비동기 콜백이나 반환값으로 에러 전달할 때
Error 프로토콜 — 커스텀 에러
enum이 Error를 채택하면 에러 타입으로 사용 가능. case별로 상황을 명확하게 표현
Output
Output
이름: 개발자리
점수: 10
Output
닉네임 없음
표시: 익명
글자수: nil
Output
사과
4
90
2
Output
[2, 4, 6, 8, 10]
[2, 4]
[1, 3]
15
Output
["Alice", "Bob", "Charlie"]
// 모두 같은 결과!
Output
class → [weak self] 필요
struct → 불필요 (값 타입)
Output
Cannot convert value of type 'Int' to
expected argument type 'String'
Value of optional type 'String?' must be
unwrapped to a value of type 'String'
Referencing operator function '=='
requires that 'MyType' conform to 'Equatable'
Output
❌ 파일을 찾을 수 없습니다

// try? → nil
content = nil

// Result
.failure(.notFound)
Homework

챌린지 1-4를 마쳤다면

필수
Optional 연습장
String?, Int?, [String]? 변수를 만들고 if let, guard let, ??, 옵셔널 체이닝으로 각각 언래핑. Playground에서 실행 결과 확인
필수
고차 함수로 데이터 가공
학생 struct 배열에서 map으로 이름만 추출, filter로 80점 이상만 걸러내기, reduce로 평균 점수 계산. for문 없이 해결
도전
클로저 단계별 줄이기 실습
sorted, filter, map 각각에 대해 기본 형태 → 타입 추론 → 단축 인자명 → 후행 클로저 4단계로 코드를 줄여보기. 각 단계에 주석 달기
완료 체크리스트
Optional이 enum이라는 것을 안다
if let / guard let / ?? 를 상황에 맞게 쓸 수 있다
map, filter, reduce를 for문 대신 쓸 수 있다
[weak self]가 필요한 경우와 불필요한 경우를 구분할 수 있다
throws / do-catch / try? 의 차이를 설명할 수 있다
Solution — 답안 보기
1. Optional 연습장
Swift
var name: String? = "김개발"
var age: Int? = nil
var tags: [String]? = ["Swift", "iOS"]

// if let
if let name {
    print("이름: \(name)")   // 이름: 김개발
}

// guard let
func printAge() {
    guard let age else {
        print("나이 정보 없음")  // 출력됨
        return
    }
    print("나이: \(age)")
}
printAge()

// ?? nil 병합
let displayAge = age ?? 0
print("나이: \(displayAge)")  // 나이: 0

// 옵셔널 체이닝
let count = tags?.count
print("태그 수: \(count)")  // Optional(2)

let first = tags?.first?.uppercased()
print("첫 태그: \(first)")  // Optional("SWIFT")
2. 고차 함수로 데이터 가공
Swift
struct Student {
    let name: String
    let score: Int
}

let students = [
    Student(name: "Alice", score: 92),
    Student(name: "Bob", score: 75),
    Student(name: "Charlie", score: 88),
    Student(name: "Diana", score: 64),
    Student(name: "Eve", score: 95),
]

// map — 이름만 추출
let names = students.map { $0.name }
print(names)
// ["Alice", "Bob", "Charlie", "Diana", "Eve"]

// filter — 80점 이상
let topStudents = students.filter { $0.score >= 80 }
print(topStudents.map { $0.name })
// ["Alice", "Charlie", "Eve"]

// reduce — 평균 점수
let total = students.reduce(0) { $0 + $1.score }
let average = Double(total) / Double(students.count)
print("평균: \(average)")  // 평균: 82.8
3. 클로저 단계별 줄이기
Swift
let numbers = [5, 2, 8, 1, 9, 3]

// === sorted ===
// 1) 기본
numbers.sorted(by: { (a: Int, b: Int) -> Bool in
    return a < b
})
// 2) 타입 추론
numbers.sorted(by: { a, b in a < b })
// 3) 단축 인자명
numbers.sorted(by: { $0 < $1 })
// 4) 후행 클로저
numbers.sorted { $0 < $1 }

// === filter ===
// 1) 기본
numbers.filter({ (n: Int) -> Bool in
    return n > 3
})
// 2) 타입 추론
numbers.filter({ n in n > 3 })
// 3) 단축 인자명
numbers.filter({ $0 > 3 })
// 4) 후행 클로저
numbers.filter { $0 > 3 }

// === map ===
// 1) 기본
numbers.map({ (n: Int) -> String in
    return "\(n)점"
})
// 2) 타입 추론
numbers.map({ n in "\(n)점" })
// 3) 단축 인자명
numbers.map({ "\($0)점" })
// 4) 후행 클로저
numbers.map { "\($0)점" }
← 이전 챌린지 1-3 여러 화면과 공유 상태
코멘트
후원하기
콘텐츠가 도움이 됐다면
커피 한 잔의 후원을 부탁드려요 :)
카카오페이 QR
이현호
카카오페이
카카오톡 QR
카카오톡 오픈채팅 💬
학습 질문 · 코드 리뷰 · 링크로 참여