SA성아 2025. 4. 9. 16:46

-- 중간고사 비율 --

오늘 15점

다음주 10점

 

옵서녈 체이닝

(Optional Chaining)

 

 

Q: 옵셔널 체이닝을 지원하는 언어

✅ 1. Swift

Swift는 옵셔널 체이닝을 매우 적극적으로 지원합니다.

class Person {
    var name: String?
    var pet: Pet?
}

class Pet {
    var breed: String?
}

let person = Person()
// 옵셔널 체이닝 사용
if let breed = person.pet?.breed {
    print("Pet breed is \(breed)")
} else {
    print("No pet or breed found")  // 여기 출력됨
}
  • person.pet이 nil이면, 이후 .breed는 실행되지 않고 nil 반환

✅ 2. JavaScript

JavaScript는 ES2020부터 옵셔널 체이닝을 ?. 문법으로 지원합니다.

const user = {
  profile: null
};

// 옵셔널 체이닝 사용
console.log(user.profile?.name);  // undefined (에러 아님)
  • user.profile이 null이기 때문에 .name에 접근하지 않고 undefined 반환

✅ 3. TypeScript

TypeScript도 JavaScript처럼 옵셔널 체이닝을 지원합니다.

type User = {
  name?: string;
  address?: {
    city?: string;
  };
};

const user: User = {};
console.log(user.address?.city);  // undefined

✅ 4. Kotlin

Kotlin은 ?. 문법을 사용해 옵셔널 체이닝을 지원합니다.

data class Address(val city: String?)
data class User(val address: Address?)

val user: User? = User(null)

val cityName = user?.address?.city
println(cityName)  // null

✅ 5. C#

C#은 ?. 문법을 이용해 옵셔널 체이닝을 지원합니다 (C# 6.0 이상).

class Person {
    public Pet Pet { get; set; }
}
class Pet {
    public string Breed { get; set; }
}

Person person = new Person();
string breed = person.Pet?.Breed;
Console.WriteLine(breed); // null

 

var x : String? = "Hi"

print(x, x!) // Optional("Hi") Hi
if let a = x {
    print(a) // Hi
}

let b = x!.count
print(type(of:b),b) // Int 2

let b1 = x?.count 
print(type(of:b1), b1, b1!) // Optional<Int> Optional(2) 2

let c = x ?? ""
print(c) // Hi
  • count : 문자열 길이 센다(Hi -> 2)
  • x?.count는 옵셔널 체이닝
  • x가 nil이 아니면 count 실행 → 결과는 Optional(2)
  • b1의 타입은 Int?
  • b1!은 강제 언래핑해서 2
표현 설명 결과
x Optional String Optional("Hi")
x! 강제 언래핑 Hi
if let a = x 안전하게 값 꺼내기 Hi
x!.count 강제 언래핑 후 길이 2
x?.count 옵셔널 체이닝 Optional(2)
x ?? "" nil이면 "" "Hi"

 

 

선언문

var x : Int! 
        Int?
  • 공통점: 둘 다Optional 형
  • 차이점: Int! 는 자동으로 언래핑 함

실행문

x!._
x?._

 

 

✅ 실행문에서 ? vs !의 차이

기호 이름 동작 방식 안전성 결과
? 옵셔널 체이닝 값이 nil이면 다음 동작을 건너뜀 안전 nil 또는 실제 값
! 강제 언래핑 값이 nil이면 크래시 발생 위험 실제 값 (하지만 반드시 nil 아님을 보장해야 함)

🔍 예시 코드로 비교

var name: String? = "Alice"

// 1. 옵셔널 체이닝 (?)
let length1 = name?.count
print(length1)  // Optional(5)

// 2. 강제 언래핑 (!)
let length2 = name!.count
print(length2)  // 5

둘 다 "Alice"이므로 count는 5입니다.


❗ 그런데 name이 nil이라면?

name = nil

let length1 = name?.count
print(length1)  // nil (안전하게 처리됨)

