[내일배움캠프 2주차 (01/12)]

yeseul jang·2026년 1월 12일

내일배움캠프

목록 보기
1/32

자세히 공부한 주제에만 맞게 TIL을 쓰려고 했는데 배우는 입장에서 내가 그 중요도를 판단 할 수 있다는 거 자체가 어불성설 같다.
일단 조금이나 배우거나 공부한 걸 조금 내용이 부족하더라도 다 정리해 보고자 한다. 이렇게 하는게 나중에 더 자세히 공부할 때 도움이 될 것 같다.

🔎 where 키워드

where clause is used to specify additional constraints or conditions
(Swift Language Guide – Generics, Control Flow)

where는 코드가 동작하기 위해서는 이런 조건을 만족해야 한다고 추가 조건을 명시하는 코드이다.

📌 if vs where

if: 실행흐름을 제어
where: 문법적 제약 / 조건필터 역할을 한다. 이 문법이 성립할 수 있는 조건을 명시한다.

let numbers = [1, 2, 3, 4, 5]

//where사용
for number in numbers where number % 2 == 0 {
    print(number)
}

//if 사용
for number in numbers {
    if number % 2 == 0 {
        print(number)
    }
}

기능은 같지만 짝수만 순회한다라는 필터링 의도가 명확해진다.

📌 어디에 쓰일까?

✏️ switch + where

스위치 문과 조건 분기를 나눌때

let score = 85

switch score {
case let x where x >= 90:
    print("A")
case let x where x >= 80:
    print("B")
default:
    print("C")
}

✏️catch + where

같은 에러지만 이런식으로 조건에 따라 다르게 처리 할 수 있다.

enum NetworkError: Error {
    case serverError(code: Int)
}

do {
    throw NetworkError.serverError(code: 500)
} catch NetworkError.serverError(let code) where code >= 500 {
    print("서버 오류")
}

✏️ Generic+ where

where로 조건을 명확하게 할 수 있다.
Comparable을 만족할때만 sortItems함수를 쓸 수 있다.

struct SortableBox<T> {
    var items: [T]

    mutating func sortItems() where T: Comparable {
           items.sort()
    }
}

제네릭 타입간의 관계를 나타내는데도 용이하다.

func compare<T, U>(a: T, b: U)
where T: Equatable, U: Equatable, T == U {
    print(a == b)
}

✏️ extension + where

extension를 활용해 의도한데로 기능을 부여할 수 있다.
아래의 extensionInt배열 일때만 존재할 수 있다.

extension Array where Element == Int {
    func sum() -> Int {
        reduce(0, +)
    }
}
[1, 2, 3].sum()     // 가능
["a", "b"].sum()   // ❌ 컴파일 에러

🔎 고차함수

📌 고차함수란?

고차함수란 “함수를 인자로 받거나, 함수를 반환하는 함수”

📌 고차함수 종류

✏️ map - 값을 변환하는 고차함수

  • 배열의 각 요소를 다른 값으로 변환해서 새로운 배열을 만들어주는 함수
  • 기존 배열은 그대로 두고 새 배열을 만들어서 반환
  • nil을 제거하지 않음
  • 사용 예시: 타입 변환, UI표시용 데이터 가공, 모델 -> 뷰모델 변환
let numbers = [1, 2, 3, 4, 5]

let result = numbers.map { number in
    return number * 2
}

func map<T>(_ transform: (Element) -> T) -> [T]

✏️ compactMap - nil 제거 후 압축 반환

  • 변환하면서 nil을 제거하는 map
  • 사용 예시: Optional 제거, 실패 가능한 변환, JSON 파싱 후 데이터 정리
let values = [1, nil, 3]

let result = values.compactMap { $0 }
// [1, 3]

let strings = ["1", "2", "a", "3"]

let numbers = strings.compactMap { Int($0) }
// [1, 2, 3]  변환시 nil이 된 것들이 제거됨 

✏️ flatMap - 중첩된 컬렉션을 한 단계 펼쳐주는 함수

  • 배열 안의 배열을 하나의 배열로 만들어주는 함수
  • map 구조 유지하는데 비해 flatMap는 구조 제거 한다.
let nested = [[1, 2], [3, 4]]

let flat = nested.flatMap { $0 }
// [1, 2, 3, 4]

✏️ forEach- 각 요소에 대해 작업만 수행하는 함수

  • 결과 배열 없음
  • break, continue 없음 (제어흐름이 필요하다면 for-in 선택)
let numbers = [1, 2, 3]

