본문 바로가기
iOS 프로그래밍 실무

iOS 프로그래밍 실무 6주차

by 노아입니다 2025. 4. 10.

🌿 옵셔널 체이닝 (Optional Chaining)

✅ 옵셔널이 뭐예요?

Swift에서 옵셔널(Optional)은 값이 "있을 수도 있고 없을 수도 있는" 상태를 표현하는 타입이에요.

var name: String? = "Alice" // 값이 있을 수도 있고
name = nil // 없을 수도 있어요!

옵셔널이 없다면 Swift는 nil 값을 허용하지 않기 때문에 반드시 초기화하거나 강제로 언래핑해야 해요. 그래서 옵셔널은 매우 중요하고 자주 사용돼요.


🔗 옵셔널 체이닝이란?

옵셔널 체이닝은 옵셔널 값에 속성, 메서드, 서브스크립트 등을 연결할 때 사용하는 문법이에요.

옵셔널이 nil이면 아무 작업도 하지 않고 nil을 반환해요. 앱이 크래시 나지 않도록 보호해줘요.

🧪 기본 문법

let result = optionalValue?.property?.method()

📘 예제: 이름이 있는 강아지 🐶

class Dog {
    var name: String = "코코"
}

class Person {
    var dog: Dog?
}

let person = Person()

// dog는 아직 nil이기 때문에 name에 접근 불가
let dogName = person.dog?.name
print(dogName) // nil

옵셔널 체이닝 덕분에 person.dog가 nil이어도 앱이 멈추지 않고 안전하게 동작해요.


📦 예제 2: 깊은 객체 탐색

class Address {
    var city: String = "서울"
}

class Profile {
    var address: Address?
}

class User {
    var profile: Profile?
}

let user = User()
let city = user.profile?.address?.city // nil

체이닝 중 하나라도 nil이면 전체 결과는 nil이 돼요.


🧠 실무 활용 예시

  • JSON 디코딩 후 모델 탐색 시 유용
  • API 응답으로 받은 데이터에 접근할 때 많이 사용
let region = response?.data?.user?.profile?.region

⚠️ 주의사항

  • 옵셔널 체이닝의 결과는 항상 옵셔널입니다.
  • nil이 아니더라도 강제로 언래핑하거나, 옵셔널 바인딩이 필요할 수 있어요.
if let dogName = person.dog?.name {
    print("강아지 이름은 \(dogName)입니다")
}

🧯 Error Handling (에러 처리)

❓ 왜 에러 처리가 필요할까요?

네트워크 요청, 파일 읽기, 데이터 파싱 등 다양한 작업에서 문제가 생길 수 있어요. 이런 에러를 처리하지 않으면 앱이 충돌할 수 있어요.

Swift는 강력한 에러 처리 메커니즘을 제공합니다.


🔨 기본 문법: try-catch

enum CustomError: Error {
    case invalidInput
}

func doSomething(input: Int) throws {
    if input < 0 {
        throw CustomError.invalidInput
    }
    print("입력값: \(input)")
}

do {
    try doSomething(input: -1)
} catch {
    print("에러 발생: \(error)")
}

😎 try? 와 try!

문법 설명 예시
try? 에러 발생 시 nil 반환 let value = try? someFunc()
try! 에러 발생 시 크래시 let value = try! someFunc() ❌ 위험

📘 예제: 파일 읽기

enum FileError: Error {
    case notFound
    case unreadable
}

func readFile(name: String) throws -> String {
    if name != "data.txt" {
        throw FileError.notFound
    }
    return "파일 내용입니다"
}

do {
    let content = try readFile(name: "wrong.txt")
    print(content)
} catch FileError.notFound {
    print("파일을 찾을 수 없습니다.")
}

🧠 실무 활용 예시

  • 서버 응답 오류 처리
  • 디코딩 실패 시 사용자에게 경고
  • 파일 또는 이미지 로딩 실패 시 대체 처리

🧬 Generic (제네릭)

🎲 제네릭이란?

다양한 타입을 하나의 함수나 타입에서 처리할 수 있게 해주는 기능이에요.

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

var a = 3
var b = 5
swapTwoValues(&a, &b)

이 함수는 Int, String, Double 등 어떤 타입이든 쓸 수 있어요!


📦 예제: 스택

struct Stack<T> {
    var items: [T] = []

    mutating func push(_ item: T) {
        items.append(item)
    }

    mutating func pop() -> T? {
        return items.popLast()
    }
}

var stringStack = Stack<String>()
stringStack.push("A")
stringStack.push("B")
print(stringStack.pop()!) // B

🎯 타입 제약 사용하기

func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? {
    for (index, item) in array.enumerated() {
        if item == value {
            return index
        }
    }
    return nil
}

🧠 실무 예시

  • 네트워크 레이어에서 API 응답 제네릭 처리
  • Custom Result 타입 만들기
  • 다양한 타입에 대응 가능한 유틸리티 함수 제작

📚 Array (배열)

🔤 배열이란?

동일한 타입의 값을 순서대로 저장하는 컬렉션입니다.

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

🔧 배열 메서드 모음

numbers.append(6)              // [1, 2, 3, 4, 5, 6]
numbers.insert(0, at: 0)       // [0, 1, 2, 3, 4, 5, 6]
let removed = numbers.remove(at: 1) // 1 제거
print(numbers)                // [0, 2, 3, 4, 5, 6]

🔍 고급 기능 예시

let scores = [90, 70, 50, 100]
let passed = scores.filter { $0 >= 70 } // [90, 70, 100]
let doubled = scores.map { $0 * 2 }     // [180, 140, 100, 200]
let sum = scores.reduce(0, +)           // 310

🧠 실무 예시

  • 테이블 뷰 데이터 관리
  • 검색 결과 리스트 저장
  • JSON 배열 응답 처리

⚠️ 주의사항

  • 인덱스 접근 시 범위 초과 방지!

    let value = numbers[10] // ❌ index out of range
  • 빈 배열일 경우 .first, .last는 nil 반환

    let first = [].first // nil

✅ 마무리 요약

개념 핵심 요약
옵셔널 체이닝 nil 값에도 안전하게 접근하는 문법
에러 처리 예외 발생 시 안전한 대응을 위한 시스템
제네릭 여러 타입을 하나의 코드로 처리할 수 있게 함
배열 가장 기본적인 컬렉션 자료형으로 데이터 저장 및 탐색