# 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 // 읽는것은 당연히 가능
```