옵셔널 체이닝이란?
옵셔널 값이 nil이 아니면 안전하게 해당 값에 접근하고, nil일 경우에는 아무것도 하지 않고 nil을 반환하는 것을 말한다.
var x : String? = "Hi" // 옵셔널 타입 선언: 'String?'은 nil을 가질 수 있는 문자열 타입을 의미
// x는 초기값 "Hi"를 가지는 옵셔널 변수로 선언됨
print(x, x!) // 'x'는 옵셔널 타입이므로 출력 시 Optional("Hi") 형식으로 출력됨
// 'x!'는 강제 언래핑(Forced Unwrapping) 연산자로, x가 nil이 아니면 그 값을 안전하게 추출
// 'x!'를 사용하면, 옵셔널이 nil일 경우 런타임 오류가 발생할 수 있음
if let a = x { // 옵셔널 바인딩(Optional Binding): x가 nil이 아니면 'a'에 안전하게 값을 바인딩
// x가 nil이 아니면, x의 값을 변수 a에 할당하고 코드 블록을 실행
print(a) // 'a'는 'x'의 실제 값을 가지고 있음
}
let b = x! .count // 강제 언래핑: 'x!'는 옵셔널을 강제로 풀어내고, 문자열의 'count' 속성에 접근
// 'count'는 문자열의 길이를 반환하는 프로퍼티
// 여기서 x는 "Hi"이므로, .count는 2를 반환함
print(type(of: b), b) // 'type(of: b)'는 'b'의 타입을 출력, 'b'의 타입은 Int이고 값은 2임
let b1 = x?.count // 옵셔널 체이닝(Optional Chaining): 'x'가 nil이 아닌 경우에만 'count'에 접근
// 'x'가 nil이라면, 'x?.count'는 nil을 반환하고, nil이 아니면 count를 실행하여 값을 반환
// 'x'가 "Hi"이므로 count는 2를 반환하고, b1은 Optional(2)가 됨
print(type(of: b1), b1, b1!) // 'type(of: b1)'은 b1의 타입을 출력, 'b1'은 Optional(Int) 타입
// 'b1!'을 강제 언래핑하여 실제 값을 출력하면 2가 됨
let c = x ?? "" // 널 병합 연산자(Null-Coalescing Operator): 'x'가 nil일 경우 기본값인 ""을 반환
// 'x'가 nil이 아니면 'x'의 값을 그대로 반환함. 여기서 'x'는 "Hi"이므로 'c'는 "Hi"가 됨
print(c) // 'c'의 값은 "Hi"이고 출력됨
//실행 결과
Optional("Hi") Hi
Hi
Int 2
Optional<Int> Optional(2) 2
Hi
옵셔널 체이닝의 다른 예시이다.
class Person { // Person 클래스 정의
var name: String // String 타입의 name 변수 선언
var age: Int // Int 타입의 age 변수 선언
init(name: String, age: Int) { // 초기화 메서드 정의, 매개변수로 name과 age를 받음
self.name = name // self 키워드로 인스턴스의 name 변수에 매개변수 name을 할당
self.age = age // self 키워드로 인스턴스의 age 변수에 매개변수 age를 할당
}
}
let kim: Person = Person(name: "Kim", age: 20) // kim이라는 상수에 Person 인스턴스 생성
print(kim.age) // kim의 age를 출력
let han: Person? = Person(name: "Han", age: 25) // han이라는 옵셔널 상수에 Person 인스턴스 생성
print((han?.age)!) // 옵셔널 체이닝을 사용하여 han의 age를 강제로 언래핑하여 출력
if let hanAge = han?.age { // 옵셔널 바인딩: han의 age가 nil이 아닐 경우 hanAge 상수에 할당
print(hanAge) // han의 나이를 출력
} else {
print("nil") // han이 nil인 경우 "nil" 출력
}
//실행 결과
20
Optional(25)
25
25
옵셔널 체이닝 예시 3
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
//실행 결과
Kim
Kim
Optional("Kim")
Kim
Kim
try
let result = try? throwingFunction() //함수가 오류를 발생시키면 nil을 반환
//안전하게 사용 가능하며 간단한 오류 처리 가능
if let value = result {
print("성공: \(value)")
} else {
print("실패")
}
let result = try! throwingFunction() //함수가 오류를 발생시키면 런타임 오류가 발생하여 프로그램이 크래시
//호출하는 시점에 오류가 발생하지 않는다는 확신이 있다면 사용
print("결과: \(result)")
throwing function
매개변수 괄호 다음 throws라는 키워드가 있다면 error handing을 해 줘야 한다. 아래의 코드가 그 예시이다.
do {
audioPlayer = try AVAudioPlayer(contentsOf: audioFile)
} catch let error as NSError {
print("Error-initPlay : \(error)")
}
throwing fucntion을 쓸 때는 do ~ try ~ catch의 형태로 사용한다.
Generic <>
따로 자료형을 선언하지 않는다.
제네릭은 다양한 데이터 타입을 안전하게 처리할 수 있게 해 주고, 자료형이 다른 똑같은 기능의 함수를 따로 만들지 않아도 된다!
func myPrint <T> (a: T, b: T) { //템플릿의 약자인 T를 많이 사용한다.
print(b,a)
}
myPrint(a:1,b:2)
myPrint(a:2.5,b:3.5)
myPrint(a: "Hi", b: "Hello")
//실행 결과
2 1
3.5 2.5
Hello Hi
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")
//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
Array
// 타입 추론을 사용하여 정수 배열을 정의
let number = [1, 2, 3, 4] // 타입 추론: number는 Array<Int>로 추론됨
// 명시적으로 Int 타입의 배열을 정의
let odd: [Int] = [1, 3, 5] // odd는 Array<Int>로 선언
// 명시적으로 Array<Int> 타입을 선언
let even: Array<Int> = [2, 4, 6] // even은 Array<Int>로 선언
// number 배열의 타입 출력
print(type(of: number)) // Array<Int>
// number 배열의 내용 출력
print(number) // [1, 2, 3, 4]
// odd 배열의 타입 출력
print(type(of: odd)) // Array<Int>
// odd 배열의 내용 출력
print(odd) // [1, 3, 5]
// even 배열의 타입 출력
print(type(of: even)) // Array<Int>
// even 배열의 내용 출력
print(even) // [2, 4, 6]
// 타입 추론을 사용하여 문자열 배열을 정의
let animal = ["dog", "cat", "cow"] // 타입 추론: animal은 Array<String>으로 추론됨
// animal 배열의 타입 출력
print(type(of: animal)) // Array<String>
// animal 배열의 내용 출력
print(animal) // ["dog", "cat", "cow"]
Empty Array
var number : [Int] = [] //var 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)
//실행 결과
[]
100
100 200 [100, 200]
초기값이 변경되지 않는 배열은 let으로 만드는 게 더 좋다. 하지만 빈 배열의 경우 계속 값을 추가해 줘야 하기 때문에 var로 선언해야 한다.
// 0으로 초기화된 5개의 요소를 가진 배열 x 생성
var x = [0, 0, 0, 0, 0]
print(x) // [0, 0, 0, 0, 0] 출력
// 0으로 초기화된 5개의 요소를 가진 배열 x1 생성
var x1 = Array(repeating: 0, count: 5)
print(x1) // [0, 0, 0, 0, 0] 출력
// 1로 초기화된 3개의 요소를 가진 Int 타입의 배열 x2 생성
var x2 = [Int](repeating: 1, count: 3)
print(x2) // [1, 1, 1] 출력
// "A"로 초기화된 4개의 요소를 가진 String 타입의 배열 x3 생성
var x3 = [String](repeating: "A", count: 4)
print(x3) // ["A", "A", "A", "A"] 출력
for ~ in 으로 배열의 항목 접근하기
let colors = ["red", "green", "blue"]
print(colors)
for color in colors {
print(color)
}
//실행 결과
["red", "green", "blue"]
red
green
blue
count, isEmpty로 항목이 몇 개인지, 비어 있는지 알아내기
let num = [1, 2, 3, 4]
var x = [Int]()
print(num.isEmpty) //배열이 비어있나? false
print(x.isEmpty)
if num.isEmpty {
print("비어 있습니다")
}
else {
print(num.count) //배열 항목의 개수
}
//실행 결과
false
true
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
}
첨자로 항목에 접근하는 법이다.
var num = [1, 2, 3, 4]
print(num[0], num[3]) //1 4
print(num.first!) //1
for i in 0...num.count-1{
print(num[i]) // 1\n 2\n 3\n 4\n
}
print(num[1...2]) //[2, 3]
num[0...2] = [10,20,30]
print(num) //[10, 20, 30, 4]
항목을 추가 / 제거하는 방법이다.
// let x = [1, 2, 3, 4] // 불변 배열은 초기값에서 변경 불가
// x.append(5) // 오류 발생: let으로 선언된 배열은 변경할 수 없음
var num = [1, 2, 3] // 가변 배열 num 생성
print(num) // [1, 2, 3] 출력
num.append(4) // 4 추가
print(num) // [1, 2, 3, 4] 출력
num.append(contentsOf: [6, 7, 8]) // [6, 7, 8] 추가
print(num) // [1, 2, 3, 4, 6, 7, 8] 출력
num.insert(5, at: 4) // 인덱스 4에 5 추가
print(num) // [1, 2, 3, 4, 5, 6, 7, 8] 출력
num.remove(at: 3) // 인덱스 3의 요소 제거
print(num) // [1, 2, 3, 5, 6, 7, 8] 출력
num.removeLast() // 마지막 요소 제거
print(num) // [1, 2, 3, 5, 6, 7] 출력
print(num.firstIndex(of: 2)) // Optional(1), 2가 처음으로 나오는 인덱스
if let i = num.firstIndex(of: 2) { // 2가 처음으로 저장된 방의 값을 20으로 변경
num[i] = 20 // num[1] = 20
}
print(num) // [1, 20, 3, 5, 6, 7] 출력
num = num + num // 배열을 자신과 더하여 중복 생성
print(num) // [1, 20, 3, 5, 6, 7, 1, 20, 3, 5, 6, 7] 출력
num += [8, 9] // [8, 9] 추가
print(num) // [1, 20, 3, 5, 6, 7, 1, 20, 3, 5, 6, 7, 8, 9] 출력
num.removeAll() // 모든 요소 제거
print(num) // [] 출력
Array는 구조체이다.
var num = [1,2,3]
var x = num //x는 num의 복사본, 별개의 배열
num[0]=100
print(num) //[100, 2, 3]
print(x) //[1, 2, 3]
배열의 최댓값과 최솟값이다.
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
요소 정렬하기(sort와 sorted의 차이)
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는 정렬한 결과를 잠깐 리턴하는 것이기 때문에 원본은 그대로이다.
sorted 한 결과를 저장하려면 다른 변수를 선언하여 넣어 줘야 한다.
'iOS' 카테고리의 다른 글
Swift - Open API 기반 앱 만들기(미완성) (0) | 2025.05.07 |
---|---|
Swift - enum, struct, extension (1) | 2025.04.16 |
Swift - TableView (0) | 2025.04.02 |
Swift - 프로토콜 (0) | 2025.03.26 |
Swift의 주요 문법 복습 (0) | 2025.03.26 |