let length2 = name!.count  // ❗ 런타임 오류!
print(length2)
 
  • name?.count: name이 nil이므로 실행되지 않고, 결과는 nil
  • name!.count: 강제로 값을 꺼내려고 해서 앱이 크래시됩니다

✅ 요약

구문 의미 동작 안전성
x?.something 옵셔널 체이닝 x가 nil이면 실행 안 함 안전
x!.something 강제 언래핑 x가 nil이면 앱 종료 위험

 

 

var x : String? // = "Hi"

if let a = x {
    print(a)
}

let b1 = x?.count
print(type(of:b1), b1) // Optional<Int> nil

let c = x ?? ""
print(c)

 

Optional Chaining 예

class Person {
    var name: String
    var age: Int
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}
let kim: Person = Person(name: "Kim", age: 20)
print(kim.age)

let han: Person? = Person(name: "Han", age: 25)
print(han.age) // error
let kim: Person = Person(name: "Kim", age: 20)
print(kim.age) // 20

let han: Person? = Person(name: "Han", age: 25)
print(han!.age) // 25
let kim: Person = Person(name: "Kim", age: 20)
print(kim.age) // 20

let han: Person? = Person(name: "Han", age: 25)
print(han?.age) // Optional(25), 옵셔널 체이닝
let han: Person? = Person(name: "Han", age: 25)

print((han?.age)!) // 25

if let hanAge = han?.age {
    print(hanAge) // 25
} else {
    print("nil")
}

 

옵셔널 체이닝: 점 앞에 ? 사용

class Company {
    var ceo: Person?
}

class Person {
    var name : String
    init(name: String) {
        self.name = name
    }
}

let apple = Company()
apple.ceo = Person(name: "Kim")
// print(apple.ceo.name)  오류

// 옵셔널 체이닝 없이
if let ceo = apple.ceo {
    print(ceo.name) // Kim
}
print(apple.ceo!.name)  // Kim
print(apple.ceo?.name)  // Optional("Kim")

// 옵셔널 체이닝
if let name = apple.ceo?.name {
    print(name) // Kim
}
print(apple.ceo?.name ?? "CEO가 없습니다")   // Kim

 

 

 

오류 처리

Error Handling

 

 

try

try? // 리턴값 optional, 예외 처리 간단하게
try! // 거의 사용 안함

 

찾은 함수에 throws가 붙어있다면 그냥 쓰지 못하고 예외 처리 해야됨

 

✅ iOS에서 자주 사용하는 throwing function 예시 (10개+)

함수 설명 예시
try JSONDecoder().decode(_:from:) JSON 데이터를 Swift 타입으로 디코딩할 때 API 응답 파싱
try JSONEncoder().encode(_:) Swift 객체를 JSON 데이터로 인코딩할 때 서버로 전송할 데이터 생성
try Data(contentsOf:) URL이나 파일 경로에서 데이터 읽을 때 로컬 파일 읽기
try String(contentsOf:) 텍스트 파일에서 문자열 읽기 텍스트 불러오기
try FileManager.default.removeItem(at:) 파일 삭제 시 캐시 삭제 등
try FileManager.default.copyItem(at:to:) 파일 복사 시 백업 등
try FileManager.default.createDirectory(at:withIntermediateDirectories:attributes:) 디렉토리 생성 시 앱 폴더 설정 등
try AVAudioSession.sharedInstance().setCategory(_:mode:options:) 오디오 세션 설정 시 오디오 앱
try AVAudioSession.sharedInstance().setActive(_:) 오디오 세션 활성화 통화/음악 앱
try? AVAudioPlayer(contentsOf:) 오디오 파일 재생 객체 생성 사운드 재생
try KeychainWrapper.standard.set(_:forKey:) 키체인에 값 저장 시 (서드파티 포함) 로그인 정보 저장
try FileHandle(forReadingFrom:) 파일 핸들로 파일 읽기 스트리밍 파일 읽기
try NSRegularExpression(pattern:) 정규식 객체 생성 시 문자열 검색/필터
try NSAttributedString(data:options:documentAttributes:) HTML 등에서 NSAttributedString 만들기 리치 텍스트 파싱
try? URLSession.shared.data(for:delegate:) Swift Concurrency 기반 네트워크 요청 async/await 네트워킹

 

