
- 커뮤니티 기반 개발
- 누구나 기여 가능
- 엄격한 문법으로 오류 예방
- 옵셔널, guard 구문 제공
- 강력한 타입 체계
3.신속성 (Fast)
- C 언어 수준의 성능
- 최적화된 실행 속도
4.표현성 (Expressive)
- 간결하고 읽기 쉬운 문법
- 세미콜론 등 불필요 요소 제거
- Xcode (Playground)
- REPL (Read-Eval-Print-Loop)
var name = "홍길동" // 문자열 변수
var age = 25 // 정수형 변수
var height = 175.5 // 실수형 변수
var myVariable = 42 // 값 변경 O
Myvariable += 42
상수 (Constant)
let pi = 3.14159 // 상수
let birthYear = 1995 // 상수
let yourVariable = 42 // 42 고정
yourVariable += 42 // X
타입 명시
var name: String = "홍길동" // 문자열 타입 명시
var age: Int = 25 // 정수형 타입 명시
var height: Double = 175.5 // 실수형 타입 명시
var isStudent: Bool = true // 불리언 타입 명시
let explicitInt: Int = 70
// 변수 explicitInt의 타입을 명시적으로 Int로 지정
let inplicitInteger = 70
// 변수 inplicitInteger의 타입은 지정되지 않았으나 rvalue인 70을 보고 Int로 지정
let implicitDouble = 70.5
let explicitDouble: Double = 70
// 자동형 변환이 되는지… 확인요망
let Forcefloat : Float = 4
// 실습: 명시적으로 Float 타입인 상수를 만들고 4라는 값을 할당해보자
Int: 정수 (1, 2, 100, -5 등)
Double: 64비트 부동소수점 (3.14, 0.25 등)
Float: 32비트 부동소수점 (3.14f, 0.25f 등)
Bool: 참/거짓 (true, false)
String: 문자열 ("안녕하세요")
Character: 단일 문자 ("A", "가")
형변환
let label = "The width is "
let width = 94
let widthLabel = label + String(width)
// String는 String의 생성자를 호출
// label , width 둘다 String 이여야함
let height = Int(”10”)
let sheight = Int(“10.5”)
let dheight = Int(10.5)
// 실습: 마지막 줄에 명시한 String 타입 변환 부분을 제거해보자. 어떤 에러가 발생하는가?
ERROR!
/tmp/h5CIZIHn1S/main.swift:9:24: error: binary operator '+' cannot be applied to operands of type 'String' and 'Int'
let widthLabel = label + width
~~~~~ ^ ~~~~~
/tmp/h5CIZIHn1S/main.swift:9:24: note: overloads for '+' exist with these partially matching parameter lists: (Int, Int), (String, String)
let widthLabel = label + width
Swift는 강한 타입 검사(strict type checking)를 하는 언어. 이는 다른 언어들과 달리 서로 다른 타입을 자동으로 변환하지 않는다는 의미
문자열 처리
let firstName = "길동"
let lastName = "홍"
let fullName = lastName + firstName // "홍길동"
문자열 보간법 (String Interpolation)
let name = "홍길동"
let age = 25
// \()가 문자열 안에 있다
let introduction = "\(name)의 나이는 \(age)세입니다."
// "홍길동의 나이는 25세입니다."
// 실습: \() 를 이용해 문자열 안에 실수형 계산을 포함하도록 해보고, 인사말 안에 누군가의 이름을 넣어보자.
// 1. 문자열 안에 실수형 계산 포함하기
let width = 2.5
let height = 3.0
let area = width * height
// 문자열 보간법을 사용해 계산 결과 포함하기
let message = "직사각형의 면적은 \(width) × \(height) = \(area) 제곱미터입니다."
print(message)
// 2. 인사말 안에 누군가의 이름 넣기
let name = "철수"
let greeting = "안녕하세요, \(name)님! 오늘 좋은 하루 되세요."
print(greeting)
// 직접 계산식을 문자열 안에 넣을 수도 있습니다
let pricePerItem = 1000.0
let quantity = 3
let total = "총 가격: \(pricePerItem * Double(quantity))원"
print(total)
직사각형의 면적은 2.5 × 3.0 = 7.5 제곱미터입니다.
안녕하세요, 철수님! 오늘 좋은 하루 되세요.
총 가격: 3000.0원
여러 줄 문자열
let poem = """
봄바람이
살랑살랑
불어옵니다.
"""
배열(array): [] 대괄호 를 이용하여 만든다
var fruits = ["사과", "바나나", "오렌지"]
fruits.append("딸기") // 배열에 요소 추가
let firstFruit = fruits[0] // 배열 인덱싱 (사과)
// 배열 선언
var list1: [Int] = []
// Int를 저장하는 빈 배열, 명시적
var list2 =[Int]()
// Int를 저장하는 빈 배열, 암시적
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
// 배열초기화, 암시적 shoppingList[1] = "bottle of water"
// 배열 요소 액세스
let first = shoppingList.first // shoppingList[0]
딕셔너리 (Dictionary) : <key, value>로 값을 저장하는 구도
var scores = ["수학": 90, "영어": 85, "국어": 95]
scores["과학"] = 88 // 새 키-값 쌍 추가
let mathScore = scores["수학"] // 값 접근 (90)
// 딕셔너리 선언
var dict1: [String: Int] = [:]
// <문자열, 값>으로 저장하는 딕셔너리, 명시적
var dict2 = Dictionary<String, Int>()
// <문자열, 값>으로 저장하는 딕셔너리, 암시적
var occupations = [ "Malcolm":"Captian", "Kaylee":"Mechanic", ]
//딕셔너리 초기화
occupations["Jayne"] = "Public Relations” let myjob = occupations[“jmlee”]
// 에러, “jmlee”가 저장되어 있지 않기 때문
세트 (Set)
var uniqueNumbers: Set<Int> = [1, 2, 3, 4, 5]
uniqueNumbers.insert(6) // 요소 추가
uniqueNumbers.contains(3) // 요소 확인 (true)
let score = 75
// 1. 올바른 방법: 불리언 표현식 사용
if score > 50 {
print("높은 점수입니다")
}
// 2. 잘못된 방법: 숫자만 사용하면 에러 발생
// if score { // 컴파일 에러: 'Int' 타입은 불리언 컨텍스트에서 사용할 수 없음
// print("점수가 0이 아닙니다")
// }
// 3. 반드시 불리언 표현식을 명시적으로 사용해야 함
if score != 0 {
print("점수가 0이 아닙니다")
}
// 4. 괄호 사용은 선택사항
if (score > 60) {
print("합격입니다 (괄호 사용)")
}
if score > 60 {
print("합격입니다 (괄호 없음)")
}
// 5. 다른 언어와의 비교 - JavaScript에서는 아래 코드가 유효함
// JavaScript 예시: if(score) { console.log("점수가 0이 아닙니다") }
// Swift에서는 반드시 명시적 비교가 필요합니다
var teamScore = 0
let score = 30
if score > 50 {
teamScore = 3
} else {
teamScore = 1
}
print(teamScore)
switch 문
let vegetable = "red pepper"
switch vegetable {
case "celery":
let vegetableComment = "Add some raisins and make ants on a log."
case "cucumber", "watercress":
let vegetableComment = "That would make a good tea sandwich."
case let x where x.hasSuffix("pepper"):
let vegetableComment = "Is it a spicy \(x)?"
default:
let vegetableComment = "Everything tastes good in soup."
}
//실습: switch문에서 default 부분을 제거해 보자. 어떤 에러가 발생하는가?
error: switch must be exhaustive
switch vegetable {
^
note: do you want to add a default clause?
switch vegetable {
^
---
Swift의 switch문은 모든 가능한 경우를 처리해야 하는 '완전성(exhaustive)' 규칙이 있습니다.
default 케이스를 제거하면 "switch must be exhaustive"(switch문이 완전해야 함) 에러가 발생합니다.
String 타입은 가능한 값이 무한히 많아 명시적 case만으로는 모든 경우를 처리할 수 없으므로 default가 필요합니다.
// 문자열 상수 vegetable을 선언하고 "red pepper"라는 값을 할당합니다.
// Swift의 switch문은 문자열, 정수, 부동소수점, 불리언 등 다양한 데이터 타입을 처리할 수 있습니다.
let vegetable = "red pepper"
print("Selected vegetable: \(vegetable)")
// vegetable 변수를 기준으로 switch문을 시작합니다.
// 변수를 switch 밖에서 선언하여 결과값을 저장할 수 있게 합니다.
var vegetableComment = ""
switch vegetable {
// 첫 번째 case: vegetable이 "celery"와 정확히 일치하는지 확인합니다.
// 일치하면 아래 코드를 실행하고 자동으로 switch문을 빠져나갑니다(break 필요 없음).
case "celery":
vegetableComment = "Add some raisins and make ants on a log."
print("Celery case matched!")
// 두 번째 case: vegetable이 "cucumber" 또는 "watercress"와 일치하는지 확인합니다.
// 쉼표로 구분하여 여러 조건을 하나의 case에서 처리할 수 있습니다.
// 일치하면 아래 코드를 실행하고 자동으로 switch문을 빠져나갑니다.
case "cucumber", "watercress":
vegetableComment = "That would make a good tea sandwich."
print("Cucumber or watercress case matched!")
// 세 번째 case: 패턴 매칭과 조건문(where)을 함께 사용합니다.
// let x는 vegetable 값을 임시 변수 x에 바인딩합니다.
// where 절은 추가 조건을 지정하며, 이 경우 x가 "pepper"로 끝나는지 확인합니다.
// "red pepper"는 "pepper"로 끝나므로 이 case와 일치합니다.
// \(x)는 문자열 보간법으로 실제 vegetable 값을 메시지에 포함합니다.
case let x where x.hasSuffix("pepper"):
vegetableComment = "Is it a spicy \(x)?"
print("Pepper suffix case matched! Found: \(x)")
// default case는 앞선 모든 case가 일치하지 않을 때 실행됩니다.
// Swift의 switch문은 완전성(exhaustive)이 요구되므로,
// 모든 가능한 값을 처리하기 위해 default case가 필요합니다.
default:
vegetableComment = "Everything tastes good in soup."
print("Default case matched!")
}
// switch문 실행 후 최종 결과 출력
print("Final comment: \(vegetableComment)")
// 첫 번째 for 루프 예제: 반열린 범위(half-open range) 사용
var firstForLoop = 0
var range = 0..<5 // 0, 1, 2, 3, 4를 포함하는 범위 (5는 포함하지 않음)
for i in range {
firstForLoop += i // 각 반복마다 i 값을 더함
}
print("firstForLoop=", firstForLoop) // 결과: 0+1+2+3+4 = 10
// 두 번째 for 루프 예제: 닫힌 범위(closed range) 사용
var secondForLoop = 0
for _ in 1...5 { // 1, 2, 3, 4, 5를 포함하는 범위
secondForLoop += 1 // 반복 값을 사용하지 않고(_) 단순히 5번 반복
// _ 를 사용하면 필요 없는 변수를 생성하지 않는다.
}
print("secondForLoop=", secondForLoop) // 결과: 1+1+1+1+1 = 5
for in 컨테이너
• 컨테이너 객체(배열, 딕셔너리) 포함된 요소들을 순차적으로 가져옴
// 숫자 컬렉션을 포함하는 딕셔너리 선언
let interestingNumbers = [
"Prime": [2, 3, 5, 7, 11, 13],
"Fibonacci": [1, 1, 2, 3, 5, 8],
"Square": [1, 4, 9, 16, 25],
]
var largest = 0
// 딕셔너리를 순회하며 가장 큰 값 찾기
for (kind, numbers) in interestingNumbers {
// (kind, numbers)대신에 (_, numbers) 가 더 좋다
// kind 변수를 사용하지 않는다면 와일드카드 패턴(_)을 사용하는 것이 좋음
for number in numbers {
if number > largest {
largest = number
}
}
}
print("The largest number is \(largest)") // 결과: 25
while
// while 루프 예제
var n = 2
while n < 100 {
n = n * 2
}
print("최종 n 값:", n) // 결과: 128
// repeat-while 루프 예제 (다른 언어의 do-while과 동일)
var m = 2
repeat {
m = m * 2
} while m < 100
print("최종 m 값:", m) // 결과: 128
//do{ }while 대신에 repeat {} while를 사용한다.
// Swift에서는 do-while 대신 repeat-while 문법을 사용함
// Swift에서 func 키워드를 사용하여 함수를 선언합니다
func greet(name: String, day: String) -> String {
// -> String은 함수의 반환 타입이 String임을 나타냅니다
// 반환값이 없는 경우 -> 생략 또는 -> Void로 표기합니다
return "Hello \(name), today is \(day)."
// \(변수명)을 사용하여 문자열 내에 변수 값을 삽입할 수 있습니다
}
// 함수 호출 시 매개변수 이름(name:, day:)을 명시해야 합니다
greet(name: "Bob", day: "Tuesday")
// Swift에서는 매개변수 순서가 중요합니다
// 파이썬과 달리 매개변수 순서를 바꾸면 오류가 발생합니다
greet(day: "Tuesday", name: "Bob")
// 전달 매개변수(argument label)와 매개변수 이름(parameter name) 모두 지정한 함수
func greet(from name: String, today day: String) -> String {
// from: 전달 매개변수, name: 함수 내부에서 사용하는 매개변수 이름
// today: 전달 매개변수, day: 함수 내부에서 사용하는 매개변수 이름
return "Hello \(name), today is \(day)."
}
// 함수 호출 시 전달 매개변수 이름을 사용해야 함
print(greet(from: "Bob", today: "Tuesday")) // 정상 작동
// 함수 내부 매개변수 이름으로 호출하면 에러 발생
// print(greet(name: "Bob", day: "Tuesday")) // 에러
// 밑줄(_)을 사용하여 두 번째 전달 매개변수를 생략한 함수
func greet(from name: String, _ day: String) -> String {
// from: 전달 매개변수, name: 함수 내부에서 사용하는 매개변수 이름
// _: 전달 매개변수 생략, day: 함수 내부에서 사용하는 매개변수 이름
return "Hello \(name), today is \(day)."
}
// 두 번째 매개변수는 전달 매개변수 없이 값만 전달
print(greet(from: "Bob", "Tuesday")) // 정상 작동
// 함수 내부 매개변수 이름으로 호출하면 에러 발생
// print(greet(name: "Bob", day: "Tuesday")) // 에러
// 가변 인자(Variadic Parameters)를 사용한 함수
// Int... 문법은 여러 개의 Int 값을 받을 수 있게 함
func sumOf(numbers: Int...) -> Int {
var sum = 0
for number in numbers {
sum += number
}
return sum
}
// 가변 인자 함수 호출 예시
print(sumOf()) // 0 (인자가 없으면 빈 배열로 처리)
print(sumOf(numbers: 42, 597, 12)) // 651 (여러 값을 콤마로 구분하여 전달)
// 배열을 매개변수로 받는 함수
func sumOf1(numbers: [Int]) -> Int {
var sum = 0
for number in numbers {
sum += number
}
return sum
}
// 배열 매개변수 함수 호출 예시
print(sumOf1(numbers: [42, 597, 12])) // 651 (배열을 직접 전달)
10.중첩 함수
// 중첩 함수(Nested Function) 예시
func returnFifteen() -> Int {
// 외부 함수에서 변수 선언
var y = 10
// 내부에 함수 정의 (중첩 함수)
func add() {
// 중첩 함수는 외부 함수의 변수에 접근 가능
y += 5
}
// 중첩 함수 호출
add()
// 변경된 변수 값 반환
return y
}
// 함수 호출 결과: 15
returnFifteen()
// 함수를 반환하는 함수 예시
// 반환 타입 (Int) -> Int는 "Int를 받아서 Int를 반환하는 함수"를 의미함
func makeIncrementer() -> (Int) -> Int {
// 중첩 함수 정의
func addOne(number: Int) -> Int {
return 1 + number
}
// 함수를 반환값으로 돌려줌
return addOne
}
// 반환된 함수를 변수에 할당
var increment = makeIncrementer()
// 할당된 함수 사용 - 결과: 8
increment(7)
// 함수를 매개변수로 받는 함수 정의
// 두 번째 매개변수 condition은 Int를 받아 Bool을 반환하는 함수 타입
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
// 배열의 각 요소에 대해
for item in list {
// 전달받은 condition 함수에 현재 요소를 전달하여 평가
if condition(item) {
return true // 조건을 만족하는 요소가 하나라도 있으면 true 반환
}
}
return false // 조건을 만족하는 요소가 없으면 false 반환
}
// 판별 함수 정의 - 숫자가 10보다 작은지 확인
func lessThanTen(number: Int) -> Bool {
return number < 10
}
// 테스트할 배열
var numbers = [20, 19, 7, 12]
// 함수를 매개변수로 전달하여 호출
// 결과: true (배열에 7이 있어 10보다 작은 요소가 존재함)
hasAnyMatches(list: numbers, condition: lessThanTen)
12.클로저
// 클로저 사용 예시
let numbers = [10, 21, 30]
// 클로저 표현식을 map 함수의 인자로 직접 전달
let y = numbers.map({ (number: Int) -> Int in
// 'in' 키워드로 클로저의 매개변수와 본문을 구분
return 3 * number
})
// 결과: [30, 60, 90]
print(y)
// 함수를 사용한 동일한 결과 구현
let numbers2 = [10, 21, 30]
// 이름 있는 함수 정의
func threeTimes(number: Int) -> Int {
return 3 * number
}
// 함수 이름을 map의 인자로 전달
let y2 = numbers2.map(threeTimes)
// 결과: [30, 60, 90]
print(y2)
배열의 map 함수

클로저의 간결화

// 1. 기본 형태의 클로저
let numbers1 = [10, 20, 30]
var y1 = numbers1.map({ (number: Int) -> Int in
let result = 3 * number
return result
})
// 2. 매개변수 타입 생략 (컴파일러가 타입 추론)
let numbers2 = [10, 20, 30]
var y2 = numbers2.map({ (number) -> Int in
let result = 3 * number
return result
})
// 3. 반환 타입 생략 및 단일 표현식으로 축약
// 단일 표현식일 경우 return 키워드 생략 가능
let numbers3 = [10, 20, 30]
var y3 = numbers3.map({ (number) in 3 * number })
// 1. 매개변수 이름을 사용한 클로저
let numbers1 = [10, 20, 30]
var y1 = numbers1.map({ number in 3 * number })
// 2. 매개변수 번호를 사용한 더 간결한 클로저
// $0은 첫 번째 매개변수를 의미
let numbers2 = [10, 20, 30]
var y2 = numbers2.map({ 3 * $0 })
// 클래스 정의
class Shape {
// 속성(프로퍼티) 선언 - 인스턴스 변수
var numberOfSides = 0
// 메서드 정의 - 인스턴스 함수
func simpleDescription() -> String {
// 문자열 보간법(String Interpolation)을 사용하여 변수값을 문자열에 포함
return "A shape with \(numberOfSides) sides."
}
}
// 인스턴스 생성 - Swift에서는 new 키워드 없이 생성자 호출
var shape = Shape()
// 점(.) 연산자로 속성에 접근하여 값 변경
shape.numberOfSides = 7
// 점(.) 연산자로 메서드 호출
var shapeDescription = shape.simpleDescription()
// Swift 클래스의 특징:
// 1. new 키워드 없이 인스턴스 생성
// 2. 속성(프로퍼티)과 메서드를 가질 수 있음
// 3. 기본값으로 속성 초기화 가능
// 4. 점(.) 연산자로 속성과 메서드에 접근
// 5. 문자열 보간법 사용 가능 - \(변수명)
// 6. 참조 타입으로 작동 (값 타입인 구조체와 대비됨)
// 7. 상속, 다형성, 캡슐화 등 객체지향 프로그래밍 지원
// NamedShape: 도형의 기본 속성을 정의하는 클래스
class NamedShape {
// 도형의 변의 수를 저장하는 속성
// - Int 타입의 non-optional 변수
// - 초기값 0으로 선언되어 있어 별도 초기화 필요 없음
var numberOfSides: Int = 0
// 도형의 이름을 저장하는 속성
// - String 타입의 non-optional 변수
// - 초기값이 없으므로 반드시 초기화 필요
var name: String
// 생성자(initializer)
// - name 파라미터를 받아 객체 초기화
init(name: String) {
// self.name에 파라미터 값 할당하여 초기화
// name 속성은 non-optional이므로 반드시 이 시점에서 초기화해야 함
self.name = name
}
// 도형에 대한 간단한 설명을 반환하는 메서드
// - 반환 타입: String
func simpleDescription() -> String {
// 문자열 보간법(String interpolation)을 사용하여 도형 정보 반환
return "A shape with \(numberOfSides) sides."
}
}
class NamedShape {
var numberOfSides: Int = 0
var name: String
// 초기화 메서드(생성자)
init(name: String) {
print("\(name) 객체가 초기화됩니다.")
self.name = name
}
// 소멸자 - 객체가 메모리에서 해제될 때 호출됨
deinit {
print("\(name) 객체가 메모리에서 해제됩니다.")
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
// deinit 동작 확인을 위한 코드
func createAndDestroyShape() {
// 함수 내부에서 NamedShape 인스턴스 생성
// 함수가 종료되면 이 인스턴스는 스코프를 벗어나 메모리에서 해제됨
let square = NamedShape(name: "사각형")
square.numberOfSides = 4
print(square.simpleDescription())
// 함수가 여기서 종료되면 square 객체의 deinit이 호출됨
}
// 함수 호출
createAndDestroyShape()
// 옵셔널 변수와 함께 사용하는 예제
var circle: NamedShape? = NamedShape(name: "원")
circle?.numberOfSides = 0 // 원은 곡선이므로 sides는 0으로 설정
print(circle?.simpleDescription() ?? "")
// nil을 할당하여 메모리에서 해제
circle = nil // 이 시점에 deinit 호출됨
// Square 클래스: NamedShape를 상속받는 사각형 클래스
class Square: NamedShape {
// 사각형의 한 변의 길이를 저장하는 속성
var sideLength: Double
// 초기화 메서드 (생성자)
// - sideLength와 name을 매개변수로 받음
init(sideLength: Double, name: String) {
// 1. 자신의 속성부터 초기화
self.sideLength = sideLength
// 2. 부모 클래스의 초기화 메서드 호출
super.init(name: name)
// 3. 부모로부터 상속받은 속성 설정
numberOfSides = 4
}
// 사각형의 넓이를 계산하는 메서드
func area() -> Double {
return sideLength * sideLength
}
// 부모 클래스의 메서드를 재정의(override)
override func simpleDescription() -> String {
return "A square with sides of length \(sideLength)."
}
}
// Square 인스턴스 생성 및 사용
let test = Square(sideLength: 5.2, name: "my test square")
test.area() // 결과: 27.04
test.simpleDescription() // 결과: "A square with sides of length 5.2."
// 실습: NamedShape클래스의 또 다른 하위 클래스인 Circle을 만들어보자.
// 이 클래스는 이니셜 라이저를 통해 radius와 name을 인자로 받는다. Circle 클래스 안에 area, simpleDescription 함수를 구현해보자
// 요구사항에 따른 Circle 클래스 구현
class Circle: NamedShape {
// 원의 반지름을 저장하는 속성
var radius: Double
// 초기화 메서드 (생성자)
init(radius: Double, name: String) {
// 1. 자신의 속성부터 초기화
self.radius = radius
// 2. 부모 클래스의 초기화 메서드 호출
super.init(name: name)
// 3. 원은 곡선이므로 변의 수는 0으로 설정
numberOfSides = 0
}
// 원의 넓이를 계산하는 메서드
func area() -> Double {
return Double.pi * radius * radius
}
// 부모 클래스의 메서드를 재정의
override func simpleDescription() -> String {
return "A circle with radius of \(radius)."
}
}
// Circle 클래스 사용 예시
let myCircle = Circle(radius: 2.5, name: "my circle")
myCircle.area() // 결과: 약 19.63 (π × 2.5²)
myCircle.simpleDescription() // 결과: "A circle with radius of 2.5."
// EquilateralTriangle: 정삼각형 클래스 (NamedShape 상속)
class EquilateralTriangle: NamedShape {
// 저장 속성(Stored Property): 실제 값을 메모리에 저장하는 속성
// - 클래스, 구조체의 인스턴스와 연관된 값을 저장
var sideLength: Double = 0.0
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 3
}
// 계산 속성(Computed Property): 값을 저장하지 않고 계산하는 속성
// - getter와 setter를 통해 다른 속성에 기반한 값을 계산하거나 설정
// - 메모리에 실제 값을 저장하지 않고, 접근할 때마다 계산됨
// - 캡슐화(encapsulation)를 구현하는 방법 중 하나
var perimeter: Double {
// getter: 속성 값을 읽을 때 실행되는 코드 블록
// - 속성에 접근할 때마다 호출되어 계산된 값 반환
// - 다른 속성값을 가공하여 새로운 값 생성
// - 값을 저장하지 않고 필요할 때마다 계산하므로 항상 최신 상태 유지
get {
// 정삼각형의 둘레 = 한 변의 길이 × 3
return 3.0 * sideLength
}
// setter: 속성 값을 설정할 때 실행되는 코드 블록
// - 속성에 새 값을 할당할 때마다 호출됨
// - newValue: 새로 할당된 값을 참조하는 기본 파라미터명
// - 다른 저장 속성의 값을 변경하는 로직 포함
// - 속성 간의 일관성을 유지하는 데 유용
set {
// 할당된 둘레 값으로부터 한 변의 길이 역산
sideLength = newValue / 3.0
}
}
override func simpleDescription() -> String {
return "An equilateral triagle with sides of length \(sideLength)."
}
}
// 정삼각형 인스턴스 생성
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
// getter 호출 - 계산 속성의 값을 읽을 때 자동으로 get 블록 실행
triangle.perimeter // 결과: 9.3 (3.1 × 3)
// setter 호출 - 계산 속성에 값을 할당할 때 자동으로 set 블록 실행
triangle.perimeter = 9.9 // sideLength가 3.3으로 변경됨 (9.9 ÷ 3)
// 변경된 저장 속성 확인
triangle.sideLength // 결과: 3.3
// 열거형(Enum) - Int 타입의 원시값(raw value)을 가지는 열거형 정의
enum Rank: Int {
// case 키워드로 열거형의 각 케이스(멤버)를 정의
// Ace에는 원시값 1을 명시적으로 할당
case Ace = 1
// 명시적으로 값을 할당하지 않으면 이전 케이스의 원시값에 1을 더한 값이 자동 할당됨
// Two=2, Three=3, ... 순서대로 값이 증가
case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
// 계속해서 값이 증가: Jack=11, Queen=12, King=13
case Jack, Queen, King
// 열거형 내부에 메서드 정의 가능
// self는 현재 열거형 값(케이스)을 참조
func simpleDescription() -> String {
// switch 문으로 현재 케이스에 따라 다른 문자열 반환
switch self {
case .Ace: // 같은 타입 내에서는 .Ace와 같이 점 구문으로 간략하게 표현 가능
return "ace"
case .Jack:
return "jack"
case .Queen:
return "queen"
case .King:
return "king"
default:
// 숫자 카드들은 원시값(rawValue)을 문자열로 변환하여 반환
return String(self.rawValue)
}
}
}
// 실험: 두개의 Rank 값의 원본 값을 비교하는 함수를 만들어보자.
// Rank 열거형의 Ace 케이스 인스턴스 생성
let ace = Rank.Ace
// 원시값 접근: rawValue 속성을 통해 열거형 케이스의 원시값에 접근 가능
let aceRawValue = ace.rawValue // 결과: 1
// 실험: 두 개의 Rank 값의 원시값을 비교하는 함수
func compareRanks(_ rank1: Rank, _ rank2: Rank) -> String {
if rank1.rawValue > rank2.rawValue {
return "\(rank1.simpleDescription()) is higher than \(rank2.simpleDescription())"
} else if rank1.rawValue < rank2.rawValue {
return "\(rank1.simpleDescription()) is lower than \(rank2.simpleDescription())"
} else {
return "\(rank1.simpleDescription()) is equal to \(rank2.simpleDescription())"
}
}
// 함수 사용 예시
compareRanks(Rank.King, Rank.Queen) // "king is higher than queen"
compareRanks(Rank.Ace, Rank.Ten) // "ace is lower than 10"
// 원시값(raw value)이 없는 열거형 정의
// 원시값이 필요 없는 경우 타입을 명시하지 않아도 됨
enum Suit {
// 각 케이스(멤버) 정의
// 원시값이 없기 때문에 초기값을 지정하지 않음
case Spades, Hearts, Diamonds, Clubs
// 열거형 내부에 메서드 정의
func simpleDescription() -> String {
// switch 문으로 현재 케이스에 따라 다른 문자열 반환
// 모든 케이스를 처리하므로 default는 필요 없음
switch self {
case .Spades: // 같은 타입 내에서는 .케이스명 형태로 간략하게 참조 가능
return "spades"
case .Hearts:
return "hearts"
case .Diamonds:
return "diamonds"
case .Clubs:
return "clubs"
}
}
// 실험 Suit에 color 메서드를 추가해 보자. color 메서드는 스페이드와 클로버는 'black'을 반환하고 하 트와 다이아몬드는 'red'를 반환하도록 하면 됩니다
// 실험: color 메서드 추가
// 카드 무늬에 따라 색상을 반환하는 메서드
func color() -> String {
switch self {
case .Spades, .Clubs: // 여러 케이스를 함께 처리 가능
return "black"
case .Hearts, .Diamonds:
return "red"
}
}
}
// Suit 열거형의 Hearts 케이스 인스턴스 생성
let hearts = Suit.Hearts
// 메서드 호출
let heartsDescription = hearts.simpleDescription() // 결과: "hearts"
// 추가한 color 메서드 사용 예시
let heartsColor = hearts.color() // 결과: "red"
let spades = Suit.Spades
let spadesColor = spades.color() // 결과: "black"
// 구조체(Struct) 정의
// 구조체는 값 타입(Value Type)으로, 전달될 때 복사됩니다
struct Card {
// 구조체의 저장 속성(Stored Properties)
var rank: Rank // 이전에 정의한 Rank 열거형 타입 사용
var suit: Suit // 이전에 정의한 Suit 열거형 타입 사용
// 구조체 메서드 - 카드 정보를 문자열로 반환
func simpleDescription() -> String {
// 각 속성의 메서드를 호출하여 결합된 문자열 생성
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
}
// 참고: 구조체는 기본 멤버와이즈 이니셜라이저(memberwise initializer)를 자동 제공합니다
// init(rank: Rank, suit: Suit) { ... } 형태의 이니셜라이저가 자동 생성됨
}
// Card 구조체의 인스턴스 생성
// .Three, .Spades와 같이 열거형 타입 내에서 점 구문을 사용하여 간략하게 표현
let threeOfSpades = Card(rank: .Three, suit: .Spades)
// 메서드 호출
let threeOfSpadesDescription = threeOfSpades.simpleDescription() // "The 3 of spades"
// 구조체와 클래스의 차이점:
// 1. 값 타입 vs 참조 타입
// - 구조체(Struct): 값 타입(Value Type) - 변수나 상수에 할당되거나 함수에 전달될 때 값이 복사됨
// - 클래스(Class): 참조 타입(Reference Type) - 객체의 메모리 주소(참조)가 복사되어 여러 변수가 동일 인스턴스 참조 가능
//
// 2. 상속
// - 구조체: 상속을 지원하지 않음
// - 클래스: 다른 클래스를 상속할 수 있음
//
// 3. 초기화
// - 구조체: 모든 속성을 초기화하는 멤버와이즈 이니셜라이저가 자동 제공됨
// - 클래스: 사용자가 직접 이니셜라이저를 구현해야 함
//
// 4. 소멸자(deinit)
// - 구조체: 소멸자를 가질 수 없음
// - 클래스: deinit 소멸자를 통해 메모리 해제 시 작업 수행 가능
//
// 5. 참조 카운팅
// - 구조체: 참조 카운팅 없음
// - 클래스: ARC(Automatic Reference Counting)를 통해 메모리 관리
// 구조체 사용 예시 - 값 복사
var card1 = threeOfSpades
var card2 = card1 // card1의 값이 복사되어 card2에 할당됨
card2.rank = .Ace // card2의 속성만 변경됨
// 이제 card1은 여전히 3 of spades, card2는 ace of spades
// 클래스였다면:
// var card2 = card1 // 참조가 복사됨
// card2.rank = .Ace // 동일한 객체의 속성이 변경되므로 card1.rank도 변경됨
16.Protocol
// 프로토콜(Protocol) 정의
// 프로토콜은 특정 기능이나 요구사항의 청사진(blueprint)을 정의
protocol ExampleProtocol {
// 프로토콜 속성 요구사항
// - 읽기 전용 속성 {get} 명시 (구현 클래스에서는 get만 구현하거나, get과 set 모두 구현 가능)
// - 저장 속성이나 계산 속성으로 구현 가능
var simpleDescription: String { get }
// 프로토콜 메서드 요구사항
// - 메서드 시그니처만 정의, 구현은 프로토콜을 채택한 타입에서 제공
func adjust()
}
// 프로토콜을 채택(adopt)한 클래스
class SimpleClass: ExampleProtocol {
// 프로토콜에서 요구한 속성 구현
// - 읽기 전용으로 선언되었지만 실제 구현에서는 읽기/쓰기 모두 가능한 속성으로 구현
var simpleDescription: String = "A very simple class."
// 프로토콜에 정의되지 않은 추가 속성 - 자유롭게 추가 가능
var anotherProperty: Int = 69105
// 프로토콜에서 요구한 메서드 구현
func adjust() {
simpleDescription += " Now 100% adjusted."
}
}
// 클래스 인스턴스 생성
var a = SimpleClass()
// 메서드 호출
a.adjust()
// 속성 접근
let aDescription = a.simpleDescription
// 출력: "A very simple class. Now 100% adjusted."
print(aDescription)
// Swift 프로토콜과 Java 인터페이스 비교:
// 1. 공통점:
// - 둘 다 타입이 구현해야 할 요구사항의 청사진 역할
// - 다중 채택/구현 가능 (다중 상속과 유사한 기능)
//
// 2. 차이점:
// - Swift 프로토콜: 속성 요구사항을 포함할 수 있음 (읽기 전용 {get} 또는 읽기/쓰기 {get set})
// - Java 인터페이스: 전통적으로 메서드만 포함 (Java 8부터 기본 메서드 지원, Java 9부터 private 메서드 지원)
//
// 3. Swift 프로토콜의 특징:
// - 클래스뿐만 아니라 구조체, 열거형에서도 채택 가능
// - 프로토콜 확장(Protocol Extension)을 통해 기본 구현 제공 가능
// - 프로토콜 합성(Protocol Composition)을 통해 여러 프로토콜의 요구사항 조합 가능
// Java 인터페이스 정의
public interface ExampleInterface {
// 상수 선언 (public static final이 암시적으로 적용됨)
String CONSTANT_VALUE = "This is a constant";
// 추상 메서드 선언 (public abstract가 암시적으로 적용됨)
// 구현부가 없고, 메서드 시그니처만 있음
void adjust();
// Java 8부터 추가된 기능: 기본 메서드(default method)
// 인터페이스 내에서 구현을 제공할 수 있음
default String getDescription() {
return "Default description";
}
// Java 8부터 추가된 기능: 정적 메서드(static method)
static void staticMethod() {
System.out.println("This is a static method in interface");
}
// Java 9부터 추가된 기능: private 메서드
// 인터페이스 내부에서만 사용 가능
private void helperMethod() {
System.out.println("Helper method");
}
}
// 인터페이스를 구현(implements)하는 클래스
public class SimpleClass implements ExampleInterface {
private String description = "A simple class";
// 인터페이스의 추상 메서드 구현 (필수)
@Override
public void adjust() {
description += " - adjusted";
}
// 기본 메서드 오버라이드 (선택 사항)
@Override
public String getDescription() {
return description;
}
// 클래스 자체의 메서드 추가 가능
public void additionalMethod() {
System.out.println("Additional functionality");
}
}
// 사용 예시
public class Main {
public static void main(String[] args) {
// 인터페이스 타입으로 객체 참조 가능
ExampleInterface obj = new SimpleClass();
obj.adjust();
System.out.println(obj.getDescription());
// 정적 메서드 호출
ExampleInterface.staticMethod();
// 상수 접근
System.out.println(ExampleInterface.CONSTANT_VALUE);
}
}
17.Extenion
// 프로토콜 정의 - 문자열 변환 기능을 위한 인터페이스
protocol ToString {
// 객체를 문자열로 변환하는 메서드 요구사항
func toString() -> String
}
// 익스텐션(Extension)을 사용하여 기존 타입(Date)에 프로토콜 구현 추가
extension Date: ToString {
// 타입 속성(static property) - 모든 인스턴스가 공유하는 DateFormatter 인스턴스
static let dateFormatter = DateFormatter()
// ToString 프로토콜의 메서드 구현
func toString() -> String {
// 날짜 형식 설정 (yyyy-MM-dd)
Date.dateFormatter.dateFormat = "yyyy-MM-dd"
// 현재 날짜를 지정된 형식의 문자열로 변환하여 반환
return Date.dateFormatter.string(from: self)
}
}
// Date 인스턴스 생성
let now = Date()
// 확장을 통해 추가된 메서드 호출
print(now.toString()) // 예: "2025-04-14" (오늘 날짜)
// Swift의 익스텐션(Extension) 특징:
// 1. 기존 타입에 새로운 기능 추가
// - 소스 코드 접근 없이도 타입을 확장 가능 (표준 라이브러리 타입 포함)
// - 프로토콜 준수를 위한 구현을 별도로 추가 가능
//
// 2. 추가 가능한 기능:
// - 계산 속성(Computed Properties) - 저장 속성은 추가 불가
// - 메서드(Methods)
// - 이니셜라이저(Initializers)
// - 서브스크립트(Subscripts)
// - 중첩 타입(Nested Types)
// - 프로토콜 준수(Protocol Conformance)
//
// 3. 제한 사항:
// - 기존 기능 재정의(override) 불가
// - 저장 속성(Stored Properties) 추가 불가
// - 타입의 상속 구조 변경 불가
// Optional 변수 예제
// Optional: 값이 있을 수도 있고 없을 수도 있는 상태를 나타내는 타입
// Int("10")은 문자열을 정수로 변환하려는 시도를 나타냄
// 변환 성공 시 값 반환, 실패 시 nil 반환
var anumber = Int("10") // anumber의 타입은 Int? (Optional Int)
// 값은 Optional(10) - 변환 성공
// "10.0"은 정수로 변환할 수 없으므로 nil 반환
var bnumber = Int("10.0") // bnumber의 타입은 Int? (Optional Int)
// 값은 nil - 변환 실패
// Optional 값 출력
print(anumber) // 출력: Optional(10) - 값이 있음을 나타냄
print(bnumber) // 출력: nil - 값이 없음을 나타냄
// 타입 불일치 에러 예제
// var cnumber: Int = Int("10") // 에러! Int? 타입을 Int 타입에 할당 불가
// 해결책: 강제 언래핑(!) 또는 옵셔널 바인딩
// 올바른 해결책들:
var cnumber1: Int = Int("10")! // 강제 언래핑 - 값이 nil이 아님이 확실할 때만 사용
// 또는
if let cnumber2 = Int("10") { // 옵셔널 바인딩 - 안전한 방법
// cnumber2는 여기서 Int 타입
}
// 또는
var cnumber3: Int = Int("10") ?? 0 // nil 병합 연산자 - nil일 경우 기본값 제공
// 주의할 점
// var dnumber: Int = Int("10.0")! // 런타임 에러! nil을 강제 언래핑하면 크래시 발생
// "10.0"은 Int로 변환 불가능하므로 Int("10.0")은 nil
// 안전한 방법:
if let dnumber = Int("10.0") {
// 이 블록은 실행되지 않음 (Int("10.0")이 nil이므로)
print("변환 성공: \(dnumber)")
} else {
print("변환 실패") // 이 부분이 실행됨
}
// 또는 nil 병합 연산자 사용
var dnumber2: Int = Int("10.0") ?? 0 // 변환 실패 시 기본값 0 사용
// 1. 일반 변수/상수: nil을 저장할 수 없음
var anumber: Int = 10 // 일반 변수 - nil 할당 불가, 반드시 값을 가져야 함
// 2. 옵셔널(Optional): 값 또는 nil을 저장할 수 있음
var bnumber: Int? = 20 // 옵셔널 변수 - 값이나 nil 저장 가능
// 3. 암시적 언래핑 옵셔널(Implicitly Unwrapped Optional):
// 초기에는 nil일 수 있지만, 읽기 전에 값이 있어야 함
var cnumber: Int! // 암시적 언래핑 옵셔널
var dnumber: Int! // 암시적 언래핑 옵셔널
// 오류 사례 1: 암시적 언래핑 옵셔널이 초기화되지 않은 상태에서 접근
// anumber = cnumber // 런타임 오류! - cnumber가 아직 nil
// cnumber 초기화 후 접근 - 안전함
cnumber = 20
anumber = cnumber // 정상 작동 - cnumber가 값을 가지고 있음
// 오류 사례 2: 일반 변수에 옵셔널 할당 시도
// anumber = bnumber // 컴파일 오류! - 타입 불일치
// 해결책: 강제 언래핑(!) 또는 옵셔널 바인딩(if let, guard let)
anumber = bnumber! // 강제 언래핑 (bnumber가 nil이 아닐 경우만 안전)
// 또는
if let unwrapped = bnumber {
anumber = unwrapped // 옵셔널 바인딩 (안전한 방법)
}
// 암시적 언래핑 옵셔널에 옵셔널 값 할당 - 허용됨
dnumber = bnumber // 정상 작동 - 둘 다 nil을 저장할 수 있음
// dnumber가 값이 있을 때 접근 - 안전함
anumber = dnumber // 정상 작동 (dnumber가 값을 가지고 있을 때)
// dnumber를 nil로 설정
dnumber = nil
// nil인 암시적 언래핑 옵셔널에 접근 시도 - 위험함!
// anumber = dnumber // 런타임 오류! - dnumber가 nil
// 변수 선언의 세 가지 방식과 접근 방법
// 1. 일반 변수 - nil을 저장할 수 없음, 항상 안전하게 읽을 수 있음
var aint: Int = 10 // 일반 변수
// 2. 암시적 언래핑 옵셔널 - nil을 저장할 수 있지만 접근 시 자동 언래핑
var bint: Int! = 20 // 암시적 언래핑 옵셔널
// 3. 옵셔널 - nil을 저장할 수 있음, 접근 시 명시적 언래핑 필요
var cint: Int? = 30 // 옵셔널
var dint: Int? // 초기값 미지정 = nil과 동일
// 출력 결과
print(aint) // 출력: 10
print(bint) // 출력: Optional(20) - 출력 시에는 옵셔널로 표시됨
print(cint) // 출력: Optional(30)
print(dint) // 출력: nil
// 값 할당
// 암시적 언래핑 옵셔널(Int!)은 일반 변수(Int)에 자동 언래핑되어 할당됨
aint = bint // 문제 없음 - bint가 자동 언래핑됨
// 옵셔널(Int?)은 암시적 언래핑 옵셔널(Int!)에 할당 가능
bint = cint // 문제 없음 - 둘 다 옵셔널이기 때문
// bint가 현재 cint의 값(Optional(30))을 가지므로 안전
aint = bint // 문제 없음 - bint(값: 30)이 자동 언래핑됨
// nil을 암시적 언래핑 옵셔널에 할당
bint = dint // 문제 없음 - bint는 이제 nil
// 여기서 문제 발생!
// aint = bint // 런타임 오류! - nil인 값을 자동 언래핑하려고 시도
// 문법적으로는 오류가 없지만 실행 시 크래시 발생
// Optional 처리 방법 예제
// 문자열을 정수로 변환 시도
let possibleNumber = "123"
// Int(String) 함수는 변환 가능한 경우 정수 값, 불가능한 경우 nil 반환
let convertedNumber = Int(possibleNumber) // convertedNumber의 타입은 Int? (Optional Int)
// 방법 1: nil 체크 후 옵셔널 값 그대로 사용
if convertedNumber != nil {
// convertedNumber는 여기서도 여전히 Optional 타입
print("convertedNumber has an integer value of \(convertedNumber).")
}
// 출력: "convertedNumber has an integer value of Optional(123)."
// 옵셔널 값이 그대로 출력됨 (Optional(123))
// 방법 2: nil 체크 후 강제 언래핑(!) 사용
if convertedNumber != nil {
// ! 연산자로 옵셔널에서 실제 값을 추출 (강제 언래핑)
print("convertedNumber has an integer value of \(convertedNumber!).")
}
// 출력: "convertedNumber has an integer value of 123."
// 실제 정수 값만 출력됨 (123)
if let constantName = someOptional {
// 이 블록 내에서만 constantName 변수가 유효함
// someOptional이 nil이 아닐 경우에만 이 블록이 실행됨
}
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber) // convertedNumber의 데이터 타입은 Int?
if let actualNumber = Int(possibleNumber) {
// 이 블록에서만 actualNumber가 유효함
print("The string \"\(possibleNumber)\" has an integer value of \(actualNumber)")
print("The string \"\(possibleNumber)\" has an integer value of \(Int(possibleNumber))")
} else {
print("The string \"\(possibleNumber)\" couldn't be converted to an integer")
}
// 출력: "The string "123" has an integer value of 123"
// 두 문자열을 정수로 변환하여 비교하는 함수
func comapre2numbers(first: String, second: String) -> String {
// 다중 옵셔널 바인딩: 쉼표로 구분하여 여러 조건을 한 번에 체크
if let firstNumber = Int(first), let secondNumber = Int(second) {
// 모든 조건이 만족될 때만 이 블록이 실행됨
// - first 문자열이 정수로 변환 가능
// - second 문자열이 정수로 변환 가능
// - firstNumber와 secondNumber는 이 블록 내에서만 유효한 지역 상수
// 추가 조건 검사
if firstNumber < secondNumber && secondNumber < 100 {
return "\(firstNumber) < \(secondNumber) < 100"
}
}
// 다음의 경우 "error" 반환:
// - 문자열 변환 실패(Int 변환 불가)
// - 조건 불만족(firstNumber >= secondNumber 또는 secondNumber >= 100)
return "error"
}
// 함수 호출 및 결과 출력
print(comapre2numbers(first: "10", second: "42")) // 출력: "10 < 42 < 100"
// 두 문자열을 정수로 변환하여 비교하는 함수
func comapre2numbers(first: String, second: String) -> String {
// guard 구문을 사용한 다중 옵셔널 바인딩
guard let firstNumber = Int(first), let secondNumber = Int(second) else {
// 변환 실패 시(하나라도 nil이면) 여기서 함수를 종료
return "error"
}
// guard 구문 이후부터는 firstNumber와 secondNumber가 non-optional 상수로 유효함
// 함수의 나머지 부분에서 사용 가능
// 추가 조건 검사
if firstNumber < secondNumber && secondNumber < 100 {
return "\(firstNumber) < \(secondNumber) < 100"
}
// 조건을 만족하지 않는 경우
return "error"
}
// 함수 호출 및 결과 출력
print(comapre2numbers(first: "10", second: "42")) // 출력: "10 < 42 < 100"
// 사람 클래스 - 거주지(residence)는 옵셔널 타입
class Person {
var residence: Residence? // 옵셔널 프로퍼티 - nil일 수 있음
}
// 거주지 클래스
class Residence {
var numberOfRooms = 1 // 방의 개수를 나타내는 일반 프로퍼티
}
// Person 인스턴스 생성
let john = Person() // 기본적으로 residence는 nil로 초기화됨
// 강제 언래핑 시도 - 위험한 접근 방식
// let roomCount1 = john.residence!.numberOfRooms
// 런타임 오류 발생! - residence가 nil이기 때문에 강제 언래핑 시 크래시 발생
// 옵셔널 체이닝 사용 - 안전한 접근 방식
let roomCount2 = john.residence?.numberOfRooms
// residence가 nil이므로 ? 이후의 코드는 실행되지 않고 roomCount2는 nil이 됨
// roomCount2의 타입은 Int? (옵셔널 정수)
// nil 출력
print(roomCount2) // nil
// 주소 클래스
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if let buildingName = buildingName {
return buildingName
} else if let number = buildingNumber {
return number
} else {
return nil
}
}
}
// 거주지 클래스
class Residence {
var rooms: [String: Room] = [:]
var numberOfRooms: Int = 0
var address: Address?
func roomWithName(_ name: String) -> Room? {
return rooms[name]
}
}
// 방 클래스
class Room {
var name: String
var furniture: [String]?
init(name: String) {
self.name = name
}
}
// 사람 클래스
class Person {
var residence: Residence?
}
// 사용 예시
let john = Person()
// 다중 레벨 옵셔널 체이닝
let roomName = john.residence?.roomWithName("Study")?.name
print(roomName) // nil - john.residence가 nil이므로
// residence 인스턴스 생성 및 할당
john.residence = Residence()
// 여전히 nil 반환 - rooms 딕셔너리에 "Study" 키가 없으므로
let studyRoomName = john.residence?.roomWithName("Study")?.name
print(studyRoomName) // nil
// 방 생성 및 추가
let study = Room(name: "Study Room")
john.residence?.rooms["Study"] = study
// 이제 방 이름에 접근 가능
let studyName = john.residence?.roomWithName("Study")?.name
print(studyName) // Optional("Study Room")
// 더 복잡한 체이닝: 가구 접근 시도
let chairName = john.residence?.roomWithName("Study")?.furniture?[0]
print(chairName) // nil - furniture가 nil이므로
// furniture 배열 설정
john.residence?.roomWithName("Study")?.furniture = ["Chair", "Desk", "Lamp"]
// 이제 가구에 접근 가능
let firstFurniture = john.residence?.roomWithName("Study")?.furniture?[0]
print(firstFurniture) // Optional("Chair")
// 주소 접근 시도
let streetName = john.residence?.address?.street
print(streetName) // nil - address가 nil이므로
// 주소 설정
john.residence?.address = Address()
john.residence?.address?.street = "123 Swift Street"
// 이제 주소에 접근 가능
let street = john.residence?.address?.street
print(street) // Optional("123 Swift Street")
// 메서드 호출을 포함한 체이닝
let buildingIdentifier = john.residence?.address?.buildingIdentifier()
print(buildingIdentifier) // nil (buildingName과 buildingNumber가 모두 nil이므로)
// buildingName 설정
john.residence?.address?.buildingName = "Swift Tower"
// 이제 buildingIdentifier 접근 가능
let buildingID = john.residence?.address?.buildingIdentifier()
print(buildingID) // Optional("Swift Tower")
// 기본 미디어 아이템 클래스
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}
// MediaItem을 상속한 영화 클래스
class Movie: MediaItem {
var director: String
init(name: String, director: String) {
self.director = director
super.init(name: name)
}
}
// MediaItem을 상속한 노래 클래스
class Song: MediaItem {
var artist: String
init(name: String, artist: String) {
self.artist = artist
super.init(name: name)
}
}
// 다양한 MediaItem 객체를 포함하는 배열 (다형성)
let library = [
Movie(name: "Casablanca", director: "Michael Curtiz"),
Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
Movie(name: "Citizen Kane", director: "Orson Welles"),
Song(name: "The One And Only", artist: "Chesney Hawkes"),
Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]
// library 배열의 타입은 [MediaItem]으로 추론됨
// 모든 요소가 MediaItem의 하위 클래스이므로 공통 상위 타입인 MediaItem으로 처리됨
// 미디어 라이브러리에서 영화와 노래의 개수를 세는 코드
var movieCount = 0 // 영화 카운터 초기화
var songCount = 0 // 노래 카운터 초기화
// 라이브러리의 각 항목을 반복하며 타입 확인
for item in library {
// is 연산자를 사용하여 타입 확인
if item is Movie {
// item이 Movie 타입이면 movieCount 증가
movieCount += 1
} else if item is Song {
// item이 Song 타입이면 songCount 증가
songCount += 1
}
}
// 결과 출력
print("Media library contains \(movieCount) movies and \(songCount) songs")
// 출력: "Media library contains 2 movies and 3 songs"
// 미디어 라이브러리를 순회하며 각 항목의 구체적인 타입으로 다운캐스팅
for item in library {
// 조건부 다운캐스팅(as?)을 사용하여 Movie 타입으로 변환 시도
if let movie = item as? Movie {
// 다운캐스팅 성공 시 Movie 타입의 특정 속성(director)에 접근
print("Movie: \(movie.name), dir. \(movie.director)")
}
// 조건부 다운캐스팅(as?)을 사용하여 Song 타입으로 변환 시도
else if let song = item as? Song {
// 다운캐스팅 성공 시 Song 타입의 특정 속성(artist)에 접근
print("Song: \(song.name), by \(song.artist)")
}
}
// 출력:
// Movie: Casablanca, dir. Michael Curtiz
// Song: Blue Suede Shoes, by Elvis Presley
// Movie: Citizen Kane, dir. Orson Welles
// Song: The One And Only, by Chesney Hawkes
// Song: Never Gonna Give You Up, by Rick Astley
– as?, as!
// 강제 다운캐스팅(as!) - 확신이 있을 때 사용
let one = library[0] as! Movie
// library[0]가 Movie 타입임이 확실하므로 성공적으로 다운캐스팅됨
// one은 Movie 타입 (non-optional)
// 조건부 다운캐스팅(as?) - 안전한 방식
let two = library[0] as? Movie
// library[0]가 Movie 타입이므로 성공적으로 다운캐스팅됨
// two는 Movie? 타입 (optional)
// 조건부 다운캐스팅이 실패하는 경우
let four = library[1] as? Movie
// library[1]은 Song 타입이므로 Movie로 다운캐스팅 실패
// four에는 nil이 할당됨 (타입은 Movie?)
// 강제 다운캐스팅이 실패하는 경우
// let five = library[1] as! Movie
// library[1]은 Song 타입이므로 Movie로 다운캐스팅 실패
// 런타임 오류 발생! 앱이 충돌함
func canThrowErrors() throws -> String
// throws 키워드는 이 함수가 에러를 던질 수 있음을 명시
// 에러를 던지는 함수를 호출할 때는 반드시 try를 사용해야 함
func cannotThrowErrors() -> String
// 일반 함수는 try 없이 호출 가능
do {
try expression
// 성공 시 실행
} catch pattern1 {
// 특정 에러 처리
} catch {
// 기타 에러 처리
}
let result = try? functionThatThrows()
// result는 옵셔널 타입, 에러 발생 시 nil
let result = try! functionThatThrows()
// 에러 발생 시 런타임 오류(앱 충돌)
ex 1 : 에러 정의 및 자판기 클래스 구현
// 자판기 오류 타입 정의
enum VendingMachineError: Error {
case invalidSelection // 유효하지 않은 선택
case insufficientFunds(coinsNeeded: Int) // 잔액 부족(필요한 금액 포함)
case outOfStock // 재고 부족
}
// 상품 구조체
struct Item {
var price: Int
var count: Int
}
// 자판기 클래스
class VendingMachine {
// 재고 목록
var inventory = [
"Candy Bar": Item(price: 12, count: 7),
"Chips": Item(price: 10, count: 4),
"Pretzels": Item(price: 7, count: 11)
]
var coinsDeposited = 0
// 상품 판매 메서드(에러를 던질 수 있음)
func vend(itemNamed name: String) throws {
// 상품이 존재하는지 확인
guard let item = inventory[name] else {
throw VendingMachineError.invalidSelection
}
// 재고가 있는지 확인
guard item.count > 0 else {
throw VendingMachineError.outOfStock
}
// 충분한 금액이 있는지 확인
guard item.price <= coinsDeposited else {
throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
}
// 모든 조건 충족 시 상품 제공
coinsDeposited -= item.price
var newItem = item
newItem.count -= 1
inventory[name] = newItem
print("Dispensing \(name)")
}
}
ex 2 : 에러 처리 방법 예제
// 자판기 인스턴스 생성 및 초기 금액 설정
var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 32
// 방법 1: do-catch로 에러 처리
do {
try vendingMachine.vend(itemNamed: "Chips")
} catch VendingMachineError.invalidSelection {
print("Invalid Selection.")
} catch VendingMachineError.outOfStock {
print("Out of Stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
} catch {
print("Unexpected error: \(error)")
}
// 출력: Dispensing Chips, coinsDeposited: 22
// 방법 2: try? - 옵셔널 반환(에러 시 nil)
let x = try? vendingMachine.vend(itemNamed: "Chips")
print(x) // Dispensing Chips, coinsDeposited: 12, 출력: ()
// 방법 3: try! - 강제 실행(에러 없음 확신)
let y = try! vendingMachine.vend(itemNamed: "Candy Bar")
print(y) // Dispensing Candy Bar, coinsDeposited: 0, 출력: ()
// coinsDeposited가 0이므로 돈 부족
let z = try? vendingMachine.vend(itemNamed: "Pretzels")
print(z) // nil (돈 부족으로 에러 발생, 프로그램 계속 실행)
// 에러 발생 확신 시 try! 사용 위험
// 다음 코드는 런타임 오류 발생 (금액 부족 오류)
// let w = try! vendingMachine.vend(itemNamed: "Pretzels")