Swift
데이터 타입
•
Int: 정수인 숫자를 표현하는 데이터 타입. -2,147,483,648 ~ 2,147,483,647 사이 숫자를 표현합니다.
•
Float: 부동 소수점 숫자를 표현하는 데이터 타입. 32비트 부동 소수(소수점 이하 6자리까지) 표현 가능합니다.
•
Double: 부동 소수점 숫자를 표현하는 데이터 타입. 64비트 부동소수(소수점 이하 15자리 이상) 표현 가능합니다.
•
Bool: 참(true)와 거짓(false) 을 표현할 수 있는 데이터 타입.
•
String: 문자열을 표현하는 데이터 타입. 텍스트를 표현할 수 있습니다.
•
Character: 단일 문자를 표현하는 데이터 타입.
•
Any: 다양한 데이터 타입의 값을 수용할 수 있는 데이터 타입. 반드시 형 변환을 하여 사용해야합니다.
•
Tuple: 여러 데이터의 묶음. 다양한 데이터 타입을 가진 값들을 묶을 수 있습니다.
var age: Int = 18 // Int 타입
var interestRate: Float = 0.123456 // Float 타입
var interestRate: Double = 0.123456789101234 // Double 타입
var isOpen: Bool = true // Bool 타입
var isLogged: Bool = false
var name: String = "David" // String 타입
var characterA: Character = "A" // Character 타입
var anyValue: Any = 1000 // Any 타입
var http404Error: (Int, String) = (404, "Not Found") // Tuple 튜플
Swift
복사
컬렉션(Collection)
•
Array: 동일한 타입의 요소들을 저장하는 순서가 있는 컬렉션. 특정 요소의 인덱스를 사용하여 접근할 수 있고 수정할 수 있으며 배열의 크기는 동적으로 조절됩니다.
•
Dictionary: 순서를 정의하지 않고 같은 타입의 key와 같은 타입의 value를 저장하는 컬렉션. key는 고유(unique)해야하므로 중복을 허용하지 않으며, 실물 사전을 찾는 것처럼 순서가 아닌 식별자 기준으로 값을 찾을 때 Dictionary를 사용합니다.
•
Set: 순서를 정의하지 않고 동일한 타입의 값을 저장하는 컬렉션. 세트 안에 있는 모든 값은 고유(unique)해야하므로 중복을 허용하지 않습니다. 항목의 순서가 중요하지 않거나 항목이 한 번만 표시되도록 해야 하는 경우 사용합니다.
// 자주 사용하는 메서드
// Array
var numbers: [Int] = [1, 2, 3]
let count: Int = numbers.count // 배열 갯수 확인 : 3
let isEmpty: Bool = numbers.isEmpty // 배열 비었는지 확인 : false
numbers[0] // 1
numbers[0...1] // [1, 2]
numbers.first // Optional(1)
numbers.last // Optional(3)
numbers.append(4) // [1, 2, 3, 4]
numbers.append(contentsOf: [5, 6, 7]) // [1, 2, 3, 4, 5, 6, 7]
numbers.insert(0, at: 0) // [0, 1, 2, 3, 4, 5, 6, 7]
numbers.insert(contentsOf: [10, 100], at: 2) // [0, 1, 10, 100, 2, 3, 4, 5, 6, 7]
numbers.remove(at: 2) // [0, 1, 100, 2, 3, 4, 5, 6, 7]
numbers.removeFirst() // [1, 100, 2, 3, 4, 5, 6, 7]
numbers.removeLast() // [1, 100, 2, 3, 4, 5, 6]
numbers.popLast() // [1, 100, 2, 3, 4, 5]
numbers.removeSubrange(1...3) // [1, 4, 5]
numbers.removeAll() // []
// Array - 비교
var array1 = [1, 2, 3]
var array2 = [1, 2, 3]
var array3 = [1, 2, 3, 4, 5]
array1 == array2 //true
array1.elementsEqual(array3) //false
// Array - 정렬
let unsorted = [1, 5, 3, 8, 6, 10, 14]
unsorted.sort() // unsorted 배열을 오름차순 정렬 [1, 3, 5, 6, 8, 10, 14]
unsorted.sort(by: >) // unsorted 배열을 내림차순 정렬 [14, 10, 8, 6, 5, 3, 1]
let sortedArray = unsorted.sorted() // unsorted 배열을 오름차순 정렬하여 반환 [1, 3, 5, 6, 8, 10, 14]
let sortedArray2 = unsorted.sorted(by: >) // unsorted 배열을 내림차순 정렬하여 반환 [14, 10, 8, 6, 5, 3, 1]
// Dictionary
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
airports.keys // ["YYZ", "DUB"]
airports.values // ["Toronto Pearson", "Dublin"]
airports.keys.sorted() // ["DUB", "YYZ"]
airports.values.sorted() // ["Dublin", "Toronto Pearson"]
airports["APL"] = "Apple International" // airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin", "APL": "Apple International"]
airports["APL"] = nil // key에 매칭된 value 값 초기화
print(airports.count) // airports.count는 2
print(airports.keys) // 딕셔너리 airports에 있는 모든 key들. 딕셔너리 airports에 있는 모든 key들
let newYyz = airports.updateValue("Hello YYZ", forKey: "YYZ") // 해당 key가 있다면 value를 덮어쓰고, 덮어쓰기 전 기존값울 반환
print(newYyz) // 출력값: Optional("Toronto Pearson")
print(airports["YYZ"]) // 출력값: Optional("Hello YYZ")
let newApl = airports.updateValue("Hello APL", forKey: "APL") // 해당 key가 없다면 그 key에 해당하는 value에 값을 추가하고 nil을 반환
print(newApl) // 출력값: nil
print(airports["APL"]) // 출력값: Optional("Hello APL")
// Set
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
favoriteGenres.isEmpty // false
favoriteGenres.count // 3
favoriteGenres.contains("Rock") // true
favoriteGenres.randomElement() // Hip hop (다른 element가 나올 수 있음)
favoriteGenres.remove("Rock") // "Rock" -> 삭제된 요소를 리턴
favoriteGenres // ["Classical", "Hip hop"]
favoriteGenres.remove(5) // nil -> 존재하지 않는 요소를 삭제했을 때 에러는 발생하지 않고 nil 리턴
favoriteGenres.removeAll() // []
let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
// Set - 합집합
oddDigits.union(evenDigits).sorted() // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
// Set - 교집합
oddDigits.intersection(evenDigits).sorted() // []
// Set - 차집합
oddDigits.subtracting(singleDigitPrimeNumbers).sorted() // [1, 9]
// Set - 대칭 차집합
oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted() // [1, 2, 9]
Swift
복사
프로퍼티 - 저장 프로퍼티 (Stored Property)
•
let: 변경할 수 없는 상수(Constant) 선언시 사용합니다.
•
var: 변경할 수 있는 변수(Variable) 선언시 사용합니다.
let name = "철수"
name = "영희" // 🚨Error
var age = 10
age = 50 // ✅
Swift
복사
프로퍼티 - 연산 프로퍼티 (Computed Property)
•
그 때 그 때 값을 계산하여 반환하는 프로퍼티
•
매번 계산하여 값을 알려주기 때문에 var를 사용하여 선언하고 let 사용은 불가능합니다.
•
매번 계산하기 때문에 값을 저장하지 않습니다.
var x = 10
var y = 20
var sum: Int { // sum이 연산 프로퍼티
get {
return x + y
}
set {
sum = x + y
}
}
print(sum) // 출력값: 30
// get, set 중 get만 필요한 경우에는 키워드 생략 가능
var sum1: Int {
return x + y
}
// 아래와 같이 더 축약 가능
var sum2: Int {
x + y // return 구문만 있을 경우 return 키워드 생략 가능
}
Swift
복사
프로퍼티 옵저버
•
didSet: 프로퍼티를 관찰(observe)하면서 새 값이 저장된 직후에 호출되며 이전 프로퍼티의 값을 oldValue로 제공됩니다.
•
willSet: 프로퍼티를 관찰하면서 값이 새 값이 저장되기 직전에 호출되며 새로운 프로퍼티의 값을 newValue로 제공합니다.
var myProperty: Int = 20{
didSet(oldValue){
print(oldValue)
}
willSet(newValue){
print(newValue)
}
}
var name: String = "Unknown" {
willSet {
print("현재 이름 = \(name), 바뀔 이름 = \(newValue)")
}
didSet {
print("현재 이름 = \(name), 바뀌기 전 이름 = \(oldValue)")
}
}
name = "Peter"
// willSet이 먼저 실행
// 현재 이름 = Unknown, 바뀔 이름 = Peter
// 현재 이름 = Peter, 바뀌기 전 이름 = Unknown
Swift
복사
접근 제한자
•
다른 소스 파일이나 모듈의 코드에서 코드 일부에 대한 접근을 제한하는 키워드
•
[제약이 적음] open < public < internal < fileprivate < private [제약이 많음]
•
private(set)으로 프로퍼티를 외부에서 수정 불가능한 읽기 전용 모드를 만들 수 있습니다.
•
접근 제한자를 작성하지 않으면 기본적으로 internal로 판단합니다.
•
상위 요소보다 하위 요소가 더 높은 접근 수준을 가질 수 없습니다.
함수
•
특정 작업을 수행하는 코드 덩어리
•
기본적으로 함수 이름, 매개 변수 (Parameter), 리턴 타입 (Return Type) 등을 사용하여 정의하고 네이밍 컨벤션은 카멜 케이스를 사용합니다.
func 함수_이름(아규먼트_레이블: 파라미터_타입) -> 리턴_타입 {
// ... 코드
}
// 다양한 함수 표현법
func sayHi(friend: String) {
print("Hi~~ \(friend)!")
}
sayHi(friend: "영호") // 출력값: Hi~~ 영호!
func sayHi(to friend: String) {
print("Hi~! \(friend)!")
}
sayHi(to: "영호") // 출력값: Hi~! 영호!
func sayHi(_ friend: String) -> String {
return ("Hi~ :) \(friend)!")
}
print(sayHi("영호")) // 출력값: Hi~ :) 영호!
Swift
복사
연산자
•
산술 연산자: 덧셈, 뺄셈, 곱셈, 나눗셈, 나머지 연산한 결과값을 반환하는 연산자
•
비교 연산자: 비교한 값을 true와 false로 반환하는 연산자
•
논리 연산자: 논리적으로 비교한 값을 true와 false로 반환하는 연산자
•
범위 연산자: 범위를 나타내는 연산자
•
삼항 연산자: question ? answer1 : answer2 형식으로, question의 답이 true면 answer1을 false면 answer2 값을 사용하는 연산자.
// 산술 연산자
var result = 1 + 2 // result는 3
result += 5 // result = result + 5와 같음. 따라서 result는 8
result = 10 - 6 // result는 4
result -= 3 // result = result - 3와 같음. 따라서 result는 1
result = 8 * 2 // result는 16
result = 12 / 5 // result는 2
result = 10 % 3 // result는 10을 3으로 나눈 후 나머지 이므로 1
// 비교 연산자
var result = (1 == 2) // result는 false
result = (1 != 2) // result는 true
result = (1 > 2) // result는 false
result = (1 < 2) // result는 true
result = (1 >= 2) // result는 false
result = (2 <= 2) // result는 true
// 논리 연산자
var allowedEntry = false
allowedEntry = !allowedEntry // allowedEntry는 true
let enteredDoorCode = true
let passedRetinaScan = false
let permittedAccess = enteredDoorCode && passedRetinaScan // permittedAccess는 false
let enter = allowedEntry || permittedAccess // enter는 true
// 범위 연산자
(1...5) // 1, 2, 3, 4, 5
(1..<5) // 1, 2, 3, 4
(3...) // 3, 4, 5, 6, 7 ...
// 삼항 연산자
let height = 150
var nickname = (height > 185) ? "Daddy Long Legs" : "TomTom" // nickname은 TomTom
Swift
복사
조건문
•
if 문: 조건을 확인하는 문법으로 if 나 else if 에 작성한 조건이 true인 경우에만 구현부 코드를 실행하는 조건문. 작성한 조건이 모두 false일 경우 else 부분의 코드가 실행됩니다.
•
switch 문: 가능한 여러 개의 일치하는 case와 값을 비교하며 일치하는 첫 번째 case를 기반으로 구현부 코드 블록을 실행하는 조건문. 모든 케이스가 적용되지 않는 경우 default 에 구현된 코드가 실행하며 특정 케이스에 실행 구문이 없을 경우 break 키워드를 반드시 사용해야 합니다.
// if 문
ar temperature = 17
if temperature <= 13 {
print("쌀쌀한 날씨가 지속되겠습니다.")
} else if temperature >= 22 {
print("해가 떠오르는 낮부터는 더위 예상됩니다.")
} else {
print("밤낮으로 선선한 날씨가 예상됩니다.")
}
// 출력값: 밤낮으로 선선한 날씨가 예상됩니다.
// switch 문
let cookieCount = 62
let message: String
switch cookieCount {
case 0:
message = "🍪 없음 🙅♂️"
case 1..<5:
message = "🍪 아주 조금 있음"
case 5..<12:
message = "🍪 조금 있음"
case 12..<100:
message = "🍪 꽤 있음 🍪"
case 100..<1000:
message = "🍪🍪 많음 🍪🍪"
default:
message = "🍪🍪🍪엄청 많음🍪🍪🍪"
}
print(message)
// 출력값: "🍪 꽤 있음 🍪"
Swift
복사
반복문
•
For-In문: 순회할 수 있는 타입(배열, 딕셔너리 등)을 순회하거나 특정 횟수만큼 로직을 반복할 때 주로 사용하는 반복문
•
While문: 특정 조건이 만족하는 동안 내부로직을 계속해서 실행하는 반복문
// For-In 문
let students = ["Tom": 2, "Harry": 4, "Sarah": 1]
for (name, grade) in students {
print("\(name) 은 \(grade) 학년이야")
}
// 출력값:
// Tom 은 2 학년이야
// Harry 은 4 학년이야
// Sarah 은 1 학년이야
// While 문
let lastName : [String] = ["송", "김", "박", "정" ]
var index : Int = 0
while index < 4 {
print("옆집 \(lastName[index]) 씨네 \(index)번째 결혼식")
index += 1
}
// 출력값:
// 옆집 송 씨네 0번째 결혼식
// 옆집 김 씨네 1번째 결혼식
// 옆집 박 씨네 2번째 결혼식
// 옆집 정 씨네 3번째 결혼식
Swift
복사
옵셔널
•
값이 nil일 가능성이 있는 상황에서 옵셔널(Optional)을 사용합니다.
•
옵셔널 타입끼리의 연산은 불가능합니다.
•
옵셔널 바인딩: if let/var 나 guard let/var를 사용하여 변수의 값이 nil인지 아닌지 검사한 후, nil이 아닌경우(값이 존재하는 경우) 그 값을 다른 변수에 대입시켜 바인딩을 할 수 있습니다.
•
강제 언래핑: !로 옵셔널 타입의 값을 강제로 언래핑하는 것보다 옵셔널 바인딩을 사용하는 것이 훨씬 안전합니다.
•
??로 값이 nil일 경우를 위해 기본값을 설정할 수 있습니다(nil-coalescing)
•
옵셔널 체이닝: 옵셔널을 연쇄적으로 사용하는 것을 의미하며, . 을 통해 내부 프로퍼티나 메서드에 연속적으로 접근할 때 사용합니다.
let optionalString1: String? = "Hello, "
let optionalString2: String? = "world!"
// 옵셔널 String 값들을 연결하려는 시도
let result = optionalString1 + optionalString2 // 🚨Error
// 강제 언래핑
let number = Int("42")! // ✅ number는 42
let address: String? = nil
print(address!) // 🚨 Error
// 옵셔널 바인딩 - if let/var
let boyName : String?
let girlName : String?
boyName = "하늘"
girlName = "나연"
if let boy = boyName,
var girl = girlName { // boy, girl 변수는 if let {} 범위 안에서 사용 가능
girl = "수지" // var로 선언한 경우 범위 안에서 값 변경 가능
}
// 옵셔널 바인딩 - guard let/var
let x : Int? = 10
let y : Int? = nil
func optionalBinding() {
guard let x = x else { return }
print(x)
guard let y = y else { return } // y는 nil 이므로 여기서 return
print(y) // 위에서 return 하였기 때문에 이 코드 라인은 실행되지 않음
}
optionalBinding() // 출력값: 10
// 기본값 설정
var optNumber: Int? = 3
let number = optNumber ?? 5 // number는 3이고 Int? 타입이 아닌 Int 타입
// 옵셔널 체이닝
struct Person {
var name: String
var address: Address
}
struct Address {
var city: String
var street: String
var detail: String
}
let samAddress = Address(city: "서울", street: "신논현로", detail: "100")
let sam: Person? = Person(name: "Sam", address: samAddress)
print(sam.address.city) // 🚨 Error.
print(sam?.address.city) // ✅ 출력값: 서울
Swift
복사
클로저
•
이름없는 함수 즉, 코드 블록을 의미
•
클로저는 일반적으로 기능을 저장하기 위해 사용합니다.
•
클래스와 마찬가지로 참조 타입(Reference type)입니다.
// 클로저
{ (parameters) -> return type in
// 구현 코드
}
// 클로저 예시 - 1
let closure1 = { (param: String) -> String in
return param + "!"
}
print(closure1("스티브")) // 출력값: 스티브!
// 클로저 예시 - 2
func closureCaseFunction(a: Int, b: Int, closure: (Int) -> Void) {
let c = a + b
closure(c)
}
closureCaseFunction(a: 1, b: 2) {(number) in
print("result : \(number)") // result : 3
}
// 클로저 예시 - 3
func performClosure(param: (String) -> Int) {
param("Swift")
}
performClosure(param: { str in
return str.count
})
performClosure(param: {
$0.count // 코드가 한 줄인 경우 return 생략 가능
})
// 탈출 클로저
Swift
복사
탈출 클로저
•
@escaping 키워드를 붙인 클로저로 함수의 실행이 종료된 후에 함수 밖에서 실행시키는 코드 블록.
•
탈출 클로저를 활용하여 기존에 있던 함수 범위 내부의 자원들을 활용해서 비동기적 작업을 할 수 있습니다.
•
@escaping 를 사용하는 클로저에서 self의 요소를 사용할 경우, self를 명시적으로 언급해야 합니다.
class Tutor {
var name = "iOS튜터"
func asyncEscaping(closure: @escaping (String) -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
closure(self.name)
}
}
}
let tutor = Tutor()
tutor.asyncEscaping { str in
print("name : \(str)") // 출력값: name : iOS 튜터
}
Swift
복사
고차함수
•
map: 컬렉션 내부의 기존 데이터를 변형(transform)하여 새로운 컬렉션을 생성하는 함수
•
filter: 기존 컨테이너의 요소 중 조건에 만족하는 값에 대한 새로운 컨테이너를 만들어 반환하는 함수
•
reduce: 기존 컨테이너에서 내부의 값들을 결합하여 새로운 컨테이너를 만들어 반환하는 함수
// map
let stringArray = ["1", "2", "3", "4", "5"]
numberArray = stringArray.map {
if let changeToInt = Int($0) {
return changeToInt
}
return 0
}
// numberArray는 [1,2,3,4,5]
// filter
let numbers1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
let evenNumbers2 = numbers1.filter { $0 % 2 == 0 } // evenNumbers2는 [2, 4, 6, 8]
// reduce
let numbers3 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let sum1 = numbers3.reduce(0, +) // sum1은 55
Swift
복사
클래스
•
프로퍼티에 값을 저장하거나 메서드를 통해 기능을 제공하 이걸 하나로 프로퍼티나 메서드를 하나로 캡슐화하여 값을 저장하거나 기능을 제공하는 사용자 정의 타입.
•
이니셜라이저(Initializer)를 통해 초기값을 설정할 수 있습니다.
•
다른 클래스에 상속(inheritance)을 할 수도 받을 수도 있습니다.
•
클래스는 참조 타입(Reference Type)으로 변수나 상수에 할당될 때에는 값을 복사하는 것이 아니라 참조(주소)가 복사되어 같은 인스턴스를 가리키게 됩니다. 따라서 동일한 인스턴스를 공유하면 한 쪽에서 값을 변경하면 다른 쪽에서도 영향을 받게 됩니다.
•
override: 부모 클래스에서 상속받은 메서드, 속성 또는 서브스크립트를 자식 클래스에서 재정의할 때 사용하는 키워드
•
super: 자식 클래스에서 부모 클래스의 메서드, 속성 또는 초기화 메서드를 호출할 때 사용하는 키워드
•
final: 클래스, 메서드, 속성 또는 서브스크립트를 표시하여 상속이 불가능하도록 만드는 키워드
class Person {
var name: String
init(name: String) { // 이니셜라이저를 이용하여 초기값 설정
self.name = name
}
func introduce() {
print("my name is \(name)")
}
}
var person1 = Person(name: "Alice") // Person 클래스를 인스턴스화 시킨 것
var person2 = person1 // 참조 복사
person2.name = "Bob"
person1.introduce() // my name is Bob
person2.introduce() // my name is Bob
// 상속
// Student 클래스가 Person 클래스를
class Student: Person {
var studentID: Int
init(name: String, age: Int, studentID: Int) {
self.studentID = studentID
super.init(name: name, age: age)
}
func study() {
print("\(name) is studying.")
}
}
let john = Student(name: "John", age: 20, studentID: 123)
john.introduce() // 출력: my name is John
john.study() // 출력: John is studying.
// override, super
class Animal {
func makeSound() {
print("Some generic sound")
}
}
class Dog: Animal {
override func makeSound() {
super.makeSound() // 부모 클래스 Animal의 메서드 호출
print("Bark!")
}
}
let dog = Dog()
dog.makeSound()
// 출력값:
// Some generic sound
// Bark!
// final
final class Vehicle {
final var wheels: Int = 0
final func makeSound() {
print("Some generic sound")
}
}
Swift
복사
구조체
•
클래스와 마찬가지로 프로퍼티에 값을 저장하거나 메서드를 통해 기능을 제공하고 이걸 하나로 캡슐화할 수 있는 사용자 정의 타입.
•
생성자(initializer)를 정의하지 않으면 구조체가 자동으로 생성자(Memberwise Initializer.)를 제공합니다.
•
값 타입(Value Type)으로 변수나 상수에 할당될 때 값의 복사본이 생성되는 타입입니다.
•
클래스와 달리 상속을 할 수 없습니다.
// 값 타입인 구조체 예시
struct Point {
var x: Int
var y: Int
}
var point1 = Point(x: 5, y: 10)
var point2 = point1 // 값 복사
point2.x = 15
print(point1) // 출력: Point(x: 5, y: 10)
print(point2) // 출력: Point(x: 15, y: 10)
Swift
복사
열거형
•
관련된 값으로 이뤄진 그룹을 같은 타입으로 선언해 타입 안전성(type-safety)을 보장하는 방법
•
구조체와 같이 값 타입(Value Type)입니다.
// 자주 사용하는 활용
enum CompassDirection: CaseIterable { // CaseIterable 프로토콜 채택시 allCases 사용 가능
case north(latitude: String, longitude: String) // 연관 값
case south
case east
case west
}
var direction: CompassDirection = .south
var anotherDirection = direction // 값 복사
direction = .east // 값을 변경해도 anotherDirection에는 영향이 없음
print(direction) // 출력: east
print(anotherDirection) // 출력: south
let north = CompassDirection.north(latitude: "123456", longitude: "789123")
func processTrade(direction: CompassDirection) {
switch direction {
case .north(let latitude, let longitude):
print("북쪽 방향의 위도 \(latitude) 경도 \(longitude).")
case .south
print("남쪽 입니다.")
case .east:
print("동쪽입니다")
case .west:
print("서쪽입니다")
}
}
let allDirections = CompassDirection.allCases.count
print("\(allDirections)개의 방향이 있습니다.")
// 출력값: 4개의 방향이 있습니다.
Swift
복사
프로토콜
•
특정 역할을 하기 위한 메소드, 프로퍼티, 기타 요구사항 등을 정의 해놓은 규약 혹은 약속
•
class, structure, enum이 프로토콜을 ‘채택’하고 모든 요구사항을 충족하면 프로토콜을 준수했다고 합니다.
•
프로토콜은 설계된 조건만 정의를 하고 제시를 할 뿐 스스로 기능을 구현하지 않습니다.
// 예시
protocol Student {
var studentId: Int { get set }
var name: String { get }
func printInfo() -> String
}
struct UnderGraduateStudent: Student {
var studentId: Int
var name: String
var major: String
func printInfo() -> String {
return "\(name), whose student id is \(studentId), is major in \(major)"
}
}
struct GraduateStudent: Student {
var studentId: Int
var name: String
var degree: String
var labNumber: Int
func printInfo() -> String {
return "\(name), member of lab no.\(labNumber), has a \(degree) degree"
}
}
// 프로토콜은 타입으로서도 사용가능
let underGraduate: Student = UnderGraduateStudent(studentId: 1, name: "홍길동", major: "computer")
let graduate: Student = GraduateStudent(studentId: 2, name: "김철수", degree: "master", labNumber: 104)
let studentArray: [Student] = [underGraduate, graduate]
Swift
복사
타입 캐스팅
•
인스턴스의 타입을 확인 하거나, 해당 인스턴스를 슈퍼 클래스(부모 클래스)나 하위 클래스(자식 클래스)로 취급하는 방법
•
is: is 연산자는 타입을 체크하는 연산자로, 비교 결과를 bool 타입을 반환합니다.(타입 체킹)
•
as: 캐스팅하려는 타입이 같은 타입 이거나 수퍼클래스 타입이라는 것을 알 때 사용하며 컴파일 단계에서 캐스팅이 실행됩니다. 캐스팅에 실패할 경우 에러가 발생하여 항상 타입 캐스팅이 성공할 경우에만 사용할 수 있습니다.
•
as?: 성공하면 옵셔널 타입의 인스턴스를 반환하고 실패하면 nil 을 반환하며 런타임에 캐스팅이 실행됩니다. 실패할 가능성이 있으면 as?를 사용하는 것이 좋습니다.
•
as1: 캐스팅에 성공한 경우 인스턴스를 반환하며 런타임에 특정 타입으로 강제 캐스팅합니다. 강제 타입 캐스팅에 실패할 경우 런타임 에러가 발생할 수 있습니다.
// is
let char: Character = "A"
print(char is Character) // 출력값: true
print(char is String) // 출력값: false
// 타입 캐스팅
class Person {
var id = 0
var name = "name"
var email = "hgk@gmail.com"
}
class Worker: Person {
var salary = 300
}
class Programmer: Worker {
var lang = "Swift"
}
// 업캐스팅 - as
let person1 = Person()
let worker1 = Worker()
let programmer1 = Programmer()
let personList = [person1, worker1, programmer1] // 타입을 선언하지 않았지만 Person 타입으로 인식 -> 즉 업캐스팅이 되었음
personList[1].name
//personList[1].salary // Person 타입으로 보고 있기 때문에 salary에 접근하지 못함
let worker2 = Worker()
worker2.salary
let workerPerson = worker2 as Person
//workerPerson.salary // Person 타입으로 보고 있기 때문에 salary에 접근하지 못함
// 다운캐스팅 - as? / as!
// as?
let pro = programmer1 as? Programmer // 타입 변환이 될 수도 있고 안될 수도 있기 때문에 옵셔널을 리턴
if let person2 = programmer1 as? Programmer {
person2.lang
}
if let person3 = worker1 as? Programmer {
person3.lang
}
// as!
let pro2 = worker2 as! Programmer // 🚨Error : 타입 변환 실패시 오류
Swift
복사
제네릭(Generic)
•
다양한 타입에서 작동하도록 일반화된 코드를 작성할 수 있게 해주는 타입
•
실제 타입 이름을 써주는 대신에 placeholder를 사용합니다. [ eg: T, V, U ]
•
placeholder의 실제 타입은 함수가 호출되는 순간 결정됩니다.
struct Queue<T> {
private var queue: [T] = []
public var count: Int {
return queue.count
}
public var isEmpty: Bool {
return queue.isEmpty
}
public mutating func enqueue(_ element: T) {
queue.append(element)
}
public mutating func dequeue() -> T? {
return isEmpty ? nil : queue.removeFirst()
}
}
var queue = Queue<Int>()
queue.enqueue(10)
queue.enqueue(20)
queue.dequeue() // 10
Swift
복사
Extension
•
structure, class, enum, protocol 타입에 새로운 기능을 추가할 수 있는 문법으로, 해당 타입의 소스코드에 접근하지 않고 기능을 확장 가능할 수 있습니다.
•
Extension으로 구현 가능한 것: 새로운 계산된 속성(Computed Property) 추가, 새로운 인스턴스/타입 메서드 추가, 새로운 초기화(Initializer) 추가, 프로토콜 채택(Protocol Conformance), 서브스크립트 추가(Subscripting), 중첩 타입(Nested Type) 추가
•
Extension으로 구현 불가능한 것: 저장 프로퍼티(Stored Property) 추가, 기존 기능의 재정의(Override), 초기화 메서드(Initializer)의 재정의, 기존 타입의 저장된 프로퍼티에 기본값 설정
// Extension 예시 - 1
extension String {
var length: Int {
return self.count
}
}
let str = "Hello"
print(str.length) // 출력: 5
// Extension 예시 - 2
extension Int {
func squared() -> Int {
return self * self
}
}
let number = 3
print(number.squared()) // 출력: 9
// Extension 예시 - 3
extension Double {
init(fromString str: String) {
self = Double(str) ?? 0.0
}
}
let value = Double(fromString: "3.14")
print(value) // 출력: 3.14
// Extension 예시 - 4
protocol Printable {
func printDescription()
}
struct MyStruct {}
extension MyStruct: Printable {
func printDescription() {
print("Printing description of MyStruct")
}
}
let myInstance = MyStruct()
myInstance.printDescription() // 출력: Printing description of MyStruct
Swift
복사
ARC(Automatic Reference Counting)
•
Swift의 메모리 관리 기법 중 하나로, 객체나 인스턴스가 참조되는 횟수를 추적하여 메모리에서 해제할 시점을 결정합니다. 객체가 생성될 때마다 참조 횟수가 1 증가하고, 해당 객체를 참조하는 다른 객체나 변수가 없어지거나 더 이상 사용되지 않을 때 참조 횟수가 1 감소합니다. 참조 횟수가 0이 되면 해당 객체는 메모리에서 해제됩니다.
•
ARC의 작동 방식
1.
객체 생성: 객체가 생성되면 참조 횟수가 1 증가합니다.
2.
객체 참조: 객체를 다른 변수나 상수에 할당하면 해당 객체의 참조 횟수가 1 증가합니다.
3.
참조 해제: 객체의 참조가 없어지면(참조하는 변수나 상수가 없거나 nil이 할당되면) 참조 횟수가 1 감소합니다.
4.
Zeroing Weak References: 약한 참조(Weak Reference)는 객체의 참조 횟수를 증가시키지 않고 추적합니다. 객체가 해제되면 약한 참조는 자동으로 nil로 설정됩니다.
•
참조 종류
1.
강한 참조(strong):
2.
약한 참조(weak): 옵셔널로 선언되는 참조. 참조하는 객체를 강제로 유지하지 않고, 참조 대상이 메모리에서 해제되면 자동으로 nil로 설정됩니다. 두 객체가 서로를 강하게 참조하는 경우, 순환 참조로 인해 메모리 누수가 발생할 수 있는데 한쪽을 weak로 선언하여 순환 참조 문제를 해결할 수 있습니다.
3.
비소유 참조(unowned): 옵셔널이 아닌 비소유 참조. 항상 값이 있다고 가정하며 참조하는 객체가 해제되면 런타임 에러가 발생할 수 있습니다.
// 레퍼런스 카운팅 예시
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
var reference1: Person?
var reference2: Person?
var reference3: Person?
reference1 = Person(name: "John Appleseed") // RC: 1️⃣
// Prints "John Appleseed is being initialized"
reference2 = reference1 // RC: 2️⃣
reference3 = reference1 // RC: 3️⃣
reference1 = nil // RC: 2️⃣
reference2 = nil // RC: 1️⃣
reference3 = nil // RC: 0️⃣
// Prints "John Appleseed is being deinitialized"
// 약한 참조(Weak Reference)를 사용한 해결 방법
class Man {
var name: String
weak var girlfriend: Woman?
init(name: String) {
self.name = name
}
deinit { print("Man Deinit!") }
}
class Woman {
var name: String
var boyfriend: Man?
init(name: String) {
self.name = name
}
deinit { print("Woman Deinit!") }
}
var chelosu: Man? = .init(name: "철수")
var yeonghee: Woman? = .init(name: "영희")
chelosu?.girlfriend = yeonghee
yeonghee?.boyfriend = chelosu
chelosu = nil
yeonghee = nil
chelosu?.girlfriend // nil
Swift
복사