오류 발생 가능 함수의 호출 방식 (do ~ try ~ catch)

do {
   audioPlayer = try AVAudioPlayer(contentsOf: AudioFile)
} catch let error as NSError {
      print("Error-initPlay : \(error)")
}
  • 오류 날 것 같은 코드 앞에는 try를 써주고 그 줄 앞뒤로 do - catch 블럭을 잡고 catch 다음 에러 처리 코드를 넣는다

 

Generic

<>

 

✅ 괄호 기호들의 용도 정리표

기호 이름 주요 용도 예시 설명
() 소괄호 (Parentheses) - 함수 호출
- 파라미터 그룹
- 제어문 조건
print("Hi")
if (x > 0)
가장 흔하게 쓰이는 괄호. 함수 실행 시 사용
[] 대괄호 (Brackets) - 배열/리스트
- 인덱스 접근
- 서브스크립트
arr[0]
let list: [Int]
배열을 만들거나 요소 접근 시 사용
{} 중괄호 (Braces) - 코드 블록
- 함수/조건문/반복문 본문
- 딕셔너리 리터럴
{ print("hi") }
let dict = ["key": "value"]
실행 가능한 코드나 딕셔너리, 클로저 정의에 사용
<> 꺾쇠 괄호 (Angle Brackets) - 제네릭 타입 지정
- XML/HTML 태그
- 비교 연산
Array<Int>
<div>...</div>
Swift, Java: 제네릭
HTML/XML: 태그 정의

 

기능은 같고 매개변수형만 다른 함수

func myPrint(a: Int, b: Int) {
    print(b,a)
}
func myPrint(a: Double, b: Double) {
    print(b,a)
}
myPrint(a:1,b:2)
myPrint(a:2.5,b:3.5)

 

제네릭 함수로 구현

func myPrint<T>(a: T, b: T) {
    print(b,a)
}
//func myPrint(a: Double, b: Double) {
//    print(b,a)
//}
myPrint(a:1,b:2)
myPrint(a:2.5,b:3.5)
myPrint(a: "Hi", b: "Hello")

 

일반 class vs. generic class

class Box {
    var item: Int
    init(item: Int) {
        self.item = item
    }
    func getItem() -> Int {
        return item
    }
} //일반 클래스
let intBox = Box(item: 12)
// Box<Int>(item: 123), generic class는 이렇게 쓰지만 타입 추론으로 <Int> 생략 가능
print(intBox.getItem()) // 12
let stringBox = Box(item: "Hello") // Box<String>(item: "Hello"), err
print(stringBox.getItem()) // Hello
class Box<T> {
    var item: T
    init(item: T) {
        self.item = item
    }
    func getItem() -> T {
        return item
    }
}
let intBox = Box(item: 12)
// Box<Int>(item: 123), generic class는 이렇게 쓰지만 타입 추론으로 <Int> 생략 가능
print(intBox.getItem()) // 12

let stringBox = Box(item: "Hello") // Box<String>(item: "Hello")
print(stringBox.getItem()) // Hello

 

 

Collection Type

 

✅ Collection Type이란?

여러 개의 값을 하나로 묶어 관리할 수 있는 타입
→ 배열, 집합, 딕셔너리 등이 대표적인 예입니다.

예: 학생 점수 여러 개, 장바구니 상품 목록, 키워드별 검색 결과 등


✅ 주요 Collection Type 종류

타입 설명 키 특징 Swift 예시
Array 순서가 있는 값들의 집합 - 인덱스로 접근
- 중복 허용
let arr = [1, 2, 3]
Set 순서가 없는 유일한 값들의 집합 - 중복 ❌
- 빠른 탐색
let s: Set = [1, 2, 3]
Dictionary 키-값 쌍으로 구성된 집합 - 키 중복 ❌
- 키로 값 접근
let dict = ["a": 1, "b": 2]

 

Array

✅ Swift Array 자주 쓰는 속성 (Properties)

