## Swift에서 데이터 전달하는 6가지 방법 ### 1. 프로퍼티에 직접 접근(A → B) - 화면 전환 부분에서 전달 가능 ### 2. segue를 통해 전달(A → B) - 스토리보드를 통해 뷰를 구현했을 때 prepare(for: sender:) 라는 함수가 호출됨 - 이 때 prepare(for: sender:) 함수를 overriding을 하여 데이터 전달 ```swift // example override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.destination is SecondViewController { let viewController = sugue.destination as? SecondViewController viewController.text = self.segueTextField.text ?? "" } ``` ### 3. 프로퍼티와 함수를 이용해서 데이터 받기(B → A) - B에서 A에 대한 참조를 만들고 A에 속한 함수를 B에서 호출하는 방법 - 두 개의 뷰 컨트롤러 사이가 강력하게 결합된 상태 → 서로의 뷰 컨트롤러 관계가 상호적으로 묶여있는 형태 - 서로 함수에 의존해서 데이터를 전달하는 방식이기에 스파게티 코드가 초래 가능 - 나중에 유지 보수 할 때 알 수 없는 늪에 빠지게 됨 ### 4. Delegate 패턴(B → A) - 자신이 처리해야 하는 일 중 일부를 다른 객체에게 넘기는 것 ### 5. 클로저를 통해 전달 ### 6. NotificationCenter를 통해 전달 - Notification의 근본적인 원리는 - 지금 메모리에 올라와있는 객체 모두에게 신호를 보내고 - 혹시 해당 객체에서 같은 신호이름을 가진 옵저버가 존재한다면 - 데이터를 수신하는 방식 ## 싱글턴 패턴이란? - 싱글턴 패턴: 특정 용도로 객체를 하나만 생성하여 공용으로 사용하고 싶을 때 사용하는 디자인 패턴 ### 장점 - 인스턴스가 1번만 생성되기 때문에 메모리 낭비 방지 - 전역 인스턴스로 사용할 수 있기 때문에 메모리 및 자원관리 쉬움 - 메모리를 할당하고 초기화하는 시간이 줄어들어 객체 접근 시간이 줄어듬 ### 단점 - 생성자가 private이기 때문에 Mock 객체를 만들어내기 어려워 테스트가 힘듬 - 어느곳에서나 쉽게 접근이 가능해 의존성이 생김 - 멀티 스레드 환경에서 객체가 2개 생성되는 위험이 발생할 수 있음 - 인스턴스가 너무 많은 일을 하거나, 많은 데이터를 공유시킬 경우 다른 클래스의 인스턴스들 간 결합도가 높아져 개방 폐쇄 원칙을 위배함 ## 객체지향 프로그래밍(Object Oriented Programming) - 컴퓨터 프로그램을 어떤 데이터를 입력받아 순서대로 처리하고 결과를 도출하는 명령어들의 목록으로 보는 시각에서 벗어나 여러 독립적인 부품들의 조합, 즉 객체들의 유기적인 협력과 결합으로 파악하고자 하는 컴퓨터 프로그래밍의 패러다임 - 초기 프로그래밍 방식은 절차적 프로그래밍 방식이었음 입력을 받아 명시된 순서대로 처리한 다음, 그 결과를 내는 것 뿐이라는 생각이 지배적이었음 즉, 프로그램 자체가 가지는 기능에 대해서만 신경을 썼지, 이 프로그램이 대체 어떤 데이터를 취급하는 것인가에는 그다지 관심이 없었음 - 절차적 프로그래밍의 문제점 조금만 복잡해져도 순서도로 나타내는 것이 거의 불가능할 정도로 꼬여서 유지보수가 어려울뿐만 아니라 다른사람들이 보고 이해하는 것이 불가능 - 이 문제를 해결하기 위해 구조적 프로그래밍을 제안함 프로그램을 함수 단위로 나누고 함수끼리 호출하는 구조적인 방식을 제안하면서 이러한 위기를 벗어나게 됨 즉, 큰 문제가 있다고 가정하면 작은 문제들로 나눠서 해결하는 탑다운 방식이라고도 함 - 이러한 구조적 프로그래밍에도 문제가 있었음 함수는 데이터의 처리 방법을 구조화했을 뿐, 데이터 자체는 구조화하지 못함 - 이를 극복하기 위해 객체 지향 프로그래밍이 등자 큰 문제를 작은것으로 나누는 것이 아니라, 작은 문제들을 해결할 수 있는 객체들을 만든 뒤, 객체들을 조합하여 큰 문제를 해결하는 바텀업 해결법을 도입 ### 장점 - 프로그램을 보다 유연하고 변경이 용이하게 만들 수 있다는 점 - 코드의 변경을 최소화하고 유지보수를 하는 데 유리함 - 코드의 재사용을 통해 반복적인 코드를 최소화하고, 코드를 최대한 간결하게 표현 - 인간 친화적이고 직관적인 코드를 작성하기에 용이함 ### 객체 지향 프로그래밍의 4가지 특징 - 추상화 - 객체의 공통적인 속성과 기능을 추출하여 정의하는 것 - 즉, 사물들 간의 공통점만 취하고 차이점을 버리는 일반화를 통한 단순화 - 프로토콜을 사용하여 추상화 가능 - 캡슐화 - 은닉화 - 객체 상태를 나타내는 속성 정보를 private하게 관리하는 것 - private 접근제어자를 통해 은닉화 및 캡슐화 - 상속성 - 하나의 클래스의 특징을 다른 클래스가 물려 받아 그 속성과 기능을 동일하게 사용하는 것 - 재사용과 확장에 의미가 있음 - 상속은 수직확장, Extension은 수평 확장 - 다형성 - 쉽게, 다양한 형태로 나타낼 수 있는 형태라고 볼 수 있음 - 동일한 요청에 대해 다른 방식으로 응답할 수 있도록 만드는 것 - 이와 같은 다형성의 방식으로 오버라이디, 오버로딩을 지원 - 오버라이딩: 상위 클래스에서 상속 받은 메서드를 하위 클래스에서 필요에 따라 재정의 하는 것 - 동일 요청이 객체에 따라 다르게 응답 - 오버로딩: 동일한 이름의 메서드가 매개 변수의 이름, 타입, 개수 등의 차이에 따라 다르게 동작하는 것 - 동일 요청이 매개 변수에 따라 다르게 응답 ## SOLID ### SRP(Single Responsibility Principle) 단일 책임 원칙 - 클래스는 단 하나의 목적을 가져야 하며, 클래스를 변경하는 이유는 단 하나의 이유여야 함 ### OCP(Open Closed Principle) 개방 폐쇄 원칙 - 클래스는 확장에는 열려있고, 변경에는 닫혀있어야 함 ### LSP(Liskov Substitution Principle) 리스코프 치환 원칙 - 상위 타입의 객체를 하위 타입으로 바꾸어도 프로그램은 일관되게 동작해야 함 ### ISP(Interface Segregation Principle) 인터페이스 분리 원칙 - 클라이언트는 이용하지 않는 메서드에 의존하지 않도록 인터페이스를 분리해야 함 ### DIP(Dependency Inversion Principle) 의존 역전 법칙 - 클라이언트는 추상화(인터페이스)에 의존해야 하며, 구체화(구현된 클래스)에 의존해선 안됨 ## ARC vs 가비지 컬렉션 - 가비지 컬렉션은 자바, C#에서 사용되는 메모리 기법 - 둘의 가장 큰 차이점은 참조를 카운팅하는 시점 - `ARC`는 컴파일 시에, `가비지 컬렉션`은 프로그램 동작 중에 카운팅 - 가비지 컬렉션과 다르게 `ARC`는 컴파일과 동시에 인스턴스를 메모리에서 해제하는 시점을 결정하기 때문에 ARC를 이용해 원하는 방향으로 메모리 관리를 효율적으로 하기 위해서는 ARC에 명확한 힌트를 남겨야 함 ## MRC(Manual Reference Counting)란? - 예전 Objective-C는 retain, release, autorelease 등을 통해 수동으로 메모리 관리를 함 - retain → retain count(= reference count) 증가를 통해 현재 scope에서 객체가 유지되는 것을 보장 - release → retain count(= reference count)를 감소시킴 retain 후에 필요없을 때 release함 - ARC는 자동으로 retain, release를 삽입해서 retain count를 관리하고, 0이 될 때 deinit을 호출하여 메모리 해제를 시킴 ## retain count와 reference count의 차이 - retain count는 객체에 의해 내부적으로 유지되는 카운트 얼마나 많이 불균형적인 retain이 이 객체에 보내지고 있는지 임 - reference count는 외부적인 사실 얼마나 많은 객체들이 이 객체에 참조를 가지는지 - 메모리 관리의 목표는 이 두 숫자를 항상 같게 유지하는 것 ## weak 참조와 unowned 참조의 차이점은 무엇인가? ### 1. unowned 참조의 경우 항상 값이 존재할 것이라 가정 - 우선적으로 알아야 할 차이는 unowned 참조의 경우 언제나 값이 존재할 것이라고 가정한다는 점 - weak 참조의 경우에는 특정 인스턴스의 참조가 할당 해제된 경우에는 참조를 nil로 설정함 ### 2. weak 참조의 경우 항상 optional로 선언되기 때문에 해당 값에 접근하기 전에 언래핑 과정 필요 - weak 참조의 경우 nil로 설정될 수 있기 때문에 항상 옵셔널로 선언됨 - 이것이 weak 참조와 unowned 참조의 두 번째 차이점 - weak 참조의 값은 접근하기 위해 언래핑해야 하지만, unowned 참조의 경우에는 언래핑 없이 바로 접근이 가능 - 하지만 unowned 참조가 적용된 인스턴스에서 해당 unowned 참조가 할당 해제된 경우에도 해당 인스턴스는 nil로 설정되지 않기 때문에, 만약 해당 객체에 접근하려 할 경우 fatal error가 발생 ## Lazy란? - lazy 저장 변수는 처음 사용되기 전까지 초기값이 계산되지 않는 프로퍼티 사용자는 선언부에 lazy 수정자를 붙임으로써 lazy 저장 변수를 나타낼 수 있음 - 변수가 처음 요청되었을 때만 사용자가 지정한 함수를 사용하여 생성됨 - 만약 요청되지 않는다면 지정된 함수는 절대 불리지 않고 이는 processing time을 절약해줌 → 불필요한 성능저하나 공간 낭비를 줄일 수 있음 - 인스턴스 초기화가 완료될 때까지 초기 값이 검색되지 않을 수 있으므로 항상 lazy 프로퍼티를 변수로 선언해야 함 let 프로퍼티는 초기화가 완료되기 전에 항상 값이 있어야 하므로 lazy 프로퍼티로 선언할 수 없음 - lazy 키워드로 표시된 프로퍼티가 여러 스레드에서 동시에 접근되고 프로퍼티가 아직 초기화되지 않은 경우 프로퍼티가 한 번만 초기화된다는 보장이 없음 → 생성되지 않은 lazy 프로퍼티에 많은 스레드가 비슷한 시점에 접근한다면, 여러 번 초기화될 수 있음 ## 스위프트에서 프로토콜은 다중 상속이 되는데 왜 클래스는 다중 상속이 안되나? ## 질문 - **Copy On Write는 어떤 방식으로 동작하는지 설명하시오.** - Copy on Write은 Swift에서 매우 무거운 연산인 값 타입의 복사를 최적화 하는 방법 Swift의 기본 구현되어있는 컬렉션 타입이 COW를 활용하여 구현되어 있음 - Copy on Write는 Swift에서 ValueType이 복사될 때에는 실제로 복사하지 않고 원본 리소스를 공유하다가 Write 시점에 실제로 복사하여 불필요한 메모리 사용과 시간을 줄이기 위한 것 - ( = Copy On Write는 A라는 변수에 B라는 변수를 할당해주었을 때, 새로 메모리에 할당하는 것이 아니라, B의 메모리를 A가 공유하는 형태로 구성됩니다. 그러다가 A가 값이 수정될 때 새로 메모리에 할당이 되는 식으로 동작합니다.) - 사용자 정의 구조체 같은 값 타입의 경우 Copy-on-Write이 반영되어있지 않다. ## Swift에서 COW가 기본적으로 구현되어 있는 타입은? - Array, Dictionary, Set, String -> STD에 정의된 가변 길이를 가진 콜렉션 타입 ## 콜백함수와 이스케이밍 클로저의 차이 uIKit에서 많이 사용하는 이유는 커스터마이징이 쉬워서 그리고 그렇게 사용할 때 그걸 사용하는 객체가 어떤 것인지 모르기 때문에 유지보수가 편함 딜리게이트를 호출하는 코드 객체 안에서는 어떠한 데이터를 처리하는지 모르는게 단점 ## 싱글턴 단점 메모리가 올라가 있어서 안좋음 결합도가 높음 멀티스레딩 환경에서는 레이스 컨디션 문제 있음 어떻게 하나 액터를 사용하는법, NSLock ## Struct와 Class와 Enum의 차이를 설명하시오 - 기본적으로 Class는 참조 타입이며, Struct와 Enum은 값 타입입니다. - class는 단일 상속이 가능하며, Struct, Enum은 상속이 불가능합니다. - (optional) 이니셜라이저같은 경우, Struct는 지정된 이니셜라이저가 있기에, 모든 프로퍼티에 초기값을 주지 않아도 멤버와이즈 이니셜라이저를 통해 이니셜라이저가 자동 생성되지만, Class는 모든 프로퍼티에 초기값이 없다면 이니셜라이저를 직접 구현해줘야 합니다. Enum은 이니셜라이저를 만들 수 있지만, 필수가 아닙니다. ### Class - Class는 주로 변할 수 있는 기능을 객체로 만들 때 사용하는 타입입니다. Struct와 비교하면 참조타입이라는 점과 힙영역에 인스턴스를 저장하고 상속이 가능한 것 등의 특징이 있습니다. Class의 인스턴스를 다른 프로퍼티에 할당하게 될 경우, 힙 영역에 저장된 메모리 원본 주소 값을 포인터로 가리킵니다. 또한, 둘 중 하나의 값이 변경 될 경우 두 인스턴스에 모두 영향을 받는다는 특징을 가지고 있습니다. ### Struct - Struct는 기본적으로 불변하는 객체를 만들 때 사용하는 타입입니다. Class와 달리 값 타입이라는 점과 상속이 불가능하다는 점 등이 있습니다. 또한 스택 영역에 저장된 주소의 값 자체를 복사하기 때문에 다른 인스턴스에 할당하여도 Class와 달리 원본 값을 그대로 복사하는 특징을 가지고 있습니다. ### Enum - Enum은 관련 타입을 묶어서 표현하는데 주로 사용하고 안전한 방식으로 해당 값을 사용할 수 있습니다. - 미리 case 구문으로 값들을 나누어 놓았기 때문에 오타를 낼 일이 없으며, 각 case 별로 연관 값을 가지고 매개변수로 활용할 수 있습니다. - Enum은 Struct와 다르게 저장 프로퍼티는 가질 수 없습니다. - 또한 Class와 Struct처럼 이니셜라이저를 가질 수 있지만, 필수로 구현해야 하지는 않고 상황에 맞춰 작성해주면 됩니다. - Enum에 연관값을 주지 않으면 자동으로 Hashable 채택 ![스크린샷 2023-06-17 오전 11.13.29.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/bcb77bf5-0a78-4ddc-a0cc-3c6a57d97e2f/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA_2023-06-17_%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB_11.13.29.png) - https://developer.apple.com/documentation/swift/hashable/ ```swift enum Region { case korea case japan case usa } enum Food { case rice case hamburger case fish init(region: Region) { switch region { case .japan: self = .fish case .korea: self = .rice case .usa: self = .hamburger } } } Food(region: .usa) // hambuger enum MyEnum { case case1(String) case case2(Int) case case3(Bool) subscript(index: Int) -> Any { switch index { case 0: return self.case1("String value") case 1: return self.case2(123) case 2: return self.case3(true) default: fatalError("Invalid index") } } } let myEnum = MyEnum.case2(456) print(myEnum[1]) // 456 ``` ### Class vs Struct - **(값타입, 참조타입)**, **상속성**, **초기화**, **값타입 인스턴스 복사** - **불변성** - Immutable한 객체를 만들 때 Struct를 사용한다. - mutable한 객체를 만들 때 Class를 사용한다. ### Struct vs Enum 용도 - Struct는 변수 또는 상수를 그룹화하는 데 사용됩니다. - Swift에서 enum은 연관된 값 또는 멤버를 그룹화합니다. ## 값을 묶어서 정의하는 건 Struct도 가능한데 enum 타입을 쓰는 이유는? - Switch 구문을 사용하여 간결하게 작성할 수 있어 코드의 가독성을 높일 수 있습니다. case 구문을 사용하여 작성하기 때문에 휴먼 에러 방지를 할 수 있습니다. → Struct vs Enum - [optional]배열의 경우 nil 값이 들어있는 등 런타임 에러가 발생할 수 있지만, enum은 오류를 컴파일러가 잡아 주기 때문에 런타임시 앱이 크래쉬 나는 것을 방지할 수 있습니다. → Array vs Enum ## 메모리 영역에는 무엇이 있나요? 각각의 역할은 무엇인가요? - 프로그램이 실행될 때, 운영체제로부터 할당받은 메모리 공간이 있습니다. 이 메모리 공간을 메모리 영역이라고 합니다. - 메모리 영역은 크게 4가지로 나뉩니다. - 코드영역: 실행할 프로그램의 코드가 저장되는 영역입니다. 읽기 전용으로 실행중에 변경될 수 없습니다. - 데이터 영역: 전역 변수, 정적 변수, 배열 등의 데이터가 저장되는 영역입니다. 프로그램 실행 중에도 데이터 영역의 값은 변경될 수 있습니다. - 스택 영역: 지역변수, 매개 변수가 저장되는 영역으로, 함수 호출 시 매개 변수, 지역변수가 스택에 저장됩니다. 함수 반환시 자동으로 해제됩니다. - 스택은 함수 호출 시 생성되는 지역 변수나 함수의 인자값 등을 저장하는 메모리 영역으로, 메모리의 할당과 해제가 빠르고 쉽습니다. - 힙 영역: 동적할당을 위한 메모리 공간입니다. - 힙은 프로그램이 실행될 때 동적으로 메모리를 할당받는 영역입니다. 일반적으로 객체나 배열 등의 큰 데이터 구조를 저장하기 위해 사용됩니다. 힙은 메모리 공간이 동적으로 할당되며, 크기도 유동적입니다. 하지만 힙에서 메모리를 할당하거나 해제하는 과정은 스택에 비해 느리고 복잡합니다. ## Call Stack - Call Stack은 함수 호출 시 생성되는 지역 변수나 함수의 인자값 등을 저장하는 메모리 영역입니다. 스택이라는 자료구조를 사용하여 구현되며, 함수가 호출될 때마다 해당 함수의 정보(매개변수, 지역변수, 반환 주소 등)를 스택에 쌓아놓고, 함수가 반환될 때마다 스택에서 해당 함수의 정보를 꺼내어 처리합니다. 이 과정을 반복하여 Call Stack이라는 스택 구조를 만들게 됩니다. Call Stack은 함수 호출의 순서와 상관 없이, LIFO(Last-In-First-Out) 방식으로 동작합니다. ## Mutating 키워드에 대해 설명하시오 - 스위프트에서 값 타입은 기본적으로 인스턴스 메서드에서 내부 데이터를 수정할 수가 없습니다. 값 타입은 인스턴스 자체를 복사하여 접근을 하기 때문에 값 타입 내부 프로퍼티가 수정되어도 원본의 인스턴스는 변화하지 않기 때문에 수정할 수 없습니다. - 값 타입의 프로퍼티를 수정하려면 인스턴스 메서드에서 mutating 키워드를 붙여야 합니다. ### Swift Standard Library Collection Type - Array는 구조체이다. 기본적으로 Array 내의 대부분의 메서드들은 Mutating 키워드를 통해 변경 된다. - Copy-on-write가 적용된 것 - 사용자 정의한 struct도 내부에 정의한 프로퍼티들 또한 Struct라면 동일하게 COW가 적용되는 것 - Struct 내부에 클래스 인스턴스를 저장 프로퍼티로 소유하고 있다면, 직접 COW를 구현해줘야 합니다. ```swift final class Ref<T> { var val: T init(_ v: T) { val = v } } struct Box<T> { var ref: Ref<T> init(_ x: T) { ref = Ref(x) } var value: T { get { return ref.val } set { if !isKnownUniquelyReferenced(&ref) { ref = Ref(newValue) return } ref.val = newValue } } } ``` 1. 제네릭 타입의 프로퍼티를 가진 class 타입을 정의한다. 2. 위에서 정의한 class 타입의 프로퍼티를 가진 struct를 정의한다. 3. 그리고 struct에 value라는 계산 프로퍼티를 정의하여 getter과 setter을 가지게 한다. 4. getter이 불린 경우에는 그대로 값을 전달하고 5. setter이 불린 경우에는 내장 전역 함수인 isKnownUniquelyReferenced 함수를 사용하여 해당 객체의 참조 카운트가 1이면 true, 1보다 크면 false를 반환한다. - 객체의 참조 카운트가 2개 이상이면 다른 곳에서 사용되고 있다는 뜻이니까 새로운 복사본을 만들어서 수정도 같이 진행한다. - 객체의 참조 카운트가 1이면 그냥 수정한다. ## Copy-on-Write(COW) - Copy-on-Write은 Swift에서 매우 무거운(heavy) 연산인 값 타입의 복사를 최적화(Optimize)하는 방법. Swift의 원시 타입 열거형인 배열, 딕셔너리, 세트의 복사는 이 방식을 활용하여 구현되어 있다. - `Copy-on-Write`는 Swift에서 `ValueType`이 복사될 때에는 실제로 복사하지 않고 원본 리소스를 공유하다가  `Write` 시점에 실제로 복사하여 불필요한 메모리 사용과 시간을 줄이기 위한 것이다. - (= Copy On Write는 A라는 변수에 B라는 변수를 할당해주었을 때, 새로 메모리에 할당하는 것이 아니라, B의 메모리를 A가 공유하는 형태로 구성됩니다. 그러다가 A가 값이 수정될 때 새로 메모리에 할당이 되는 식으로 동작합니다.) - 사용자 정의 구조체 같은 값 타입의 경우 Copy-on-Write이 반영되어있지 않다. - (optional) Struct 내부에 클래스 인스턴스를 저장 프로퍼티로 소유하고 있다면, 직접 COW를 구현해줘야 합니다. 이 때, Ref 클래스를 만들어서 해당 클래스에 값의 참조 횟수를 저장하고, Box 구조체에서 Ref 클래스 인스턴스를 저장하는 방식으로 구현합니다. 이를 통해 Copy-on-Write가 적용됩니다. - (optional)Copy-on-Write(COW)의 단점은 동시성 처리에 있습니다. COW는 데이터 복사를 최대한 지연시켜서 메모리 사용량을 최적화하지만, 이 때문에 멀티 스레드 상황에서 원치 않은 결과가 발생할 수 있습니다. 여러 스레드에서 동일한 데이터를 수정하려고 할 때, 데이터를 복사하지 않고 원본 데이터를 공유하는 상황이 발생할 수 있습니다. 원본 데이터를 공유하는 상황이 값 복사되고 있다가, 데이터가 변경되어 새로운 값을 메모리에 할당 때, 여러 스레드에서 서로 다른 값을 가질 수 있기 때문이다. ### Cow의 한계 - 첫 수정 작업을 할 때는 실질적으로 복사를 하기에 이후 수정 작업보다 시간이 더 걸립니다. # OS에서의 Copy on write ```cpp std::string x("Hello"); std::string y = x; // x and y use the same buffer y += " World!"; // now y uses a different buffer // x still uses the same old buffer ``` 위와 같은 코드가 있다고 하자. ( C++ 98에서의 동작이다. C++ 11 이상에서는 동작하지 않음 ) !https://blog.kakaocdn.net/dn/ccswnf/btrnGIpdK7X/sTfZFKkhiBSdx2HCeo4Tb1/img.png **COW 예시** x라는 buffer에 "Hello" 라는 string을 넣고, y라는 복사본을 만든다고 하자. 이때 x와 y는 같은 buffer를 가리키게 된다. 하지만 이때 복사본인 y를 변경하면 아래와 같이 된다. !https://blog.kakaocdn.net/dn/X9i2B/btrnCAe2O0x/sMCG01SF0tn6dwfLwm9zK0/img.png **COW 예시** 더이상 y는 같은 buffer를 가리키지 않고, 새로운 buffer를 생성해 가리키게 된다. x가 가리키던 원래 데이터는 여전히 변질되지 않고 잘 보존되어 있다. 이처럼 Copy On Write는 **리소스가 복제되었지만 수정되지 않은 경우에 새 리소스를 만들 필요 없이 복사본과 원본이 리소스를 공유하고, 복사본이 수정되었을 때만 새 리소스를 만드는 리소스 관리 기법**을 말한다. OS에서의 COW는 fork() 기능과 관련이 깊다. fork()를 수행하면 자식 프로세스가 부모프로세스의 복사본이 된다. 그런데 이때 대부분 fork() 이후 exec()를 수행해 새로운 프로세스를 overwrite하게 된다. 즉, 복사를 하고 바로 exec()를 통해 새로운 프로세스를 만들게 된다. 이것은 overhead가 발생하게 되므로 비효율적이다. !https://blog.kakaocdn.net/dn/AfLNK/btrnBYmwjH3/pIblLRiS630doUqVKKXLL0/img.png OS에서의 COW 따라서 COW를 이용해 자식 프로세스가 같은 페이지를 공유하게 하면 된다. 만약 자식 프로세스에 수정이 일어난다면 그때 Copy를 수행하면 된다. 위와 같은 상황에서 만약 page C가 변경된다면, 아래와 같이 된다. !https://blog.kakaocdn.net/dn/bSRNel/btrnE89VwGC/yyKvr6zGKxPnTKKRhzpD0K/img.png OS에서의 COW page C의 복사본을 할당하고, 이를 가리키게 하면 된다. 이런식으로 내용이 바뀌지 않을 때까지는 페이지를 공유하고, 내용이 변경된다면 새로운 page를 할당해서 복사를 해주어 복사에 드는 비용(overhead)를 감소시킬 수 있다. 대부분의 OS에서 COW 방식을 채택한다. ## Data Race vs Race Condition Data race와 race condition은 모두 다중 스레드 환경에서 발생할 수 있는 문제입니다. 하지만 두 개념은 서로 다른 의미를 가지고 있습니다. Race condition은 여러 스레드가 동시에 공유된 자원에 접근하여, 예상치 못한 결과가 발생하는 상황을 말합니다. 이는 일반적으로 공유된 자원에 대한 동기화 문제로 발생하며, 예를 들어 두 개의 스레드가 동시에 같은 변수를 읽고 쓰는 상황에서 발생할 수 있습니다. Data race는 race condition의 한 종류로, 최소한 두 개의 스레드가 공유된 자원에 접근하며, 적어도 하나의 스레드가 해당 자원을 쓰는 상황에서 발생합니다. 이때 최소한 하나 이상의 스레드가 자원에 대한 접근을 제대로 동기화하지 않아 발생할 수 있습니다. 다시 말해, race condition은 여러 스레드가 동시에 공유된 자원에 접근하여, 예상치 못한 결과가 발생하는 상황을 말하며, 이를 방지하기 위해 동기화 문제를 처리해야 합니다. 반면, data race는 race condition 중에서도, 최소한 두 개의 스레드가 공유된 자원에 접근하며, 적어도 하나의 스레드가 해당 자원을 쓰는 상황에서 발생하는 문제를 말하며, 이를 방지하기 위해 동기화 처리가 필요합니다. ## URLSession에 대해 설명하세요 - 앱과 서버 간의 데이터를 주고받기 위해서는 HTTP 프로토콜을 이용해야 하는데, URLSession은 iOS 앱에서 서버와 통신하기 위해 애플이 제공하는 API이다. - HTTP를 포함한 몇 가지 프로토콜을 지원하고 인증, 쿠키 관리, 캐시 관리 등을 지원한다. - 기본적으로 request, response 구조를 가지고 있다. ## **URLSessionConfiguration의 종류** - `Default Session`: 기본적인 Session으로, 디스크 기반 캐싱을 지원하고 인증서를 사용자의 key chain에 저장합니다. 또한 쿠키도 저장합니다, - `Ephemeral Session`: 어떠한 데이터(cache, cookie, credential) 저장하지 않는 형태의 세션입니다. - `Background Session`: 앱이 종료된 이후에도 통신이 이뤄지는 것을 지원하는 세션입니다. ## **URLSessionTask의 종류** - dataTask(with:) → URLSessionDataTask - uploadTask(with:from:) → URLSessionUploadDataTask - downloadTask(with:) → URLSessionDownloadDataTask - streamTask(withHostName:port:) → URLSessionStreamTask - 하나의 세션당 여러개의 dataTask 생성이 가능하다. ![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/8363b8fa-b93c-47d2-8a36-5e09d2255db6/Untitled.png) ## **SessionDelegate 는 무엇인가요?** - Authentication 실패시 - 서버에서 데이터가 왔을 때 - 데이터가 캐싱이 가능해질 때 등 다양한 이벤트들을 처리할 수 있는 메서드를 제공한다. 업로드/다운로드 등의 진행상황을 실시간으로 받을 수 있다. ## **URLSession의 기본 Life Cycle** 1. Session configuration을 결정하고, Session을 생성한다. 2. 통신할 URL과 Request 객체를 설정한다. 3. 사용할 Task를 결정하고, 그에 맞는 Completion Handler나 Delegate 메소드들을 작성한다. 4. 해당 Task를 실행한다. 5. Task 완료 후 Completion Handler가 실행된다. ## **어떤 Thread에서 Task를 진행해야 하나요?** - URLSession API는 thread-safe하기 때문에 어떤 쓰레드를 사용하여도 무방합니다. [RESTful API란 무엇인가요? - RESTful API 설명 - AWS](https://aws.amazon.com/ko/what-is/restful-api/) ## RESTAPI에 대해 설명하세요 Representational State Transfer의 약자로서 두 컴퓨터 시스템이 인터넷을 통해 정보를 안전하게 교환하기 위해 사용하는 인터페이스이다. - 균일한 인터페이스 - 서버가 표준 형식으로 정보를 전송 - **무상태** - 서버가 이전의 모든 요청과 독립적으로 모든 클라이언트 요청을 완료하는 통신 방법 - 계층화 시스템 - 클라이언트는 클라이언트와 서버 사이의 다른 승인된 중개자에게 연결할 수 있으며 여전히 서버로부터도 응답을 받습니다. - 캐시 가능성 - 온디맨드 코드 - (optional) 클라인언트와 서버 분리 ## **REST 구성 요소에는 뭐가 있나요?** - 자원(Resource): URL > 모든 자원에 고유한 ID가 존재하고, 이 자원은 Server에 존재한다. 자원을 구별하는 ID는 ‘/groups/:group_id’와 같은 HTTP URI 다. Client는 URI를 이용해서 자원을 지정하고 해당 자원의 상태(정보)에 대한 조작을 Server에 요청한다. > - 행위(Verb): HTTP Method > HTTP 프로토콜의 Method를 사용한다. HTTP 프로토콜은 GET, POST, PUT, DELETE 와 같은 메서드를 제공한다. > - 표현(Representation of Resource) > Client가 자원의 상태(정보)에 대한 조작을 요청하면 Server는 이에 적절한 응답(Representation)을 보낸다. REST에서 하나의 자원은 JSON, XML, TEXT, RSS 등 여러 형태의 Representation으로 나타내어 질 수 있다. JSON 혹은 XML를 통해 데이터를 주고 받는 것이 일반적이다. > ## **API란 무엇인가요?** 애플리케이션 프로그래밍 인터페이스(API)는 다른 소프트웨어 시스템과 통신하기 위해 따라야 하는 규칙을 정의합니다. 개발자는 다른 애플리케이션이 프로그래밍 방식으로 애플리케이션과 통신할 수 있도록 API를 표시하거나 생성합니다. 예를 들어, 근무 시간 기록 애플리케이션은 직원의 전체 이름과 날짜 범위를 요청하는 API를 표시합니다. 이 정보가 수신되면 내부적으로 직원의 근무 시간 기록을 처리하고 해당 날짜 범위에서 근무한 시간을 반환합니다. 웹 API는 클라이언트와 웹 리소스 사이의 게이트웨이라고 생각할 수 있습니다. ### 클라이언트 클라이언트는 웹에서 정보에 액세스하려는 사용자입니다. 클라이언트는 API를 사용하는 사람이거나 소프트웨어 시스템일 수 있습니다. 예를 들어 개발자는 날씨 시스템에서 날씨 데이터에 액세스하는 프로그램을 작성할 수 있습니다. 또는 사용자가 날씨 웹 사이트를 직접 방문할 때 브라우저에서 동일한 데이터에 액세스할 수 있습니다. ### 리소스 리소스는 다양한 애플리케이션이 클라이언트에게 제공하는 정보입니다. 리소스는 이미지, 동영상, 텍스트, 숫자 또는 모든 유형의 데이터일 수 있습니다. 클라이언트에 리소스를 제공하는 시스템을 서버라고도 합니다. 조직은 API를 사용하여 리소스를 공유하고 보안, 제어 및 인증을 유지하면서 웹 서비스를 제공합니다. 또한 API는 특정 내부 리소스에 액세스할 수 있는 클라이언트를 결정하는 데 도움이 됩니다. ## **REST API의 장점은?** - 확장가능하고 유연한 API를 설계할 수 있습니다. - 무상태는 서버가 과거 클라이언트 요청 정보를 유지할 필요가 없기 때문에 서버 로드를 제거합니다. 잘 관리된 캐싱은 일부 클라이언트-서버 상호 작용을 부분적으로 또는 완전히 제거해 효율적입니다. 이러한 모든 기능은 성능을 저하시키는 통신 병목 현상을 일으키지 않으면서 확장성을 지원합니다. - RESTful 웹 서비스는 완전한 **클라이언트-서버 분리**를 지원합니다. 각 부분이 독립적으로 발전할 수 있도록 다양한 서버 구성 요소를 단순화하고 분리합니다. 서버 애플리케이션의 플랫폼 또는 기술 변경은 클라이언트 애플리케이션에 영향을 주지 않습니다. - REST API는 사용되는 기술과 독립성을 유지합니다. API 설계에 영향을 주지 않고 다양한 프로그래밍 언어로 클라이언트 및 서버 애플리케이션을 모두 작성할 수 있습니다. ## **RESTful API 클라이언트 요청에는 무엇이 포함되어 있나요?** - RESTful API에는 다음과 같은 주요 구성요소를 포함하는 요청이 필요합니다. - **고유 리소스 식별자** 서버는 고유한 리소스 식별자로 각 리소스를 식별합니다. REST 서비스의 경우 서버는 일반적으로 URL(Uniform Resource Locator)을 사용하여 리소스 식별을 수행합니다. URL은 리소스에 대한 경로를 지정합니다. URL은 웹페이지를 방문하기 위해 브라우저에 입력하는 웹 사이트 주소와 유사합니다. URL은 요청 엔드포인트라고도 하며 클라이언트가 요구하는 사항을 서버에 명확하게 지정합니다. - **메서드** 개발자는 종종 Hypertext Transfer Protocol(HTTP)을 사용하여 RESTful API를 구현합니다. HTTP 메서드는 리소스에 수행해야 하는 작업을 서버에 알려줍니다. GET, POST, PUT, DELETE ## **REST API의 기본 수행 기능** - Create: `POST` 요청을 사용하여 리소스를 생성 - Read: `GET` 요청을 사용하여 리소스를 검색 - Update: `PUT` 요청을 사용하여 리소스를 업데이트 - Delete: `DELETE` 요청을 사용하여 리소스를 삭제 **HTTP 헤더** 요청 헤더는 클라이언트와 서버 간에 교환되는 메타데이터입니다. 예를 들어, 요청 헤더는 요청 및 응답의 형식을 나타내고 요청 상태 등에 대한 정보를 제공합니다. - 데이터 REST API 요청에는 POST, PUT 및 기타 HTTP 메서드가 성공적으로 작동하기 위한 데이터가 포함될 수 있습니다. - 파라미터 RESTful API 요청에는 수행해야 할 작업에 대한 자세한 정보를 서버에 제공하는 파라미터가 포함될 수 있습니다. 다음은 몇 가지 파라미터 유형입니다. URL 세부정보를 지정하는 경로 파라미터. 리소스에 대한 추가 정보를 요청하는 쿼리 파라미터. 클라이언트를 빠르게 인증하는 쿠키 파라미터. ## **Response로 돌아오는 상태코드를 종류별로 설명해주세요** `200`: 일반 성공 응답 `201`: POST 메서드 성공 응답 `400`: 서버가 처리할 수 없는 잘못된 요청 `404`: 리소스를 찾을 수 없음 `5XX`: 서버 오류 ## 멱등성 > **연산을 여러 번 하더라도 결과가 달라지지 않는 성질**을 뜻함. > HTTP 메서드 멱등성이란, 동일한 요청을 여러 번 수행해도 같은 결과가 나오는 것을 의미합니다. 즉, 멱등성이 보장되면 한 번의 요청 실패는 다시 시도하여도 같은 결과를 얻을 수 있습니다. 멱등성을 보장하는 HTTP 메서드는 다음과 같습니다. - GET: 서버에서 데이터를 가져오기만 하므로 멱등성이 보장됩니다. - PUT: 리소스를 업데이트하는 데 사용됩니다. 동일한 데이터로 여러 번 호출해도 결과가 동일합니다. - DELETE: 리소스를 삭제하는 데 사용됩니다. 이미 삭제된 리소스를 다시 삭제하려고 하면 실패합니다. - HEAD: GET과 동일하지만 본문을 반환하지 않으므로 멱등성이 보장됩니다. - OPTIONS: 서버가 제공하는 옵션 목록을 가져오는 데 사용됩니다. 멱등성이 보장됩니다. 멱등성이 보장되지 않는 HTTP 메서드는 다음과 같습니다. - POST: 새로운 리소스를 생성하는 데 사용됩니다. 동일한 데이터로 여러 번 호출하면 다른 결과가 생성됩니다. - PATCH: 리소스의 일부를 업데이트하는 데 사용됩니다. 동일한 데이터로 여러 번 호출하면 다른 결과가 생성됩니다. - 앱이 실행 중이지만 이벤트를 받지 않는 상태 - 다른 상태로 넘어가기 전에 앱은 반드시 이 상태를 거침 - 전화나 메시지 같은 인터럽트가 발생하면 InActive 상태가 됨 - 미리알림 같은 특정 알림창이 화면을 덮어서 앱이 실질적으로 이벤트를 받지 못하는 상황이 여기에 해당함. ## 앱이 foreground에 있을 때와 background에 있을 때 어떤 제약사항이 있나요? - Foreground 는 애플리케이션이 클라이언트에게 보여지고 있는 상태로 메모리 및 시스템 리소스에 높은 우선 순위를 가지고 있습니다. - background 는 앱이 백그라운드에 있지만 여전히 실행되고 있는 상태를 말합니다. 제약사항으로는 가능한 가장 작은 메모리공간을 사용하도록 해야하며 사용자의 이벤트를 받기 어렵다는 특징이 있습니다. - Audio, AirPlay, PIP(Picture in Picture) - 사용자 위치 업데이트 - Voice over IP - 외부 악세서리와의 통신 - 블루투스 LE(Low Energy)와 통신, 혹은 디바이스를 블루투스 LE 악세서리로 변환 - 서버에서의 정기적인 업데이트 - RemoteNotification(Apple Push Notification 지원) - Background Processing ## 앱이 In-Active 상태가 되는 시나리오를 설명하시오 - 앱이 실행 중이지만 이벤트를 받지 않는 상태입니다. - 다른 상태로 넘어가기 전에 앱은 반드시 이 상태를 거칩니다. - 전화나 메시지 같은 인터럽트가 발생하면 InActive 상태가 됩니다. - 미리알림 같은 특정 알림창이 화면을 덮어서 앱이 실질적으로 이벤트를 받지 못하는 상황이 여기에 해당합니다. ## App의 Not running, Inactive, Active, Background, Suspended에 대해 설명하시오 [Managing your app’s life cycle | Apple Developer Documentation](https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle) ![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/24f43f89-a63c-49f6-bdf7-169d2d955aef/Untitled.png) ![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e6ca788c-23c2-42b9-bc1d-36a81788ff45/Untitled.png) - Not Running - 앱이 아직 실행 전인 상태 - Inactive - 앱이 실행되고 화면에 표시되는 상태이지만, 이벤트를 받지 못하는 상태 - Interruption(잠금, 전화, 멀티태스킹 등)으로 발생하거나 background에서 active로 상태 변환 시 이 과정을 거친다. -> 전화가 일부로 가려짐 - Active - 앱이 foreground 상태이며, 이벤트를 받는 상태 - Background - 앱이 background 상태에서 실행되고 있는 상태 - Suspended - 앱이 background 상태에 있으며, 실행을 멈추고 있는 상태 ```swift optional func applicationWillTerminate(_ application: UIApplication) ``` **Background -> Suspended는 알 지 못한다.** - 앱이 Suspended 상태에 있을 때 앱의 상태 변화를 직접적으로 감지할 수는 없습니다. Suspended 상태에서 Active 상태로 돌아가려면 사용자가 앱을 다시 실행하거나 푸시 알림 등의 외부 이벤트가 발생해야 합니다. **active -> inactive -> background -> inactive -> active** ## **앱 델리게이트와 씬 델리게이트의 차이는?** 앱 델리게이트와 씬 델리게이트의 차이는 다음과 같습니다. - 앱 델리게이트 - 앱 전체의 라이프 사이클을 관리합니다. - 앱이 시작되거나 종료될 때, 백그라운드나 포그라운드로 전환될 때 등 앱의 상태 변화를 감지합니다. - 주요한 앱 이벤트에 대한 응답을 처리합니다. - 씬 델리게이트 - 앱 화면 내에서 다른 씬(뷰 컨트롤러의 형식)으로 전환되는 경우, 현재 씬의 상태를 저장하고 다음 씬의 상태를 설정합니다. - 예를 들어, 게임에서 한 스테이지에서 다른 스테이지로 전환하는 경우, 현재 스테이지에서의 점수 등을 저장하고 다음 스테이지에서의 초기 상태를 설정합니다. **iOS13 이후** iOS 13부터는 씬 델리게이트가 없어졌습니다. 대신, 씬 연결 및 트랜지션을 처리하는 SceneDelegate 클래스가 추가되었습니다.