# Swift Study Note ### ✔️ 컴퓨터의 동작원리 * 폰노이만 컴퓨터 구조 : cpu <-> ram(주기억 장치) <-> hdd(보조기억 장치) * 프로세스 : 실행중인 프로그램 * 프로그래밍 -> 컴파일 -> 기계어 * 메모리 = **코**드(프로그램) + (**데**이터 + **힙** + **스**택) ### ✔️ 메모리 저장방식에 대한 이해 - 비트 : 2진수(0b)에서 한개의 자릿수 - 바이트 : 비트 8개 (컴퓨터에서 데이터를 다루기 위해 사용되는 기본단위) - 2$^8$ 즉 256가지를 표현 할 수 있다. - 1바이트 = 글자 1개 - 16진법(0x) : 2진수를 4비트 2개로 나눠서 16진법을 사용하면 쉽게 표현된다. - 2$^{32}$ = 32비트 = 4G 메모리 주소 기억 - 2$^{64}$ = 64비트 = 16 EByte 메모리 주소 기억(16억) ### ✔️ 메모리에서 음수 표현 방법 - 가장 앞에 자릿수로 부호를 표기 - 기존 숫자에 2의 보수로 변환하고 1을 더함으로 음수를 표현 - 2의 보수의 방식은 컴퓨터의 덧샘을 보다 빨리 계산할 수 있게 한다. ### ✔️ 변수와 상수 / 데이터 타입 - String Interpolation : 문자열 보간법 ```swift= print("저의 이름은 \(name)입니다") ``` - Type Annotation : 변수를 선언하면서 타입도 명확하게 지정하는 방식 - Type Inference : 타입을 지정하지 않아도 컴파일러가 타입을 유추해서 알아내는 방식 - Type Safety : 다른 데이터 타입끼리 계산 할 수 없다. (메모리 공간의 크기가 다름) - Type Conversion : 기존의 값을 다른 형식으로 바꿔서 새로운 값을 생성후 다른 메모리 공간에 저장 - Type Alias : 기존에 선언되어 있는 타입에 새로운 별칭을 붙여서 코드의 가독성을 높이는 방법 ```swift= typealias: Something = (Int) -> String func someFunction(completionHandler: (int) -> String) { } func someFunction(completionHandler: Something) { } ``` - Literals : 코드에서 고정된 값으로 표현되는 데이터 그 자체 - Identifier (식별자) : 변수, 상수, 함수, 사용자 정의 타입의 이름들 모두 ### ✔️ 기본 연산자 - Operator (연산자), Operand (피연산자) - 모듈로 연산자 : 시간, 분, 배열에서 선택 할 때 유용 - Compound Assignment Operator (복합 할당 연산자) : ++는 지원하지 않음 - 접근연산자 : 점 (하위개념으로 접근) ### ✔️ 조건문 - 프로그래밍의 세가지 논리 : 순차, 조건, 반복 - 스위치문에서 콤마는 또는의 의미이다. - 스위치문은 비교하는 모든 경우의 수를 반드시 다뤄야 함 (exhaustive) - 패턴 매칭 연산자 (~=) : 참과 거짓의 결과가 나옴 - 스위치문에서 범위연산자(...)를 사용해야함 - fallthrough : 매칭된 값에 대한 고려없이 무저건 다음문장도 실행하고 싶을때 사용 - 바인딩 : 새로운 변수나 상수에 새로운 식별자로 할당한다 ```swift= var num = 10 switch num { case let x where x % 2 == 0: print("짝수숫자 : \(x)") case let x where x % 2 != 0: print("홀수숫자 : \(x)") default: break } ``` ### ✔️ 튜플 (Tuple) - 괄호와 쉼표로 복수 데이터 할당 - 접근방법 : 변수명.0 - Named Tuple : 코드의 가독성이 높아짐 ```swift= let iOS = (language: "swift", version: "5.0") iOS.language iOS.version ``` - Decomposition (튜플의 분해) ```swift= let (name, age, address) = ("홍길동", 20, "남산") ``` - 튜플의 바인딩 ```swift= var coordinate = (0, 5) switch coordinate { case (let distance, 0), (0, let distance): print("X 또는 Y축 위에 위치하며, \(distance)만큼의 거리가 떨어져있다.") default: print("축 위에 있지 않음") } coordinate = (1, -1) switch coordinate { case let (x, y) where x == y: print("(\(x), \(y))의 좌표는 y = x 1차 함수의 그래프 위에 있다.") case let (x, y) where x == -y: print("(\(x), \(y))의 좌표는 y = -x 1차 함수의 그래프 위에 있다.") case let (x, y): print("(\(x), \(y))의 좌표는 임의의 지점에 있다.") } ``` ### ✔️ 삼항연산자와 범위연산자 - 조건 ? 참 : 거짓 (조건에 따라 선택이 두가지 일 경우) - 조건에 따라서 리터럴 값을 변수에 할당 할 때 *주로* 사용 ```swift= var name = a > 0 ? "스티브" : "팀쿡" let result = score >= 70 ? "Pass" : "Fail" ``` - 패턴매칭 연산자 : 오른쪽의 표현식이 왼쪽의 범위에 포함되는 지에 따라 참과 거짓을 리턴 ```swift= a...b ~= age ``` ### ✔️ 반복문 - for 문 : 반복횟수를 미리 알고 있거나 컬렉션, 범위 등을 이용 할 때 사용 (범위, 컬렉션, 문자열, stride등) - while 문 : 반복횟수가 미리 정해저 있지 않고 조건에 따라 바꿜 때 사용 (조건) - 제어전송문 1. **continue** : 반복문에서 다음 주기로 넘어가서 계속함 ```swift= for num in 1...20 { if num % 2 == 0 { continue } print(num) // 1, 3, 5, 7, 9 .... } ``` 2. **break** : 반복문을 아예 중지 ```swift= for num iun 1...20 { if num % 2 == 0 { break } print(num) // 1 } ``` 3) fallthrough 4) throw: 에러가 발생 가능하도록 정의된 함수에서 throw 다음의 에러의 타입을 리턴후 함수를 벗어남 5) return : 아래줄 참조 ### ✔️ 함수 - 리턴 타입이 없는 함수는 결과값이 Void 타입 - 리턴 타입이 있는 함수는 결과값이 있는것 (일반적으로 사용) - Parameter (매개변수=인자) - Argument (인수) - Nested Function : 함수를 제한적으로 사용하고 싶을 때 사용 (stepForward, stepBackward) - 함수의 타입 표기 방법 ```swift= var function1: (Int) -> () = numberPrint(n:) var function2: (Int, Int) -> Void = addPrintFunction(_:_:) ``` - Overloading : 같은 함수에 매개변수를 다르게 선언하여, 하나의 함수이름에 여러개의 함수를 대응 시킴 - Scope : 상식적으로 생각해 - inout 키워드 : 함수내에서 변수의 값을 바꾸고 싶을 때 사용 (선언은 inout 키워드, 실행시 & 사용) ```swift= num1 = 123 num2 = 456 func swapNumber(a: inout Int, b: inout Int) { temp = a a = b b = temp } swapNumber(a: &num1, b: &num2) ``` ### ✔️ Guard 문 - 여러개의 조건이 있을때 코드의 가독성 문제를 해결하기 위해 사용 - if-else에서 else문이 먼저 배치해서 조건을 판별하여 조기 종료 (early exit) - if 문 조건을 만족해야만 내부 실행, guard 문 조건을 만족하지 않으면 다음을 실행 ```swift= func checkNumber(words: String) -> Bool { guard words.count >= 5 else { print("5글자 이하입니다.") return false } print("\(words.count) 글자 입니다.") return true } ``` - guard 문에는 반드시 return, throw, break, countinue로 조기 종료해야 함 - 어트리뷰트 키워드 : 선언과 타입에 추가적인 정보를 제공하는 키워드 - @discardableResult : 함수의 결과값이 나오더라도 사용하지 않을수도 있다고 알려줌 - 재귀함수 : 자기 자신을 반복해서 호출하는 함수 (조건을 반드시 넣어줘야 함 =!stackoverflow) ```swift= func factorial(num: Int) -> Int { var sum = 1 for i in 1...num { sum = sum * i } return sum } // 아래 재귀 함수로 표현 func factorialF(num: Int) -> Int { if num <= 1 { return 1 } return num * factorialF(num: num - 1) } factorial(num: 10) ``` - guard문에서 선언된 상수는 래에서 사용 가능 ### ✔️ Optional - 옵셔널 타입은 반드시 변수(var)로 선언해야 한다. - 옵셔널 타입 추출 방법 1) 강제추출 : num! (값이 항상 있는것이 확실 할 때 사용) 2) nil 아닌지 확인후 강제추출 : if문을 통해 nil이 아님을 먼저 확인 후, 강제추출 ```swift= if num != nil {print(num!)} ``` 3) ==**옵셔널 바인딩** : 바인딩(상수나 변수에 대입)이 되는 경우만 특정작업을 하겠다== ```swift= if let name = optionalName { print(name) } func doSomething(name: String) { guard let n = name else {return} print(n) } ``` 4) Nil-Coalescing : 옵셔널 표현식 뒤에 기본값을 제시하여 옵셔널의 기능을 없앰 ```swift= var serverName: String? var userName = serverName ?? "미인증 사용자" var hello = "인사하겠습니다." + (str ?? "Say, Hi") ``` 5) 옵셔널 체이닝 : 표현식 자체가 옵셔널의 가능성이 있다는 것을 표현, 결과는 항상 옵셔널 ```swift= human?.choco?.name ``` 6) 함수에서 옵셔널 타입의 사용 : nil을 초기값으로 선언해 넣어 두면 편하다 ```swift= func doSomething(with label: String, name: String? = nil) { print("\(label): \(name)") } ``` ### ✔️ Collection > 데이터를 담는 바구니 (Array, Dictionary, Set) #### 1. Array - 빈 배열을 생성하는 방법 ```swift= let emptyArray: [Int] = [] let emptyArray = Array<Int>() let emptyArray = [Int]() ``` - 배열의 기본기능 ```swift= var numsArray = ["goog", "ssdf"] numsArray.count numsArray.isEmpty numsArray.contains(1) numsArray.randomElement() numsArray.swapAt(0, 1) numsArray.first numsArray.last numsArray.startIndex numsArray.endIndex numsArray[numsArray.endIndex - 1] // 사용주의! numsArray.firstIndex(of: "iOS") numsArray.lastIndex(of: "iOS") if let index = numsArray.firstIndex(of: "iOS") { print(index) print(numsArray[index]) } ``` - 메소드 : insert, replace, append, remove - 메소드 함수명 - 동사원형 : 컬렉션 자체를 ==직접적으로 변환함== - 과거형 : 컬렉션 자체를 직접 ==변환하지 않고 변경된 값만 리턴함== #### 2. Dictionary - 키, 밸류값 - 동일한 타입 쌍의 데이터만 담을수 있음 - 중첩사용 가능 - Key 값은 hashable 해야 함 (유일성 보증) - 빈 딕셔너리를 생성하는 방법 ```swift= let emptyDic1: Dictionary<Int, String> = [:] let emptyDic2: Dictionary<Int, String>() let emptyDic3: [Int: String]() ``` - 딕셔너리는 기본적으로 서브스크립트[]를 이용한 문법을 주로 사용 - 딕셔너리의 키로 호출시 옵셔널 타입으로 값을 반환함 - 딕셔너리의 기본기능 ```swift= print(dic.keys) print(dic.values) dic.keys.sorted() // 키를 배열로 변환함 dic.values.sorted() // 밸류를 배열로 변환함 for key in dic.keys.sorted() { print(key) } ``` - 메소드 : update (삽입, 교체, 추가), remove - 반복문과의 결합 ```swift= for (key, value) in dict { print("\(key): \(value)") } ``` #### 3. Set (집합) - 배열이랑 구분이 안되기 때문에 타입을 반드시 선언해야 함! - 요소는 중복 저장이 되지 않음 (hashable) - 서브스크립트 관련 문법이 없음 ### ✔️ 열거형 (Enumeration) - 타입 자체를 한정된 사례(CASE) 안에서 정의 할 수 있는 타입 (열거형은 타입이다!) - 일반적으로 switch 문으로 분기처리 - 타입의 값(리터럴) - 열거형의 원시값 (Raw Value) : 원시값을 정의하여(타입) 열거형을 좀 더 쉽게 사용 ```swift= enum RpsGame: Int { case rock case paper case scissors } // 실제 사용 var game = RpsGame(rawValue: 0) RpsGame(rawValue: 0) RpsGame(rawValue: 1) RpsGame(rawValue: 2) ``` - 열거형의 연관값 (Associated Value) : 열거형에 구체적인 추가정보를 저장하기 위해 사용 ```swift= enum Computer { case cpu(core: Int, ghz: Double) case ram(Int, String) case hardDisk(gb: Int) } var chip = Computer.cpu(core: 8, ghz: 3.1) chip = Computer.ram(8, "DRAM") switch chip { case .cpu(core: 8, ghz: 3.1): print("CPU 8코어 3.1GHz 입니다.") case .cup(core: 8, ghz: 2.8): print("CPU 8코어 2.8GHz 입니다.") case .ram(32, _): print("32기가 램 입니다.") default: print("그 이외의 칩에는 관심이 없습니다.") } // 바인딩 사용 switch chip { case let .cpu(a, b): print("CPU는 \(a)코어 \(b)GHz 입니다.") case let .ram(a, _): print("\(a)기가 램입니다.") case hardDisk(let a): print("하드디스크 \(a)기가 용량입니다.") } ``` - 옵셔널 열거형인 경우 swift에서 switch문에서 편의적인 기능을 제공한다 - 열거형에 연관값이 있는 경우 switch문외에 조건문과 반복문에도 사용가능하다. (열거형 케이스 패턴) - 옵셔널 패턴 : - @unknown 키워드 : switch 문에서 열거형의 모든 케이스를 다루지 않은 경우 경고창을 출력 ### ✔️ 클래스와 구조체의 이해 #### 1. 클래스 - 프로그래밍 패러다임 : 객체 지향 프로그래밍으로 변화됨 - 객체 지향 프로그래밍 : 틀(클래스, 구조체)로 실제데이터(객체)를 찍어내는 것 ```swift= class Dog { var name = "강아지" var weight = 0 func sit() { print("앉았습니다.") } func layDown { print("누웠습니다.") } } var bori = Dog() var choco = Dog() ``` - 클래스는 변수와 함수를 묶음으로 만들어 내는 것 - 클래스 내의 변수는 속성(property), 함수는 메서드(method)라고 하고 반드시 2가지로 이루어짐 #### 2. 구조체 - 기본적으로 클래스와 동일한 구조를 가지고 있으나, 상속이 안됨 #### 3. 클래스와 구조체 비교 - 둘다 메모리에 찍어낸 것을 인스턴스라고 함 - 인스턴스 : 실제로 메모리에 할당되어 구체적 실체를 갖춘 것이라는 의미 - 클래스의 인스턴스를 특별히 객체(object)라고 부름 | 구조체 | 클래스 | | ---- | ---- | | 값형식(Value Type)| 참조형식(Reference)| | 인스턴스 데이터를 모두 **스택**에 저장 | 인스턴스 데이터를 **힙**에 저장| | 복사시 값을 전달할때 마다 **복사본 생성** <br> (서로 다른 데이터 존재) | 복시시 값을 전달하지 않고 저장된 **주소를 전달** <br>(동일한 데이터를 가르킨다)| | 스택 프레임 종료시 메모리에서 자동 제거 | ARC 시스템을 통해 메모리 관리| - 관습 : 속성과 메서드의 순서로 작성함 - 주의 : 클래스 내부에는 메서드 실행문이 올 수 없다. 그러나, 선언문 안에서는 함수 실행 가능 - 초기화의 생성자(initializer)의 의미 - 모든 저장 속성(변수)을 초기화 해야함 - 생성자의 목적은 결국 "저장속성 초기화" ```swift= class Dog { var name: String var weight: Double init(name: String, weight: Double) { self.name = name self.weight = weight } } var bori = Dog(name: "보리", weight: 12) ``` - 식별 연산자(Identity) : 두개의 참조가 같은 인스턴스를 가르키고 있는지 비교하는 방법 ```swift= print(dog1 === dog2) print(dog1 !== dog2) ``` #### 4. 클래스와 구조체를 사용하는 이유 - 의미있는 데이터를 묶음으로 만들려고 함 (모델링) 1. 사용하려는 모델의 설계 2. 애플이 미리 설계해 놓은 클래스/구조체를 잘 사용하기 위함 - 객체지향(OOP)의 4대 특징 (==캡상추다==) 1. 추상화(모델링) : 공통적인 특성을 뽑아내서 하나의 분류(class)로 만든 것 2. 캡슐화 : 속성(상태)와 매서드(기능)을 하나의 클래스로 묶어서 활용 (은닉화 가능) 3. 상속성 : 재사용과 확장 4. 다형성 : 오버라이딩, 오버로딩 ### ✔️ 속성 #### 1. 저장속성 - 그 자체가 메모리 공간을 갖는것 (변수) - 지연 저장속성 (Lazy Stored Property) : 처음엔 메모리 공간을 가지지 않는 것, 초기화 값 반드시 필요 - 지연 저장속성을 사용하는 이유 1. 메모리 공간을 많이 차지 하는 속성을 사용 할 때 2. 다른 속성을 이용해야 할 때 (순서상) #### 2. 계산속성 - 다른 저장 속성의 의한 결과로 계산해 나오는 방식의 메서드인 경우 아예 속성으로 만들어버림 - get(값을 얻음, getter), set(값을 저장, setter) - set은 생략가능 (read-only) ```swift= class Person { var height: Double = 0 var weight: Double = 0 func calculateBMI() -> Double { let bmi = weight / (height * height) * 10000 return bmi } } let p = Person() p.height = 178 p.weight = 81 p.calculateBMI() class Person1 { var height: Double = 0 var weight: Double = 0 var bmi: Double { get { // get ===> 값을 얻는다는 의미 let bmi = weight / (height * height) * 10000 return bmi } } } let p1 = Person1() p1.height = 183 p1.weight = 86 p1.bmi ``` #### 3. 타입속성 - 타입 자체에 속한 속성이므로 내/외부에서 Type.property로 접근, static keyword - 저장 타입 속성 : 모든 인스턴스가 동일하게 가져야 하는 보편적인 속성이나 공유해야 하는 성격의 것들 (초기값 반드시 필요) - 계산 타입 속성 : 상속시 재정의 가능 (class 키워드를 사용시), 메서드라서 메모리 공간 미할당 #### 4. 속성 감시자(관찰자) - (일반적으로) 저장 속성을 관찰함 - willset : 값이 변하기 직전에 호출 - didset : 값이 변한후 직후에 호출 (실제 프로젝트에서 일반적으로 주로 사용) ```swift= class Profile { var name: String = "이름" var statusMessage: String { // willSet(message) { // 바뀔 값이 파라미터로 전달 // print("상태가 \(statusMessage)에서 \(message)로 변경될 예정입니다.") // print("상태메세지 업데이트 준비") // } willSet { // newValue 사용 print("상태가 \(statusMessage)에서 \(newValue)로 변경될 예정입니다.") print("상태메세지 업데이트 준비") } // didSet(message) { // 바뀌기 전의 과거값이 파라미터로 전달 // print("상태가 \(message)에서 \(statusMessage)로 이미 변경되었습니다.") // print("상테메시지 업데이트 완료") // } didSet { // oldValue 사용 print("상태가 \(oldValue)에서 \(statusMessage)로 이미 변경되었습니다.") print("상테메시지 업데이트 완료") } } init(message: String) { self.statusMessage = message } } let profile = Profile(message: "기본 상태 메시지") profile.statusMessage = "기분 좋아졌으" >> 상태가 기분 상태 메시지에서 기분 좋아졌으로 변경될 예정입니다. >> 상태메시지 업데이트 준비 >> 상태가 기본 상태 메시지에서 기분 좋아졌으로 이미 변경되었습니다. >> 상태메시지 업데이트 완료 ``` ### ✔️ 메서드 클래스나 구조체이 있는 함수 #### 1. 인스턴스 메서드 - 구조체는 인스턴스 메서드 내에서 속성을 수정 할 수 없음 (mutating 키워드 사용해야 함) - 오버로딩 적용 가능 #### 2. 타입 메서드 - 인스턴스에 속한 속성이 아니라 타입 자체에 속한 속성이므로 Type.method() 로 접근 - static 키워드 사용 : 타입 저장속성에 접근가능 (붕어빵 틀 안에 속해 있는 메서드) ```swift= // 예시 Int.random(in: 1...100) Double.random(in: 1.2...3.7) ``` - class 키워드 사용 : 상속시 재정의 가능 (static 으로 선언하면 상속시 재정의 불가) #### 3. 서브스크립트 - 대괄호는 사실 특별한 형태의 메서드 호출 역할임 -> 따라서, 직접 구현도 가능하다. - 인스턴스.method()의 형태가 아닌 인스턴스[파라미터]의 형태 ```swift= class Somedata { var datas = ["Apple", "Swift", "iOS", "hello"] subscript(index: Int) -> String { get { return datas[index] } set(parameterName) { // newValue로 교체 가능, setter와 동일 datas[index] = parameterName } } } var data = Somedata() data[0] data[0] = "AAA" ``` - 계산 속성과 형태는 유사함 (getter / setter) - 인스턴스 서브스크립트와 타입 서브스크립트 모두 가능 (static/class 키워드 사용) #### 4. 참고 - 접근제어 : private 키워드를 사용하면 외부에서 접근이 불가능하게 객체의 은닉화 - 싱글톤 패턴 : 메모리상에 유일하게 1개만 존재하는 객체 설계 (==static let== 키워드) ```swift= class Singleton { static let shared = Singleton() var userInfoId = 12345 private init(){} // 새로운 객체를 찍어 내는것을 막는다! } let object = Singleton.shared // 싱글톤에 접근 let object = Singleton() // 에러!! ``` ### ✔️ 클래스의 상속과 초기화 - 상속 (Inherance = Subclassing) : 새로운 타입을 만들어서 저장속성을 추가하는 것 (수직확장) - 재정의 (Overriding) : 상위클래스의 속성/메서드를 재정의(기능을 변형하여 사용) 하는 것 (==저장속성 불가==) - 속성과 메서드는 재정의 방식이 다름 : 메서드는 재정의시 배열을 새롭게 만들기 때문에 변형 가능 ```swift= class SomeSuperclass { var aValue = 0 func doSomething() { print("Do something") } } class SomeSubclass: SomeSuperclass { // override var aValue = 3 // 저장속성의 재정의는 불가 override var aValue: Int { get { return 1 } set { // self가 아님!! super.aValue = newValue } } // 저장속성 ===> (재정의) 계산속성 가능 override func doSomething() { super.doSomething() print("Do something 22222") } } ``` - 초기화(Initialization) : 클래스, 구조체, 열거형의 인스턴스를 생성하는 과정 - 각 "저장 속성"에 대한 초기값을 설정하여 인스턴스를 사용 가능한 상태로 만드는것 <br>(열거형은 저장속성이 존재하지 않으므로, case 중 한가지를 선택 및 생성) - 생성자는 overloading을 지원하여 생성자를 여러개 구현 가능 - 반드시 생성자를 정의해야만 하는것은 아님 (저장 속성 선언시 값 저장, 저장 속성을 옵셔널로 선언) - 구조체는 멤버와이즈 이니셜라이저 자동 제공해서 초기화 안해도 됨 - 구조체의 지정(Designated) 생성자(일반적인 생성자)의 구현 ```swift= struct Color { let red, green, blue: Double init(red: Double, green: Double, blue: Double) { self.red = red self.green = green self.blue = blue } init() { self.init(red: 0.0, green: 0.0, blue: 0.0) } init(white: Double) { self.init(red: white, green: white, blue: white) } } ``` - 클래스의 편의(Convenience) 생성자의 구현 : convenience 키워드 사용, 상속시 재정의 불가 - 편의 생성자는 다른 편의 생성자를 호출하거나, 지정 생성자를 호출해야 함 (궁긍적으로 지정 생성자 호출) ```swift= class Aclass { var x: Int var y: Int init(x: Int, y: Int) { self.x = x self.y = y } convenience init() { self.init(x: 0, y: 0) } } class Bclass: Aclass { var z: Int init(x: Int, y: Int, z: Int) { self.z = z // 필수 super.init(x: x, y: y) // 필수 } convenience init(z: Int) { self.init(x: 0, y: 0, z: z) } convenience init() { self.init(z: 0) } } ``` - 필수 생성자 : required 키워드를 사용하며 하위 생성자는 반드시 해당 필수 생성자를 구현해야 함 - 필수 생성자 자동상속 조건 : 다른 지정 생성자를 구현 안하면, 자동으로 필수 생성자가 상속됨 - 실패가능 생성자 : 인스턴스 생성에 실패 할 수도 있는 가능성을 가진 생성자(클래스, 구조체, 열거형) - 소멸자 : deinit 키워드 사용, 인스턴트가 메모리에서 해제 되기 직전에 필요한 내용을 구현 ### ✔️ 타입캐스팅 - 정의??? #### 1. is 연산자 - 인스턴트의 타입에 대한 검사를 수행하는 연산자 ```swift= class Person { var id = 0 var name = "이름" var email = "abc@gmail.com" } class Student: Person { var studentId = 1 } class Undergraduate: Student { var major = "전공" } let person1 = Person() let student1 = Student() let undergraduated1 = Undergraduate() let person2 = Person() let student2 = Student() let undergraduated2 = Undergraduate() let people = [person1, person2, student1, student2, undergraduated1, undergraduated2] var studentNumber = 0 for someOne in people { if someOne is Student { studentNumber += 1 } } print(studentNumber) ``` #### 2. as 연산자 - 인스턴트의 타입의 힌트를 변경하는 연산자 - 다운캐스팅 (Downcasting) - 인스턴스 as? 타입 : 성공시 옵셔널 타입으로 리턴, 실패시 nil 리턴, 언래핑 필요 - 인스턴스 as! 타입 : 성공시 강제 언래핑, 실패시 런타임 오류 - 업캐스팅 (Upcasting) - as 항상 성공 (당연) - as 연산자 활용 - 브릿징 #### 3. 타입과 다형성 - 다형성 (Polymorphism) : 여러가지 모양 (추상화) 1 ) 하나의 객체(인스턴스)가 여러가지 타입의 형태로 표현 될 수 있다 2) 상속했을 때 ==재정의한 메서드가 실행==된다 3) 다형성이 구현되는 것은 "클래스의 상속"과 깊은 연관이 있다 (프로토콜과 깊은 연관) #### 4. Any와 AnyObject를 위한 타입캐스팅 - Any Type : 어떤 타입의 인스턴스도 표현 할 수 있는 타입 (옵셔널 포함) - AnyObject Type : 어떤 ***클래스*** 타입의 인스턴스도 표현 할 수 있는 타입 - switch문에 is/as 패턴을 사용하여 case에서 배열등 열거후 분기처리 가능 ```swift= let array: [Any] = [5, "안녕", 3.5] for (index, item) in array.enumerated() { switch item { case is Int: print("Index - \(index): 정수입니다.") case let num as Double: print("Index - \(num): 소수입니다.") case is String: print("Index - \(index): 문자열입니다.") default: print("Index - \(index): 그 외의 타입니다.") } } ``` ### ✔️ 타입의 확장 (Extension) - 현재 존재하는 타입에 기능(메서드)을 추가하여 사용 (클래스, 구조체, 열거형 모두 가능) - 확장의 장점 : 소급-모델링(retroactive modeling)을 사용 할 수 있다 (애플이 미리 만들어 놓은 타입) ```swift= extension Int { var squared: Int { return self * self } } func squard(num: Int) { // 함수로도 만들수는 있으나;;;; return num * num } 5.squared squared(num: 5) // 사용 할 때 매우 불편하다.. ``` - 확장 가능 멤버 1) 타입 및 인스턴스의 계산 속성 ```swift= extension Double { static var zero: Double { return 0.0 } } Double.zero extension Double { var km: Double { return self * 1_000.0 } var m: Double { return self } var cm: Double { return self / 100.0 } var mm: Double { return self / 1_000.0 } var ft: Double { return self / 3.28084 } } let oneInch = 25.4.mm print("1인치는 \(oneInch)미터 입니다.") let threeFeet = 3.ft print("3피트는 \(threeFeet)미터 입니다.") ``` 2) 타입 및 인스턴스의 메서드 ```swift= extension Int { // 타입 메서드 확장 static func printNumbersFrom1to5() { for i in 1...5 { print(i) } } } Int.printNumbersFrom1to5() extension String { func printHelloRepetitions(of times: Int) { for _ in 0..<times { print("Hello \(self)!") } } } "Steve".printHelloRepetitions(of: 10) extension Int { mutating func squared() { // 구조체(열거형)에서 자신의 속성을 변경하는 self = self * self // 메서드는 mutating 키워드 필요 } } var someInt = 3 someInt.squared() ``` 3) 새로운 생성자 (클래스의 경우 편의 생성자만 가능, 구조체는 제한이 없으나 예외사항 존재) ```swift= // 클래스 extension UIColor { convenience init(color: CGFloat) { self.init(red: color/255, green: color/255, blue: color/255, alpha: 1) } } UIColor(color: 1) // 구조체 struct Point { var x = 0.0, y = 0.0 } extension Point { init(num: Double) { self.init(x: num, y: num) } } ``` 4) 서브스크립트 ```swift= extension Int { subscript(num: Int) -> Int { var decimalBase = 1 for _ in 0..<num { decimalBase *= 10 } return (self / decimalBase) % 10 } } 123456789[0] // 9 123456789[1] // 8 123456789[2] // 7 123456789[3] // 6 ``` 5) 새로운 중첩 타입 정의 및 사용 ```swift= extension Int { enum Kind { case negative, zero, positive } var kind: Kind { switch self { case 0: return .zero // Kind.zero case let x where x > 0: return .positive // Kind.positive default: return .negative // Kind.negative } } } func printIntegerKinds(_ numbers: [Int]) { for number in numbers { switch number.kind { case .negative: print("- ", terminator: "") case .positive: print("+ ", terminator: "") case .zero: print("0 ", terminator: "") } } } printIntegerKinds([1, 3, -1, 0, 1, -2]) ``` 6) 프로토콜 채택 및 프로토콜 관련 메서드 ### 프로토콜 - 규약/협약(약속) : 자격증(운전면허증)과 같은 개념 - 기본문법 1. 정의 ```swift= protocol MyProtocol { func doSomthing() -> Int } ``` 2. 채택 ```swift= class Class: MyProtocol { } ``` 3. 구현 ```swift= class Class: MyProtocol { func doSomething() -> Int { return 7 } } ``` 4. 속성의 요구사항 정의 방법 1. 인스턴스 속성 요구사항 : 저장/계산 속성으로 모두 구현가능 {get} {get set} 2. 타입(static) 속성 요구 사항 : 채택시 static 키워드로만 구현가능 (저장속성 재정의 불가원칙) 3. 단, class에서 채택시 계산 타입 속성에서 static/class 키워드 모두 구현 가능(class 키워드 재정의) ```swift= protocol MyProtocol { var id: String {get} var name: String {get set} static var type: String {get set} } ``` - 메서드 요구사항 정의 : 메서드의 헤드부분(인/아웃풋)의 형태만 요구사항으로 정의 - 타입 메서드로 제한하려면 static 키워드 붙임 - mutating 키워드 : 구조체에서 저장 속성을 변경하는 경우 ```swift= protocol RandomNumber { static func reset() func random() -> Int mutating func doSomething() } struct Number: RandomNumber { var num = 0 static func reset() { print("다시 세팅") } func random() -> Int { return Int.random(in: 1...100) } mutating func doSomething() { self.num = 10 } } let n = Number() n.random() Number.reset() protocol Toggleable { mutating func toggle() } enum OnOffSwitch: Toggleable {  // 열거형으로 구현 case on, off mutating func toggle() { switch self { case .off: self = .on case .on: self = .off } } } var s = OnOffSwitch.off s.toggle() s.toggle() class BigSwitch: Toggleable { // 클래스로 구현 var isOn = false func toggle() {             // mutatating 키워드 필요 없음 isOn = isOn ? false : true } } var big = BigSwitch() print(big.isOn) big.toggle() print(big.isOn) ``` - 생성자의 요구사항 : 클래스는 생성자 앞에 required를 붙여야 함 - 클래스와 프로토콜 모두 사용시 (required, override) ```swift= protocol AProtocol { init() } class ASuperClass { init() { } } class ASubClass: ASuperClass, AProtocol { required override init() { } } ``` - 서브스크립트 요구사항 : get, set 키워드를 통해 읽기/쓰기 여부 설정 - (관습적인) 프로토콜 채택과 구현 - Extension에서 사용함 - 프로토콜은 타입이다(일급객체로 취급) 1. 프로토콜을 변수에 할당 할 수 있음 2. 함수를 호출 할 때 프로토콜을 파라미터로 전달 할 수 있음 3. 함수에서 프로토콜을 반환할수 있음 - AnyObject : 클래스 전용 프로토콜 - 프로토콜 합성 : & 문자열로 연결해서 사용 (두개의 타입을 병합해서 사용) - 선택적인(구현해도 되고 안해도 되는) 멤버 선언하기 (클래스 전용 프로토콜) 1. 프로토콜 앞에 @objc 추가 2. 멤버 앞에 @objc optional 추가 #### 프로토콜 확장 - 프로토콜을 채택한 타입에서 실제 메서드 구현을 반복해야 하는 불편함 제거 - 요구사항의 우선순위 있음 (직접 구현한 해당 메서드->기본 메서드 순서) - 형식을 제한 가능 ```swift= protocol Remote { func turnOn() func turnOff() } protocol Bluetooth { func blueOn() func blueOff() } extension Bluetooth where Self: Remote { // 뭔뜻인지 알지? 설명이 길다. func blueOn() {print("블루투스 켜기")} func blueOff() {print("블루투스 끄기")} } class SmartPhone: Remote, Bluetooth { } ``` ### Method Dispatch ### 중첩타입 (Nested Type) - 사용이유 : 1) 사용범위를 한정 2) 타입간의 연관성을 명확히 구분하고 내부 구조를 디테일하게 설계 ```swift= class Aclass { struct Bstruct { enum Cenum { case aCase case bCase struct Dstruct { }         } var name: Cenum } } let aClass: Aclass = Aclass() let bStruct: Aclass.Bstruct = Aclass.Bstruct(name: .bCase) let cEnum: Aclass.Bstruct.Cenum = Aclass.Bstruct.Cenum.aCase let dStruct: Aclass.Bstruct.Cenum.Dstruct = Aclass.Bstruct.Cenum.Dstruct() ``` ### self vs Self | self | Self | |----------|-------------| | 인스턴를 가르킴 | 타입을 가르킴 | | "hello", 7 ... | String, Int ...| | 1) 인스턴스 내부에서 인스턴스의 속성을 더 명확하게 가르키기 위해 사용 <br> 2) 값타입(구조체/열거형)에서 인스턴스 자체의 값을 치환할때 사용(클래스는 불가)<br> 3) 타입속성/메서드에서 사용하면, 타입자체를 가르킴<br> 4) 타입 인스턴스를 가르킬때, 타입 자체의 뒤에 붙여서 사용(타입자체를 외부에서) | 1) 특정타입 내부에서 타입을 선언하는 위치에 사용<br> 2) 특정타입 내부에서 타입속성/타입메서드를 지칭하는 위치에서 타입 대신 사용<br> 3) 프로토콜에서 채택하려는 타입을 지칭| ```swift= // self // 1.인스턴스를 가르키기 위해 사용 class Person { var name: String init(name: String) { self.name = name } } // 2. 새로운 값으로 속성 초기화 가능한 패턴 (값타입에서) struct Calculator { var number: Int = 0 mutating func plusNumber(_ num: Int) { number = number + num } // 값 타입(구조체, 열거형)에서 인스턴스 값 자체를 치환 가능 mutating func reset() { self = Calculator()     // 값 타입은 새로 생성해서 치환 하는것도 가능 } } // 3. 타입멤버에서 사용하면, 인스턴트가 아닌 타입 자체를 가르킴 struct MyStruct { static let club = "iOS 부서" static func doPrinting() { print("소속은 \(self.club)입니다.")   // MyStruct.club } } // 4. 외부에서 타입 인스턴스를 가르키는 경우에 사용 class SomeClass { static let name = "SomeClass" } let myClass: SomeClass.Type = SomeClass.self SomeClass.name  // SomeClass.self.name Int.max         // Int.self.max // Self extension Int { static let zero: Self = 0   // Int 타입 // static let zero: Int = 0 // 인스턴스의 계산속성 var zero: Self {    // 1. 타입을 선언하는 위치에서 사용 return 0 } // var zero: Int { return 0 } // 2. 타입 속성/메서드에서 지칭 static func toZero() -> Self { return Self.zero    // Int.zero } // 인스턴스 메서드 func toZero() -> Self { return self.zero } } extension BinaryInteger {   // 해당 프로토콜을 채택한 모든 타입에서 사용가능 func squared() -> Self { return self * self } } let x1: Int = -7 let y1: UInt = 7 if x1 <= y1 { int("\(x1)가 \(y1)보다 작거나 같다.") } else { print("\(x1)가 \(y1)보다 크다.") } x1.squared()    // 확장성이 좋아짐 y1.squared()    // 확장성이 좋아짐 ``` ### 클로저(Closure) - 이름이 없는 (익명)함수 - 스위프트는 함수를 "일급객체"로 취급 (함수는 타입이다) 1) 함수를 변수에 할당 할 수 있다. 2) 함수를 파라미터로 전달이 가능하다. 3) (함수에서) 함수를 반환 할 수 있다. (리턴가능) ```swift= let closureType = { (param: String) -> String in return param + "!" } closureType("잡스") ``` - 클로저를 사용하는 이유 1) 함수를 실행할때 함수를 정의하면서(변수에 담아서 혹은 클로저 형태로 직접) 전달(콜백함수)하기 위해 2) 사후적 정의로 개발의 자유도(활용도)가 높아진다 ```swift= func performClosure(closure: ()->()) { print("시작") closure() print("끝") } performClosure(closure: { print("=====중간=====") }) ``` - 클로저의 문법 최적화 1) 문맥상에서 파라미터와 리턴밸류 타입 추론으로 생략이 가능 2) single expression인 경우(한줄), 리턴을 안 적어도 됨 (Implicit return) - 함수도 동일 3) argument 이름을 축약 가능 (Shorthand Arguments) ===> $0, $1 (파라미터 순서) 4) Trailing Closure : 함수의 ===마지막 전달 인자===로 클로저 전달 되는 경우, 소괄호 생략 가능 ```swift= func performClosure(closure: ()->()) { print("시작") closure() print("끝") } performClosure(closure: { print("=====중간=====") }) // 1) Trailing Closure (후행 클로저) func closureParamFunction(closure: ()-> Void) { print("프린트 시작") closure() } // 축약과정 closureParamFunction(closure: { print("프린트 종료") }) closureParamFunction(closure: ) { print("프린트 종료") } closureParamFunction() { print("프린트 종료") } // 최종 축약 closureParamFunction { print("프린트 종료") } func shortenClosure(param: (String) -> Int) { param("Swift") } shortenClosure { $0.count } ``` - @escaping 키워드 : 외부에 변수에 할당하기 위해 클로저를 빠져나감 (힙에 저장됨) - @autoclosure 키워드 : 파라미터가 없는 클로저를 자동으로 만들어 줌 (non-escaping 특성을 가짐) ### 고차함수 (Higher-order Function) - 함수를 파라미터로 사용하거나, 함수실행의 결과를 함수로 리턴하는 함수 - map : - filter : 기존 배열의 각 아이템을 클로저가 제공하는 논리조건에 맞추어 확인후 참을 만족하는 아이템으로 새로운 배열을 리턴 - reduce : 기존 배열의 각 아이템을 결합해서 마지막 결과값을 리턴(초기값, 클로저 필요) - foreach : 기존 배열의 각 아이템을 활용해서 특정작업을 실행 - compactMap : 기존 배열의 각 아이템을 새롭게 매핑후 변형하되 옵셔널 요소는 제거하고 새로운 배열리턴 - flatMap : 중첩된 배열의 각 아이템을 새롭게 매핑하면서 중첩된 배열을 제거하고 리턴 ### 스위프트의 프로그래밍 패러다임 - 객체지향 프로그래밍 - 프로토콜 지향 프로그래밍 - 함수형 프로그래밍 (선언형) : 함수를 이용해서 (사이드이펙트가 없도록) 선언형으로 프로그래밍 하는것 #### 옵셔널 체이닝 - 규칙 : 옵셔널 타입으로 선언된 값에 접근해서, 속성, 매서드를 사용할때 접근연산자(.) 앞에 ?을 붙여야 함 ### ARC ### 에러처리 - 에러는 (Error 프로토콜을 채택한) 열거형이다 1) 에러정의 : 열거형으로 정의 2) 에러를 발생할수 있는 함수 정의 : throw 키워드로 에러를 던질수 있도록 정의 3) 함수에 대한 처리 : do-catch 블럭에서 처리 ```swift= enum HeightError: Error { case maxHeight case minHeight } func checkHeight(height: Int) throws -> Bool { if height > 190 { throw HeightError.maxHeight } else if height < 130 { throw HeightError.minHeight } else { if height >= 160 { return true } else { return false } } } do { let isChecked = try checkHeight(height: 160) print("놀이기구 타는것 가능 : \(isChecked)") } catch { print("놀이기구 타는것 불가능") } ``` - 에러처리방법 1) try 2) try? : 정상적인 경우 함수의 리턴타입을 반환, 에러가 발생하면 nil 리턴 3) try! : 에러가 발생하면 런타임 에러 (에러가 발생 할 수 없다고 확신하는 경우에만 사용) ```swift= func checkHeight(height: Int) throws -> Bool { if height > 190 { throw HeightError.maxHeight } else if height < 130 { throw HeightError.minHeight } else { if height >= 160 { return true } else { return false } } } do { let isChecked = try checkHeight(height: 200) if isChecked { print("청룡열차 가능") } else { print("후룸라이드 가능") } } catch { print("놀이기구 타는것 불가능") } let isChecked = try? checkedHeight(height: 200) let isChecked = try! checkedHeight(height: 200) ``` - Defer : 어떤 동작을 마지막으로 미루는 방법 ### 네트워크 통신의 이해 - 클라이언트 ----- TCP/IP ---- 컴퓨터 - HTTP 프로토콜 / 요청 메소드의 종류 1) get : 조회 - 리소스 취득 (게시판 글 읽어오기-데이터 표시) 2) post : 등록 - 엔티티 (게시판 글쓰기 / 댓글달기 / 새로운 주문서 생성) 3) put : 데이터 대체/ 없으면 생성 - 파일 생성 (게시글 수정 / 데이터 전부 대체) 4) patch : 리소스 부분 변경 5) delete - HTTP 프로토콜 / 응답 상세코드 1) 1xx : informational - 리퀘스트를 받아들여 처리중 (거의 사용되지 않음) 2) 2xx : success - 리퀘스트를 정상적으로 처리했음 3) 3xx : redirection - 리퀘스트를 완료하기 위해서 추가 동작이 필요 4) 4xx : client error - 서버는 리퀘스트 이해 불가능 (클라이엔트 에러 / 잘못된 요청) 5) 5xx : server error - 서버는 리퀘스트 처리 실패 (서버에러 / 서버 내부의 문제) - iOS의 네트워킹 1) URL 구조체 2) URLSession : 브라우저를 켜고 연결상태를 유지한다 3) dataTask 4) 시작 (resume) ```swift= let myKey = "29817be1a7bf5ffdd7666868c265f1c9" let movieURL = "http://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=\(myKey)&targetDt=20120101" let structUrl = URL(string: movieURL)! let session = URLSession.shared let task = session.dataTask(with: structUrl) { data, response, error in if error != nil { print(error!) return } if let safeData = data { print(String(decoding: safeData, as: UTF8.self)) } } task.resume() ``` - 네트워크 통신의 단계 > 요청 (Request) -> 서버데이터 (JSON) -> 분석 (Parse) -> 변환 (우리가 쓰려는 Struct / Class) ### 동시성 프로그래밍 - Sync : 작업을 다른 쓰레드에 시킨후 그 작업이 끝나길 **기다렸다가** 다음일을 진행한다 - Async : 작업을 다른 쓰레드에 시킨후 그 작업이 끝나길 **안 기다리고** 다음일을 진행한다 - Serial (직렬처리) : 다른 한개의 쓰레드에서 처리 (순서가 중요할때) - Concurrent (동시처리) : 다른 여러개의 쓰레드에서 처리 (각자가 독립적이지만 유사한 여러개의 작업시) - Parallel (병렬성) : 물리적인 쓰레드에서 실제 동시에 일을 하는 개념 - Concurrency (동시성) : 메인 쓰레드가 아닌 다른 소프트웨어적인 쓰레드에서 동시에 일을 하는 개념 - Queue의 종류 1) DispatchQueue.main : 메인큐 = 메인쓰레드(1번 쓰레드), ==UI 업데이트==- 직렬 2) DispatchQueue.global() : 6가지 Qos(Quality of service) 우선순위 - 동시 3) DispatchQueue(label:) : 커스텀 프리이빗, Qos 추론 및 설정가능 : 기본 직렬이나 동시 설정가능 #### GCD 사용시 주의사항 - swift에서 쓰레드 관련 작업은 Grand Central Dispatch API를 통해 처리한다 ##### 1. UI 업데이이트와 관련된 작업은 메인쓰레드에서 진행해야 함 ```swift= var imageView: UIImageView? = nil let url = URL(string: "https://bit.ly/32ps0DI")! // URL 세선은 내부적으로 비동기로 처리된 함수임 URLSession.shared.dataTask(with: url) { (data, response, error) in if error != nil { print("에러있음") } guard let imageData = data else { return } let photoImage = UIImage(data: imageData) // 이미지 표시는 DispatchQueue main 쓰레드로 보내줘야함 DispatchQueue.main.async { imageView?.image = photoImage } }.resume() ``` ##### 2. 비동기적인 작업은 함수를 설계를 할 때 리턴을 하면 안됨 - 콜백 함수를 통해 결과를 전달을 받아야 한다. 즉, 콜백 함수로 전달을 받아야 한다 (클로저 방식으로 리턴) - 올바른 비동기 함수의 설계 1) 잘못된 설계 ```swift= func getImages(with urlString: String) -> UIImage? { let url = URL(string: urlString)! var photoImage: UIImage? = nil URLSession.shared.dataTask(with: url) { (data, response, error) in if error != nil { print("에러있음 \(error!)") } // 옵셔널 바인딩 guard let imageData = data else { return } // 데이터를 UIImage 타입으로 변형 photoImage = UIImage(data: imageData) }.resume() return photoImage } getImages(with: "https://bit.ly/32ps0DI") // 무조건 nil을 리턴함 ⭐️ ``` 2) 올바른 설계 ```swift= func getImages(with urlString: String, completionHandler: @escaping (UIImage?) -> Void) { let url = URL(string: urlString)! var photoImage: UIImage? = nil URLSession.shared.dataTask(with: url) { (data, response, error) in if error != nil { print("에러있음 \(error!)") } // 옵셔널 바인딩 guard let imageData = data else { return } // 데이터를 UIImage 타입으로 변형 photoImage = UIImage(data: imageData) completionHandler(photoImage) }.resume() } getImages(with: "https://bit.ly/32ps0DI") { image in DispatchQueue.main.async { // UI 관련 작업처리 imageView?.image = photoImage } } ``` ##### 3. weak, strong 캡쳐의 주의 - 객체 내에서 비동기 코드 사용시 - 강한참조 : 캡쳐리스트 안에서 self로만 선언하면 강한 참조 1) 서로를 가리키는 경우 메모리 누수(Memory Leak) 발생 가능 2) 메모리 누수가 발생하지 않아도 클로저의 수명주기가 길어지는 지연이 발생될 수 있음 - 약한참조 : 대부분의 경우, 캡쳐리스트 안에서 weak self로 선언하는 것을 권장 (((((((((((((((메모리 부분 강의 다시 보자!!!!!!!))))))))))))))) ##### 4. 동기함수를 비동기적으로 동작하는 함수로 변형 하는 방법 ```swift= func longtimePrint(name: String) -> String { print("프린트 - 1") sleep(2) print("프린트 - 2") sleep(2) print("프린트 - 3") sleep(2) print("프린트 - 4") sleep(2) print("프린트 - 5") sleep(2) return "작업종료" } //longtimePrint(name: "잡스") func asyncLongtimePrint(name: String, completionHandler: @escaping (String) -> Void) { DispatchQueue.global().async { let n = longtimePrint(name: name) completionHandler(n) } } asyncLongtimePrint(name: "잡스") { result in print(result) } ``` ##### 5. 비동기적으로 이미 구현된 메서드는 따로 비동기적으로 구현할 필요없음 - 대표적으로 애플에서 제공하는 네트워크 관련 메서드 (URLSession.shared.dataTask) ##### 6. Async / await 도입 (swift 5.5 이후) - 비동기적인 함수에도 리턴형으로 가능함 (Pymaid of Doom 문제 해결) - async 키워드로 리턴형이 있는 함수를 설계하고, 실행하는 쪽에서 await 키워드 사용 ```swift= func processImageData() async throws -> Image { let dataResource = try await loadWebResource("dataprofile.txt") let imageResource = try await loadWebResource("imagedata.dat") let imageTmp = try await decodeImage(dataResource, imageResource) let imageResult = try await dewarpAndCleanupImage(imageTmp) return imageResult } ``` ##### 동시성 프로그래밍과 관련된 문제 1. 경쟁상황 : 같은 시점에 여러개의 쓰레드에서 하나의 메모리에 동시에 접근하는 문제 (Thread-safe) 2. 교착상태 : 배타적인 메모리 사용으로 일이 진행이 안되는 문제 (Dead Lock) - 일반적인 해결 방법 솔루션 : 동시큐에서 직렬큐로 보내자 ```swift= var array = [String]() let serialQueue = DispatchQueue(label: "serial") for i in 1...20 { DispatchQueue.global().async { print("\(i)") //array.append("\(i)") // 동시큐에서 실행하면 동시다발적으로 배열의 메모리에 접근 serialQueue.async { array.append("\(i)") } } } // 5초후에 배열 확인하고 싶은 코드 DispatchQueue.main.asyncAfter(deadline: .now() + 5) { print(array) } ``` ### 제네릭 (Generics) #### 제너릭 문법 : 한번의 구현으로 모든 타입을 처리하여 일반화 가능한 코드로 작성 - 제네릭의 개념이 없다면 함수를 모든 경우마다 다시 정의 해야 한다. - (인풋)타입만 다르고 구현내용이 완전이 동일할 때 굳이 코드를 반복할 필요가 없다 ### Result 타입 - 에러가 발생하는 경우, 에러를 따로 외부로 던지는 것(throws)이 아니라 리턴 타입 자체를 함수 실행의 성공과 실패의 정보를 함께 담아서 리턴하는 것 - 성공과 실패의 경우를 깔끔하게 처리가 가능한 타입 ```swift= enum Result<Success, Failure> where Failure : Error ``` - Result 타입은 열거형 1) case sucess (연관값) 2) case failure (연관값) ### 접근제어 - 코드의 세부 구현 내용을 숨기는 것ㅣ 가능하도록 만드는 개념(은닉화) - 접근수준 (Access Levels) 1) open : 다른모듈에서도 접근가능 / 상속 및 재정의 가능 2) public : 다른 모듈에서도 접근가능 / 상속 및 재정의 불가 3) internal : 같은 모듈 내에서만 접근가능 (디폴트) 4) fileprivate : 같은 파일 내에서만 접근가능 5) private : 같은 scope 내에서만 접근가능 - 대부분의 요소에서 접근제어를 가질 수 있다 - 기본원칙 : 타입의 접근제어 수준이 더 높은수준(넓게) 선언되어야 함 (함수도 동일) - 관습적인 패턴 : private으로 외부에 감추고 싶은 속성은 _ (언더스코어)를 사용 ```swift class SomeOtherClass { private var _name = "이름" // 쓰기 - private var name: String { return _name // 읽기 - internal } // func changeName(name: String) { // self.name = _name // } } // 위 코드의 간편한 패턴 class SomeAnotherClass { private(set) var name = "이름" // 읽기 - internal, 쓰기 - private } ``` - 커스텀 타입의 접근제어 : 타입 내부 멤버는 타입 자체의 접근 수준을 넘을 수 없음 - 타입자체를 private으로 선언하는 것은 의미가 없어짐 (아무곳에서도 사용을 못하기 때문) - 상속과 확장의 접근제어 : 상속 - Vertically, 확장 - Horizentally 로 이해하시라 - 저장/계산속성의 읽기와 쓰기의 접근 제어 수준을 구분해서 구현 가능 (이부분 다시보자) ```swift struct TrackedString { // var numberOfEdits = 0 // 외부에서 변경가능 // private var numberOfEdits = 0 // 읽기조차 불가능함 private(set) var numberOfEdits = 0 // setter에만 private 선언 // internal private(set) var numberOfEdits = 0 var value: String = "Start" { didSet { numberOfEdits += 1 } } } var stringToEdit = TrackedString() stringToEdit.value = "init" stringToEdit.value += " Add1" stringToEdit.value += " Add2" stringToEdit.value += " Add3" print("Number of Edit : \(stringToEdit.numberOfEdits)") print(stringToEdit.value) //stringToEdit.numberOfEdits = 3 // 외부에서 설정하는것 불가능 stringToEdit.numberOfEdits // 읽는것은 당연히 가능 ```