속성 설명 예시
.count 배열의 요소 개수 arr.count → 3
.isEmpty 비어 있는지 여부 (Bool) arr.isEmpty → false
.first 첫 번째 요소 (Optional) arr.first → Optional(10)
.last 마지막 요소 (Optional) arr.last → Optional(30)
.min() 최소값 (Optional) arr.min() → Optional(10)
.max() 최대값 (Optional) arr.max() → Optional(30)
.sorted() 정렬된 배열 반환 arr.sorted() → [10, 20, 30]

✅ Swift Array 자주 쓰는 메서드 (Methods)

메서드 설명 예시
.append(_:) 요소 추가 arr.append(40)
.insert(_:at:) 특정 위치에 요소 삽입 arr.insert(15, at: 1)
.remove(at:) 특정 위치 요소 삭제 arr.remove(at: 2)
.removeFirst() 첫 요소 삭제 arr.removeFirst()
.removeLast() 마지막 요소 삭제 arr.removeLast()
.removeAll() 모든 요소 삭제 arr.removeAll()
.contains(_:) 값 포함 여부 확인 (Bool) arr.contains(20) → true
.index(of:) / .firstIndex(of:) 특정 요소의 인덱스 반환 (Optional) arr.firstIndex(of: 20)
.map {} 각 요소를 변환하여 새 배열 생성 arr.map { $0 * 2 }
.filter {} 조건을 만족하는 요소만 추출 arr.filter { $0 > 15 }
.forEach {} 요소를 하나씩 순회 arr.forEach { print($0) }
.reduce(_:_:) 모든 요소를 하나로 합침 arr.reduce(0, +) → 합계

 

Array 의 자료형

let number = [1,2,3,4] // 타입 추론
let odd : [Int] = [1,3,5]
let even : Array<Int> = [2,4,6]

print(type(of:number)) // Array<Int>
print(number) // [1,2,3,4]

print(type(of:odd)) // Array<Int>
print(odd)  // [1,3,5]

print(type(of:even))    // Array<Int>
print(even) //[2,4,6]

let animal = ["dog", "cat", "cow"]
print(type(of:animal)) // Array<String>
print(animal)   // ["dog", "cat", "cow"]

 

빈 배열(empty array) 주의 사항

let number : [Int] = []
//빈 배열을 let으로 만들 수는 있지만 초기값에서 변경 불가이니 배열의 의미 없음
var odd = [Int]()
var even : Array<Int> = Array()
print(number) //[]
print(number[0]) //오류, 빈 배열을 값을 넣은 다음에 접근
number.append(100) //let으로 선언한 불변형 배열이라 추가 불가능
//error: cannot use mutating member on immutable value: 'number' is a 'let' constant
print(number[0])
number.append(200)
print(number[0], number[1],number)
var number : [Int] = []

var odd = [Int]()
var even : Array<Int> = Array()
print(number) //[]

number.append(100) //let으로 선언한 불변형 배열이라 추가 불가능

print(number[0]) // 100
number.append(200) 
print(number[0], number[1],number) // 100 200 [100,200]
  • 빈 배열은 반드시 append를 먼저 하고 방을 가지고 놀아야 함
  • append를 하면 할 수록 방이 자동으로 늘어남
  • var 사용

가변형

var animal = ["dog", "cat", "cow"]

 

불변형

* 초기화 후 변경 불가

let animal1 = ["dog", "cat", "cow"]

 

var number : [Int] = []

// number[0] = 1 // crash, 방을 만든 후 사용
number.append(1)
print(number)   // 1
number[0] = 10
print(number)   // 10

 

Array(repeating:count:)

var x = [0,0,0,0,0]
print(x)    // [0,0,0,0,0]
var x1 = Array(repeating: 0, count: 5)
print(x1)   // [0,0,0,0,0]

 

항목이 몇 개인지(count), 비어있는지(isEmpty) 알아내기

let num = [1, 2, 3, 4]
var x = [Int]()
print(num.isEmpty) // 배열이 비어있나? false
print(x.isEmpty)    // true
if num.isEmpty {
    print("비어 있습니다")
}
else {
    print(num.count) // 배열 항목의 개수, 4
}

 