numbers.forEach {
    print($0)
}

✏️ filter - 조건으로 걸러내는 고차함수

  • 조건을 만족하는 요소만 남겨서 새로운 배열을 만드는 함수
  • filter안의 클로저는 bool값의 결과에 따라서 true일 경우 남기고 false는 버린다.
let numbers = [1, 2, 3, 4, 5]

let evenNumbers = numbers.filter { $0 % 2 == 0 }
//[2, 4]

func filter(_ isIncluded: (Self.Element) throws -> Bool) rethrows -> [Self.Element]

✏️ reduce- 하나의 값으로 합치는 고차함수

  • 여러 값을 하나의 결과값으로 합치는 함수
  • 정의한 클로저를 사용하여 기존 요소를 결합한 결과를 반환하는 함수
let numbers = [1, 2, 3, 4]

let sum = numbers.reduce(0) { result, number in
    result + number
}
// 10

func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result
  • initialResult: 초기값으로 사용할 값을 넣으면 클로저가 처음 실행될 때 nextPartialResult에 전달됨
  • nextPartialResult: 컨테이너의 요소를 새로운 누적값으로 결합하는 클로저

🔎 캡처 리스트

  • 클로저는 힙에 저장되는 참조 타입 객체다 상태를 기억할 수 있고 메모리 관리가 중요하다
  • 캡처 리스트는 클로저가 외부 값을 어떻게 잡을지(참조방식)를 명시하는 문법
  • 클로저는 자동으로 외부 변수를 캡처하고 캡처 리스트는 그걸 명시적으로 제어함
{ [캡처 리스트] (매개변수) -> 반환값 in
    실행 코드
}

{ [weak self] in
    self?.doSomething()
}

✏️ 캡처 리스트는 변수도 캡처함

var count = 0

let closure = { [count] in
    print(count)
}
  • 값 타입 일 경우 count 값이 복사됨
  • 하지만 복사된 값이라 이후에는 count값이 변경 되어도 영향이 없다
class Box { 
    var value = 0 
}

let box = Box()

let closure = { [box] in
    print(box.value)
}
  • 참조타입은 참조자체를 캡처
  • 여기서는 box 참조를 캡처함, 값이 반영됨

✏️ 캡처 리스트의 참조 방식

  • 값을 strong, weak, unowned 중에 어떤 방식으로 잡을 것인가 인지를 제어

1️⃣ weak

  • escaping 클로저일 때(나중에 실행 될 수 있을때) self를 약한 참조로 캡처
  • self가 사라지면 -> nil
  • 순환 참조가 되지 않음
{ [weak self] in
    self?.doSomething()
}

api.request { [weak self] result in
    guard let self else { return }
    self.updateUI(result)
}
  • 주로 weak self + guard let self 함께 사용

1️⃣ unowned

{ [unowned self] in
    self.doSomething()
}
  • 약한 참조
  • nil 체크 없음
  • self가 이미 해제된 경우라면 크래시
  • 실행시점이 명확하고(ex 즉시실행) self 살아있음이 보장되며 성능상 유리할 때 권장

🔎 오류 처리

enum DeliveryError: Error {
    case invalidAddress
    case notStarted
    case systemError(reason: String)
}
  • 이런식으로 enum Error 프로토콜을 채택하면 swift 내부에서 catch 문 사용시 이렇게 만들어준다.
catch let error {
    print(error)
}
  • 위의 코드는 어떤 종류의 에러든 일단 Error로 받는 범용 핸들러

  • throw → Error 프로토콜 타입으로 포장 → catch로 전달

  • throw DeliveryError.invalidAddress를 던지면 실제로는 Error 타입으로 업캐스팅되어서 catch로 전달된다.

  • 이게 가능한 이유는 swift가 자동으로 문자열로 만들어주는 CustomStringConvertible 구현을 제공하기 때문임

  • print(error)는 사실상 print(String(describing: error)) 호출하는것과 같다. 결과적으로는 "DeliveryError.systemError(reason: "DB down")" 이런 식으로 print되는 걸 볼 수 있음.

  • 에러 별로 나누고 싶다면 이런식으로 쓸 수 있다.

// 상수 활용
catch let error as DeliveryError {
    print("배송 에러:", error)
}
catch {
    print("기타 에러:", error)
}
//enum값 활용
catch DeliveryError.invalidAddress {
    print("주소 잘못됨")
}
catch DeliveryError.systemError(let reason) {
    print("시스템 에러:", reason)
}
profile
iOS 개발

0개의 댓글