📱 모바일 프로그래밍/iOS 프로그래밍 실무
iOS 프로그래밍 실무 6주차
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 프로그래밍 실무 강의 자료