first와 last 프로퍼티

let num = [1, 2, 3, 4]
let num1 = [Int]()
print(num.first, num.last)//Optional(1) Optional(4)
print(num1.first, num1.last)//nil nil
if let f = num.first, let l = num.last {
    print(f,l) //1 4
}
  • 빈 배열이 있기 때문에 옵셔널 값이 나옴

 

첨자(subscript)로 항목 접근( 시험 자주 나옴 )

var num = [1, 2, 3, 4]
print(num[0], num[3])   
print(num.first!)
for i in 0...num.count-1{
    print(num[i])
}
print(num[1...2])
num[0...2] = [10,20,30]
print(num)
더보기

1 4

1

1

2

3

4

[2, 3]

[10, 20, 30, 4]

 

Array는 구조체이므로 값 타입

var num = [1,2,3]
var x = num //x는 num의 복사본, 별개의 배열
num[0]=100
print(num)  // [100, 2, 3]
print(x)    // [1, 2, 3]

 

Array 요소의 최댓값 최솟값 :max(), min()

var num = [1,2,3,10,20]
print(num)  // [1, 2, 3, 10, 20]
print(num.min())    // Optional(1)
print(num.max())    // Optional(20)
print(num.min()!)   // 1
print(num.max()!)   // 20

 

Array 요소의 정렬(시험 많이 나옴)

var num = [1,5,3,2,4]
num.sort() //오름차순 정렬하여 원본 변경
print(num) //[1, 2, 3, 4, 5]
num[0...4] = [2,3,4,5,1]
num.sort(by:>) //내림차순 정렬하여 원본 변경
print(num) //[5, 4, 3, 2, 1]
num[0...4] = [2,3,4,5,1]
num.reverse() //반대로 정렬하여 원본 변경
print(num) //[1, 5, 4, 3, 2]
print(num.sorted()) //오름차순 정렬 결과를 리턴하고, 원본은 그대로, var x = num.sorted()
//[1, 2, 3, 4, 5]
print(num) //[1, 5, 4, 3, 2]
print(num.sorted(by:>)) //내림차순 정렬 결과를 리턴하고, 원본은 그대로
//[5, 4, 3, 2, 1]
print(num)//[1, 5, 4, 3, 2]
  • sort: 원본 변경, sorted: 원본 그대로
  • 기본은 오름차순
  • 내림차순은 sort(by:>)

 

Swift 접근 제어

(access control, access modifier)

(중간고사 한두문제 나옴, 간단하게 단답식)

Swift 접근 제어자 비교 표

접근 수준 설명 접근 가능 범위 상속/재정의 가능 여부
Open 가장 개방적, 모듈 외부에서도 상속 및 재정의 가능 모듈 내부 및 외부 가능
Public 모듈 외부에서도 접근 가능, 상속 및 재정의 불가 모듈 내부 및 외부 불가능
Internal 같은 모듈 내에서만 접근 가능 같은 모듈 불가능
File-private 같은 파일 내에서만 접근 가능 같은 파일 불가능
Private 선언된 범위 내에서만 접근 가능 선언된 범위(클래스, 구조체 등) 불가능
Package 같은 패키지 내에서만 접근 가능 (Swift 5.9) 같은 패키지 불가능

 

Swift의 access control

public class MyClass {
// 모듈의 모든 소스 파일 내에서 접근+정의한 모듈을 가져오는 다른 모듈의 소스파일에서도 접근 가능
   fileprivate var name : String = "Kim"
   // 현재 소스 파일 내에서만 사용 가능
   private func play() {}
   // 현재 블럭 내에서만 사용 가능
   func display(){} // internal은 디폴트 속성으로 생략됨
   // internal 접근은 해당 모듈의 모든 소스 파일 내에서 사용
}

 

  • 기본 접근 제어 - internal
  • 모듈은 앱이다
  • 볼 수 있을만한건 private

 

 

 

출처: iOS 프로그래밍 실무 강의 자료