[면접 준비] iOS + UIKit

김상우·2023년 4월 19일
16

개발자 면접 준비

목록 보기
1/6

💁🏻‍♂️ iOS 개발자를 위한 면접 준비 - [iOS + UIKit] 편

🙏🏻 민령이와 함께 정리한 글


🍏 앱 디자인 패턴

1. MVVM

💁🏻‍♂️ : MVVM 패턴에 대해 설명해주세요

  1. Model / View / View Model 로 이루어진 디자인 패턴입니다.

  2. MVVM 패턴의 목표는 뷰 로직과 비즈니스 로직을 분리하는 것입니다.

  3. View 에서는 뷰 로직을 다룹니다. 사용자 입력을 받습니다.

  4. ViewModel 에서는 View 를 위한 상태와 메서드를 정의하고 View 는 ViewModel 의 상태변화를 옵저버 패턴을 통해 관찰합니다. 이를 데이터 바인딩이라고 합니다.

  5. ViewModel 은 View 가 쉽게 사용할 수 있도록 Model 의 데이터를 가공해서 View 에게 전달합니다.

  6. Model 은 데이터와 비즈니스 로직을 포함합니다. 데이터베이스, 네트워크 API 에서 데이터를 가져오고, 처리 및 저장하는 책임을 가집니다.

  7. View 와 ViewModel은 N:1의 관계를 맺을 수 있습니다.

  • MVVM 장점
  1. 뷰 로직과 비즈니스 로직의 책임을 분리했기 때문에 테스트 및 유지 보수가 유용합니다.

  2. 개발자와 디자이너가 병렬적으로 작업이 가능하다. UI 디자인이 나오지 않았더라도 미리 정의된 뷰와 뷰 모델을 먼저 개발할 수 있습니다.

  3. 개발을 할때는 수정하기 좋은 코드를 짜는 것이 좋은데, MVVM 패턴을 통해서 코드 변경을 최소화하며 수정할 수 있습니다.

  4. View: ViewModel = N: 1 의 구조가 가능합니다.


2. 옵저버 패턴

💁🏻‍♂️ : 옵저버 패턴에 대해 설명해주세요

  1. 옵저버 패턴(Observer Pattern)은 객체 간의 의존성을 줄이고, 객체 간의 상호작용을 느슨하게 만들기 위한 디자인 패턴 중 하나입니다.

  2. 옵저버 패턴이란, 어떤 객체의 상태가 변화할 때 그를 관찰하는 구독자들에게 이벤트를 발생시켜주는 디자인 패턴입니다. 이 때 이벤트를 발행하는 객체를 Publisher, 구독자를 Observer 라고 합니다.

  3. 옵저버 패턴을 사용함으로써 객체지향의 Open-Close 원칙 (개방 폐쇄 원칙)을 지킬 수 있습니다. (옵저버 패턴의 장점)

💁🏻‍♂️ → 왜죠?

  1. Publisher 와 Observer 가 인터페이스를 통해 느슨하게 결합되어 있기 때문입니다.

  2. Observer 는 Publisher 가 누구인지 알고 있어야 하지만, Publisher 는 구체적으로 구독자를 알지 못해도, Observer 라는 인터페이스를 지키는 객체들이 있다는 사실 정도만 알고 있어도 됩니다.

  3. Observer interface 를 사용하면, 새로운 타입의 구독자가 생겼을 때 Oberver interface 를 준수하는 형태로 구현하기만 하면 기존 코드가 돌아가는데 문제가 없습니다. (기능 확장에 열려있다)

  4. Observer 들의 특성이 변경될일이 생겼다고 했을 때, 일일히 클래스를 수정할 필요가 없이 interface 통해 효율적으로 수정을 할 수 있습니다. (기존 코드의 변경에 닫혀있다)


3. 애플의 MVC / MVVM

💁🏻‍♂️ : 애플은 Swift 라는 언어를 설계할 때 MVC 를 추구하며 설계했습니다. UIKit 에서 ViewController 라는 노골적인 단어를 사용하는 것만 봐도 알 수 있죠. 왜 그렇게 했을까요?

  1. 여러가지 변형된 형태의 MVC 들이 있었지만 애플이 추구한 MVC 는 명확한 방향성이 있었습니다.

  2. 그건 바로 View 와 Model 의 완전한 분리입니다. 다시말해, 뷰와 모델이 소통하기 위해서는 반드시 ViewController 를 거쳐야 한다는 특징입니다.

  3. 이렇게 한 이유는 'UI 재사용성을 극대화'하기 위해서입니다. 뷰에 특정한 로직이 들어가거나, 모델에서 직접 데이터를 받아오거나 가공하게 한다면 뷰의 재사용성은 낮아질 것입니다.

  4. 뷰는 그저 어떻게 화면에 그릴지만 담당하고, 셀이 눌리면 어떤 작업을 할지, 셀 안에 어떤 텍스트를 넣어야 할지와 같은 것들은 Delegate, DataSource 프로토콜을 활용해서 UIViewController 에게 위임합니다.

  5. 덕분에 그래서 UITableView, UICollectionView 와 같은 UI컴포넌트들은 거의 모든 iOS 앱에서 재사용됩니다. 괜찮은 커스텀 뷰를 제작한다면 서로 다른 앱 에서도 재활용할수도 있습니다. 하지만 ViewController 의 재사용성은 매우 낮습니다.

💁🏻‍♂️ +) 그럼에도 불구하고 개발자들은 왜 설계 역사를 거스르고 MVVM 패턴을 많이 사용하는 것일까요?

  1. 애플의 MVC 에는 문제점이 있었습니다. 그건 View 와 ViewController 가 너무 강하게 결합된다는 것입니다. UIKit 프레임워크를 사용하는 이상, ViewController 는 매우 중요한 역할을 담당할 수 밖에 없습니다.

  2. ViewController 가 직접 View 를 생성하는데다가, View 의 대부분 로직을 ViewController 에게 위임하기 때문에, 결합도가 매우 높을 수 밖에 없습니다. 프레젠테이션 로직이 결합되어있으면 유닛 테스팅을 할 때도 불편함으로 다가옵니다.

  3. 그래서 MVVM 패턴 에서는 View 와 ViewController 를 묶어 'View'로 간주합니다. MVVM 의 목표는 뷰 로직과 비즈니스 로직을 분리하는 것이고, 이는 테스팅 및 유지 보수에 더 유리한 패턴이 됩니다.


4. MVP vs MVVM

💁🏻‍♂️ : MVP 와 MVVM 의 차이점은 무엇인가요?

  1. 가장 큰 차이점은, MVVM 에서는 옵저버 패턴을 활용한 View Data Binding이 일어난다는 것입니다.

  2. MVP 에서는 View 와 Presenter 사이에 의존성이 있었지만, MVVM 은 상대적으로 View, ViewModel, Model 모두 의존성이 낮습니다.

📌 애플의 MVC 와 애플의 MVP 는 비슷하게 동작하지만 이런 구조적 차이가 있다.
- MVC 의 V 는 (View) 이고 C 는 (ViewController)이다.
- MVP 의 V는 (View + ViewController)이고 P (Presenter) 가 따로 존재.
- ViewController 와 Presenter 는 같은 역할 -> View 와 Model 의 분리. 다리 역할.

🤔 그럼 MVC 랑 MVP 는 다를게 뭔데 ?
- 사실 애플의 MVC 와 애플의 MVP 는 구현상의 차이 말고는 큰 차이가 없다. 
- MVP 의 Presenter 에서 단지 UIViewController 의 부담을 덜어줄 수 있다는 정도.
- 전통적인 MVC (애플의 MVC 말고) 는 View 와 Model 사이에 결합성이 존재했다.
- MVP 는 그래서 전통적인 MVC 의 단점을 해결하기 위해서 등장했다.
- View 와 Model 사이의 결합성은 해결했지만, 이로 인해 View 와 Presenter 사이의 결합성이 생겼다.

5. 디자인 패턴 정의

💁🏻‍♂️ : 디자인 패턴이란 무엇이고, 왜 사용하나요?

  • 디자인 패턴이란 일반적인 개발 과정에서 자주 발생하는 문제를 해결하기 위한 개발자들의 교과서입니다.

  • 사용 이유

  1. 이미 증명된 솔루션이기 때문에 개인적으로 생각해낸 해결방안보다 사용할 근거가 충분합니다.

  2. 약속되어있기 때문에 아예 다른 사람이 처음 코드를 보게 되더라도, 디자인 패턴에 대한 이해만 있다면 수월하게 코드를 이해할 수 있습니다.


🍏 iOS 프로그래밍

6. 메모리 관리

💁🏻‍♂️ 6-1 : 메모리 릭에 대해 설명해주세요

  • 메모리 릭 (= 메모리 누수) 이란 사용되고 있지 않는 인스턴스가 메모리에서 해제되지 않아 메모리 공간이 낭비되는 현상입니다.

💁🏻‍♂️ 6-2 : 순환 참조에 대하여 설명해주세요

  1. 두 가지 객체가 서로에 대한 강한 참조 상태를 가질 때 순환 참조라고 합니다.

  2. 순환 참조가 발생하면 메모리 누수 현상이 발생하기 때문에 약한 참조를 통해 이를 해소시켜줘야 합니다.

💁🏻‍♂️ 6-3 : 강한 참조와 약한 참조에 대하여 설명해주세요

  1. 강한 참조는 reference count 를 증가시키고, 약한 참조는 reference count 를 증가시키지 않습니다.

  2. 일반적인 참조 방법이 강한 참조 방법이고, weak / unowned 등의 키워드를 사용해서 약한 참조를 할 수 있습니다.

💁🏻‍♂️ 6-4 : Retain Count 방식에 대해 설명해주세요

  1. ARC 에서 사용하는 메모리 관리 방식을 Retain Count 방식이라고 합니다.

  2. 메모리에서 reference count 를 증가시키는 것을 retain, 감소시키는 것을 release 라고 합니다.

💁🏻‍♂️ 6-5 : weak 과 unowned 를 비교 설명해주세요

  1. weak 는 참조 대상이 메모리에서 해제되었을 때 nil 설정되는 optional 이지만, unowned 는 참조 대상이 메모리에서 해제되었을 때 nil 이 되지 않는 non-optional 이고, 에러를 발생합니다.

  2. unowned 는 객체를 직접 가리키지만, weak 는 side table 을 거쳐서 객체를 가리킵니다.

  3. 그렇기 때문에 unowned 키워드를 사용할 경우, strong reference count == 0 이 되어버리면 dangling pointer 가 되어 오류가 생기기 때문에 인스턴스의 해제 시점을 예민하게 고려해줘야 합니다.

  4. weak 는 weak reference 를 증가시키고 unowned 는 unowned reference 를 증가시킵니다.

💁🏻‍♂️ 그러면 side table 이 무엇이고 reference count 종류에 대해 자세히 설명해보실래요

  1. side table 은 HeapObject 에서 weak rc 를 관리하기 위해 별도로 생성된 자료구조 입니다. weak 참조를 하게 되면 자동으로 side table 이 생성됩니다.

  2. side table 은 Swift4 이전의 좀비 오브젝트 문제를 해결하기 위해 도입되었습니다.

  3. reference count 에는 3가지 종류가 있습니다. strong / unowned / weak. HeapObject struct 내부에서는 이 3가지에 대한 rc 들을 모두 카운팅합니다.

  4. strong rc == 0 이 되면 인스턴스가 deinit 상태가 됩니다. 이는 메모리가 dealloc 된 상태는 아닙니다.

  5. unowned rc == 0 이 되면 dealloc 상태가 되어 완전히 메모리에서 해제됩니다.

  6. dealloc 이 되었어도 weak rc != 0 이라면 side table 이 메모리에 남아있습니다. 이를 freed 상태라고 합니다.

  7. weak rc == 0 이 된다면 비로소 객체는 완전히 죽어 DEAD 상태가 됩니다.

💁🏻‍♂️ 좀비 오브젝트가 뭔데요?

  1. strong rc == 0 이지만 weak rc > 0 일 경우, 메모리에서 완전한 해제가 일어나지 않은 오브젝트를 말합니다.

  2. 런타임에 weak reference 를 통해서 좀비 오브젝트가 해제 될 수 있지만 비효율적입니다.


💁🏻‍♂️ 6-6 : 메모리를 힙과 스택으로 나누는 이유는 무엇일까요?

  1. 메모리에서 힙 영역과 스택 영역은 각자 역할이 다릅니다. 힙 영역은 프로그램 실행 중 동적으로 메모리를 할당하는 공간이고, 스택 영역은 함수 매개변수나 지역변수 등 실행 중이 아닌 정적으로 메모리를 할당하는 공간입니다.

  2. 힙 영역의 메모리 공간의 할당과 해제는 개발자가 직접 관리해주어야 하고, 스택 영역의 메모리는 append 와 pop 을 반복하며 자동으로 관리됩니다.

  3. 상황별로 처리해주어야 하는 메모리의 역할과 종류가 다르기 때문에 힙 영역과 스택 영역을 나누어 설계함으로써 효율적인 메모리 관리를 할 수 있게 한 것입니다.


7. ARC / MRC / GC

💁🏻‍♂️ 7-1 : ARC, MRC란 무엇인지 설명해주세요

  1. ARC 와 MRC 는 모두 힙의 메모리를 관리하는 기법입니다.

  2. ARC 는 Automatic. 자동으로 HeapObject 안에 들어있는 Reference Count를 계산해서 메모리를 관리 해주는 방법입니다.
    Java의 GC와는 다르게 컴파일 시점에 실행됩니다.

  3. MRC 는 Manual. 수동으로 Reference Count를 계산합니다. Retain, release 메서드를 직접 작성해줘야합니다. Objective-C 에서 사용합니다.

🧐
- 동적 할당으로 인스턴스가 생성되면 해당 정보는 HeapObject 라는 struct로 관리됩니다.
- HeapObject 에는 동적 할당되는 객체를 구성하는 데이터 즉 reference count 와 type meta data 를 갖습니다.

💁🏻‍♂️ 7-2 : ARC는 compile time에 실행되는데 어떻게 동적으로 실행되는 것들의 reference count를 세고 메모리 관리를 할 수 있나요 ?

  1. ARC 는 compile time 에 자동으로 retain 과 release 코드를 적절한 위치에 삽입해줍니다. MRC 에서 수동으로 작성하던 메서드를 자동으로 작성해줍니다.

  2. Run time에 코드가 실행되다가 retain/release 에 의해 count 가 0이 되면 메모리를 해제합니다.


💁🏻‍♂️ 7-3 : ARC와 GC는 어떤 차이점이 있나요?

  1. ARC는 컴파일 타임에 실행되고, GC는 런타임에 실행됩니다.

  2. 이에 따라 ARC는 런타임 성능이 좋아진다는 장점을 갖지만, 앱 실행중 순환참조 발생시 매우 치명적이라는 단점을 갖습니다.

  3. GC는 런타임에 참조를 계속 추적하는 리소스가 필요하다는 단점을 갖지만, ARC에 비해 치명적인 메모리 누수를 막을 확률이 높다는 장점을 갖습니다.

😀 ARC와 GC는 모두 힙메모리를 관리한다는 공통점을 갖습니다.

💁🏻‍♂️ 7-4 : ARC가 메모리 해제를 할 수 없는 상황에 대해 설명해보세요. GC는 해결할 수 있을까요?

  1. ARC의 경우 단순히 컴파일 타임에 reference counting 을 통해 메모리 관리를 하기 때문에 순환참조가 발생할 경우 메모리 해제를 하기 어렵습니다.

  2. GC는 런타임에 Mark-and-Sweep 방식으로 모든 인스턴스를 체크합니다. 따라서 순환참조가 발생하더라도 메모리를 해제할 수 있습니다.

    • Mark : 메모리 영역을 탐색하며 사용중인 객체를 표시
    • Sweep : 표시 되지 않은 객체를 메모리에서 제거

8. Delegate 패턴

💁🏻‍♂️ 8-1 : Delegate 패턴에 대해 설명해주세요

  1. Delegate 패턴은 객체 간의 커뮤니케이션을 위한 디자인 패턴 중 하나입니다. 이 패턴은 객체 간의 결합도를 낮추고, 유연한 앱 구조를 구현하는데 도움을 줍니다.

  2. Delegate 패턴은 어떤 객체 A가 해야 하는 일을 다른 객체 B에서 부분적으로 확장해서 대신 처리하게 합니다.

  3. 프로토콜에 정의된 내용을 누군가 대신해주길 바라며 떠넘깁니다 (delegating). 그리고 일을 위임받은 자 (delegated) 는 delegate = self 해서 그 일을 구체적으로 대신해줍니다.

  4. 델리게이트 패턴을 사용함으로써 느슨한 결합을 가질 수 있습니다. 프로토콜을 만족하기만 하면 그 누구든 일을 대신해줘도 상관 없다는 뜻인데, 이는 요구 사항이 변경될 경우 작업을 매우 쉽게 수정할 수 있다는 뜻과도 같습니다. 다른 객체로 교체해도 잘 동작하는 코드를 짤 수 있습니다.

  5. 그리고 델리게이트 패턴은 책임 분리를 촉진합니다. 기능을 위임할 수 있는 객체가 있다는 것은 그만큼 현재 객체에서 책임할 부분이 적어진다는 뜻이기 때문입니다.

  • 애초에 여기서 예를 들어버려서 설명해도 좋을 것 같다. ex) UITextViewDelegate
// 날 수 있는 새.
protocol Bird {
    func fly()
}
// 날아다니는 걸 보여주는 서커스.
class Circus {
    // 누구든 좋으니 날 수 있는 놈 아무나 날아봐. 해줘 ~
    // 누군가 날아주길 기대하며 떠넘긴다. (delegating)
    var delegate: Bird?
    
    func showFlying() {
        // 독수리인지 비둘기인지는 중요치 않아. 걍 아무나 날기만 해줘.
        delegate?.fly()
    }
}
// 독수리
class Eagle: Bird {
    init(circus: Circus) {
        // 내가 날아줄게 (delegated)
        circus.delegate = self
    }
    
    func fly() {
        print("쌔애앵")
    }
}

// 비둘기
class Pigeon: Bird {
    init(circus: Circus) {
        // 내가 날아줄게 (delegated)
        circus.delegate = self
    }
    
    func fly() {
        print("구구구")
    }
}

var someCircus = Circus()
var somePigeon = Pigeon(circus: someCircus)

// 만약 참새라는 클래스가 새로 생겨서 들어와도 좋아. 코드 수정이 필요가 없으니까.
someCircus.showFlying()


💁🏻‍♂️ 8-2 : Delegate 패턴을 활용하는 경우를 예를 들어 설명해주세요.

  1. UITextViewDelegate 의 textViewDidBeginEditing(). 텍스트 뷰의 편집을 감지하는 메서드

  2. UIScollViewDelegate 의 viewDidScroll(). 스크롤 감지. offset 리턴

  3. UICollectionViewDelegate 의 didSelectItemAt. 셀이 선택되었음을 감지.

  4. UITableViewDelegate 의 셀을 선택하거나 스크롤할 때 알림을 받음.

// 예를 들어 이런식으로 사용된다.

class MyViewController: UIViewController, UITextViewDelegate {
    var myTextView = UITextView()
    let placeholder: String = "입력해주세요."
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 핵심
        myTextView.delegate = self
    }
    
	// textview에 focus를 얻는 경우
    func textViewDidBeginEditing(_ textView: UITextView) {
        if myTextView.text == placeholder {
            myTextView.text = nil
            myTextView.textColor = .black
        }
    }
}

// 그리고 UITextView 클래스를 까보면 다음과 같이 delgate 변수가 있다.
@available(iOS 2.0, *)
open class UITextView : UIScrollView, UITextInput, UIContentSizeCategoryAdjusting {
...    
    weak open var delegate: UITextViewDelegate?
...

// 즉 myTextView 가 delegating 한 것이고(= Circus), 
// MyViewController 가 delegated 된 것이다(= Eagle)
// MyViewController 는 UITextViewDelegate 를 채택했기 때문에 위임받은 일을 할 수 있다.
// myTextView 에서 UITextViewDelegate 가 하는 기능을 확장시키고 싶었고,
// 그 확장한 일을 MyViewController 에서 받아, 대신 처리한 것이다.

💁🏻‍♂️ 8-3 : Delegate 패턴과 Escaping Closure 둘다 데이터를 전달할 수 있는데 각각 어떤 상황에 좋을까요?

  1. Delegate 패턴은 프로토콜을 사용하기 때문에 좀 더 엄격하게 행동을 규정할 수 있습니다.

  2. Delegate 패턴은 중간에 쓰레드가 교체되지 않고 Escaping Closure 는 쓰레드가 교체됩니다. 따라서 쓰레드가 교체되지 않는 것을 바란다면 Delegate 패턴을 쓰는 것이 좋습니다. (컨설팅 때 들은 내용인데 구글링 해도 못찾겠음)


💁🏻‍♂️ 8-4 : Delegate Pattern과 Notification의 동작 방식의 차이에 대해 설명 해주세요.

  1. Delegate 패턴은 다른 객체의 인스턴스를 내부적으로 보유하여 그 인스턴스를 활용하는 방식으로 동작하며, Notification 은 객체를 Observing 하는 방식으로 동작합니다.

  2. Notification 은 싱글턴으로 되어있기 때문에 화면간의 거리가 먼 경우에 유리하고, 보다 다수의 객체들에게 이벤트를 전달하는 데에 유용합니다.

  3. 하지만 다른사람이 NotificationCenter 로 구현된 옵저버 패턴을 봤을 때, 정확히 어떤 객체들이 구독했는지 파악하기 까다롭습니다.

  4. Delegate 를 사용할 경우 다른 사람이 봤을 때 코드를 더 직관적으로 이해하기 좋습니다.


💁🏻‍♂️ 8-5 : NotificationCenter 동작 방식과 활용 방안에 대해 설명해주세요.

  1. 이벤트가 발생했다는 것을 싱글턴 NotificationCenter 에 쏩니다.

  2. NotiCenter 가 등록된 객체들에게 모두 이벤트를 보내줍니다.


💁🏻‍♂️ 8-6 : Singleton 패턴을 활용하는 경우를 예를 들어 설명해주세요.

  1. 싱글턴은 앱 주기동안 오직 하나의 인스턴스를 생성하고 앱 전체 객체들이 알게하는 패턴입니다.

  2. 뷰의 거리가 먼 곳 or 뎁스 차이가 많이 나는 곳에서 데이터 전달을 끌고 가야 하는 경우, Delegate 패턴으로 계속해서 뷰 간에 전달하기는 까다롭기 때문에 싱글턴을 사용합니다.

  3. NotificationCenter 에서 옵저버 패턴을 위해 싱글턴을 사용합니다.


9. KVC / KVO

💁🏻‍♂️ : KVC 와 KVO 에 대해 설명해주세요.

  1. KVC 는 Key Value Coding. 객체의 값을 직접 사용하지 않고, Key Path를 이용해 간접적으로 사용하고 수정하는 방식입니다.

  2. KVO 는 Key Value Observing. Key로 등록한 변수를 관찰하며 값이 변할때마다 어떠한 동작을 수행하는 것입니다. 프로퍼티 옵저버 willSet didSet 과 매우 유사하게 동작합니다.

💁🏻‍♂️ : 그럼 프로퍼티 옵저버와 KVO의 차이점은 무엇인가요?

  • 프로퍼티 옵저버는 타입 내부에서 선언하지만, KVO 는 타입 정의 밖에서 observe 를 추가합니다.

  • willSet didSet 은 본인이 직접 타입을 만드는 경우에 구현해줄 수 있겠지만, 다른 사람 혹은 외부 라이브러리에서 정의한 타입이라면 내부 소스를 마음대로 변경할 수 없습니다.

  • 이럴때는 KVO 방식을 사용해서 옵저빙을 할 수 있습니다.

// KVC
struct A {
    var data = "Data"
}
 
var aInstance = A()
print(aInstance[keyPath: \.data]) // Data
print(aInstance.data) // Data
aInstance[keyPath: \.data] = "Data2"
print(aInstance[keyPath: \.data]) // Data2
 
let key = \A.data
print(aInstance[keyPath: key]) // Data2


// KVO
class Obj: NSObject {
    @objc dynamic var data = "Data"
}
 
let obj = Obj()
let observer = obj.observe(\.data, options: [.new, .old]) { _, changeInfo in
    print("\(changeInfo.oldValue) has been changed to \(changeInfo.newValue)")
}
 
obj.data = "Data2"
// Optional("Data") has been changed to Optional("Data2")

10. 스토리보드 vs 코드베이스

💁🏻‍♂️ : 스토리보드와 코드베이스의 차이를 설명해주세요

  • 스토리보드를 사용하면 눈에 보이는 직관적인 개발을 할 수 있지만, 코드베이스로 UI를 개발했을 때보다 코드 직관성이 떨어져서 협업에 불편함을 느낄 수 있습니다.

11. 앱 상태

💁🏻‍♂️ 11-1 : Run Loops에 대해 설명하시오.

  1. Run Loop 는 스레드에서 인풋 이벤트나 타이머를 처리하기 위한 이벤트 처리 루프입니다.

  2. 스레드마다 런루프를 생성할 수 있고, Main Thread 의 Run Loop 인 Main Run Loop 는 앱이 실행될 때 UIApplication 에서 자동으로 생성하고 실행됩니다. (11-2 에서 자세히 설명)

  3. 다른 스레드에서 실행 중인 RunLoop 객체의 메서드를 호출하면 예기치 않은 결과가 발생할 수 있습니다.

  4. Run Loop 는 thread-safe 하지 않으므로, 현재 스레드의 컨텍스트 내에서만 메서드를 호출해야합니다.


💁🏻‍♂️ 11-2 : 앱이 시작할 때 main.c 에 있는 UIApplicationMain 함수에 의해서 생성되는 객체는 무엇인가요?

  1. iOS 앱은 Objective-C 기반으로 돌아가기 때문에 앱은 main 함수에서 시작됩니다.

  2. main() 함수는 UIKit 내부에 숨겨져 있습니다. 그리고 main() 함수 내부에서 UIApplicationMain 함수를 실행합니다.

  3. 그리고 UIApplication 이라는 객체를 생성합니다.

  4. UIApplication 은 실행중인 앱을 제어하는 핵심 객체이고, Main Run Loop를 실행합니다.

  5. 모든 앱은 단 하나의 UIApplication 인스턴스를 가지고, UIApplication.shared 로 접근할 수 있습니다.

  6. UIApplication 인스턴스가 생성된 뒤에는 info.plist 파일을 읽어 파일에 기록된 정보를 참고해서 필요한 데이터를 로드합니다.


💁🏻‍♂️ 11-3 : @main에 대해서 설명하시오.

  1. @main 은 프로그램의 진입점을 나타내고 주로 AppDelegate 에서 사용합니다.

  2. UIKit 내부에는 프로그램 시작점인 main() 함수가 숨겨져 있는데, 이 @main 어노테이션을 사용함으로써 진입점을 나타냅니다.

  3. Swift5.4 이전에서는 @main 대신에 @UIApplicationMain 키워드를 사용했는데, 이는 struct 에서는 사용하기 어려운 구조였기 때문에 class 와 struct 를 모두 커버 가능한 @main 을 사용하게 되었습니다.

💁🏻‍♂️ 11-4 : App의 LifeCycle 에 대해 설명하시오.

  • Unattached : 앱이 실행되지 않은 상태. 메모리에 올라오지 않은 상태.

  • Foreground : 앱이 화면에 보여지는 상태로, CPU를 포함한 시스템 리소스를 우선적으로 사용합니다.

  • 그리고 Foreground 상태는 InActive 상태와 Active 상태로 나눌 수 있습니다.

    • Active : 앱이 실행 중이고 이벤트를 받을 수 있는 상태

    • InActive : 앱이 실행 중이지만 전화나 알림에 의해 이벤트 입력을 멈추고 잠시 비활성화 된 상태

  • Background : 앱이 화면을 점유하지 않지만 동작은 하고 있는 상태. 예를 들어 백그라운드에서 음악을 실행하는 상황

  • Suspend : 백그라운드에서 활동을 멈춘 상태. 메모리에 올라는 가있습니다. 메모리가 부족한 상황이 생기면 Suspend 상태의 앱을 메모리에서 내리고 메모리 공간을 확보합니다.

  • https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle

  • https://co-dong.tistory.com/62


💁🏻‍♂️ 11-5 : 앱이 foreground에 있을 때와 background에 있을 때 어떤 제약사항이 있나요?

  • Foreground 상태는 앱이 실행되어 유저에게 보여지는 상태이고, 메모리 및 시스템 리소스를 효율적으로 사용해야 한다는 조건을 가집니다.

  • Background 상태는 앱이 화면에 띄워져있지는 않지만 뒤에서 실행되고 있는 상태입니다. 제약사항으로는 사용자의 이벤트를 받을 수 없고, 메모리를 가능한 조금만 차지해야 한다는 특징이 있습니다.


💁🏻‍♂️ 11-6 : 상태 변화에 따라 다른 동작을 처리하기 위한 앱델리게이트 메서드들을 설명하시오.

  • didFinishLaunching : 앱을 메모리에 올리고, 앱을 실행할 준비를 마쳤을 때 호출합니다.

  • 그 외 라이프 사이클에 대한 메서드 들이 있습니다.

  • applicationDidBecomeActive : Active 상태가 됐을 때 호출

  • applicationWillResignActive : InActive 상태가 되려할 때 호출

  • applicationDidEnterBackground : Background 상태가 됐을 때 호출

등등이 있습니다.


💁🏻‍♂️ 11-7 : UIWindow 에 대해 설명하시오.

  1. UIWindow 는 UIView 들을 담는 컨테이너입니다.

  2. UIWindow 의 코드를 열어보면, UIView 를 상속받고 있다는 것을 알 수 있습니다.

  3. 이에 UIWindow 도 결국에는 일종의 뷰이지만, UIView 들을 액자처럼 담고 있는 컨테이너 뷰라고 생각 할 수 있습니다.

  4. iOS 12 까지는 window 의 개념을 사용했지만 iOS 13부터는 scene 의 개념이 이를 대체하게 되었습니다.


💁🏻‍♂️ 11-8 : AppDelegate와 SceneDelegate 에 대해 설명하시오.

  • iOS 12까지는 대부분 하나의 앱이 하나의 window 를 가졌지만, iOS 13 부터는 window의 개념이 scene으로 대체됐고, 하나의 앱에서 여러 개의 scene을 가질 수 있게 되었습니다.
  1. 예전에는 AppDelegate 가 Process LifeCycle 과 UI LifeCycle 을 모두 담당했었지만, SceneDelegate 가 등장하면서 UI LifeCycle 의 책임을 옮겨 받게 되었습니다.

  2. 그리고 AppDelegate 는 모든 scene 의 정보를 관리하는 Scene Session 을 관리하게 되었습니다. (scene configuration)

  3. (여기 설명이 좋음) https://velog.io/@dev-lena/iOS-AppDelegate%EC%99%80-SceneDelegate


💁🏻‍♂️ 11-9 : App Bundle의 구조와 역할에 대해 설명하시오.

  1. 앱 번들은 앱의 성공적인 빌드를 위한 것들을 저장합니다.

  2. Info.plist , 실행파일 , 소스파일 등을 저장합니다.

  3. Info plist 안에는 앱 버전, 번들 id, 권한 요청 메시지 등 여러가지 앱 메타 데이터를 저장합니다.


💁🏻‍♂️ 11-10 : 다크모드를 지원하는 방법에 대해 설명하시오.

  1. Xcode 의 Color Assets 에서 Color Set 로 다크 모드인 경우 색상과 라이트 모드인 경우 색상을 함께 등록합니다.

  2. 다크모드를 아예 사용하고 싶지 않다면 info plist 에서 설정해줄 수 있습니다.


12. Cocoa Touch

💁🏻‍♂️ : Cocoa Framework와 Cocoa Touch Framework의 차이를 설명해주세요.


13. GCD

💁🏻‍♂️ 13-1: NSOperationQueue 와 GCD Queue 의 차이점을 설명하시오.

  1. iOS 에서 멀티스레딩을 할 수 있는 방법은 크게 Operation 과 GCD 가 있습니다.

  2. GCD (Grand Central Dispatch) 는 C 기반, Operation 은 Objective-C 기반으로, GCD 가 상대적으로 더 가볍습니다.

  3. NSOperationQueue 에서는 KVO 가 사용가능하지만 GCD 에서는 사용불가 합니다.


💁🏻‍♂️ 13-2 : GCD API 동작 방식과 필요성에 대해 설명하시오.

  1. GCD 란 iOS 에서 멀티 스레딩을 하기 위해 지원하는 API 입니다. 개발자는 해야할 작업들을 GCD 의 큐에 넘겨주기만 하면 OS 레벨에서 비동기 처리를 도와줍니다.

  2. GCD 에서는 여러가지 형식의 큐를 지원합니다.

  • Serial Queue : 분산시킨 작업을 단 한개의 스레드에서 처리하는 큐. 주어진 시간에 하나의 작업만 순차적으로 수행.

  • Concurrent Queue : 분산시킨 작업을 여러개의 스레드에서 처리하는 큐. 주어진 시간에 여러 작업 수행 가능.

  • Sync Queue : 큐에 추가된 작업이 종료될때까지 기다리는 큐.

  • Async Queue : 큐에 추가된 작업을 종료될때까지 기다리지 않는 큐.

  • Main Queue : 메인 스레드 Serial 큐. UI 작업을 담당합니다.

  • Global Queue : 전체 시스템에 의해 공유되는 Concurrent Queue. UI가 아닌 작업을 수행합니다.

  • Custom Queue : 개발자가 직접 생성하는 큐. (Serial / Concurrent)

  • Sync vs Async : 작업을 보내는 시점에서 기다릴지 말지 기다리는 것

  • Serial vs Concurrent : 큐로 보내진 작업들을 한개의 스레드로 순차처리 할 것인지, 여러 스레드에 보내 순서상관없이 처리할 것인지.

  • https://furang-note.tistory.com/37

  • https://sujinnaljin.medium.com/ios-%EC%B0%A8%EA%B7%BC%EC%B0%A8%EA%B7%BC-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-gcd-4-a621eca0a1d2


💁🏻‍♂️ 13-3 : Dispatch Queue의 Serial Queue에 대해서 설명해보세요.

  • Concurrent Queue 와 다르게, 동시에 여러개의 작업을 수행하지 않고, 순차적으로 하나의 작업만 수행하는 큐입니다. iOS 의 Main Queue 가 Serial Queue 입니다.

💁🏻‍♂️ 13-4 : DispatchQueue.main.async 와 DispatchQueue.main.sync 의 차이

  • 메인 큐는 시리얼 큐이고, UI 작업 흐름이 계속해서 수행되어야 하기 때문에 async 를 통해서 다른 작업을 추가하는 것이 좋습니다.

  • 메인 큐에서 sync 를 사용하면 데드락이 발생하는데 그 이유는 다음과 같습니다.

  1. 메인 쓰레드에서 일을하다가 sync 블럭 "X"를 만나서 메인 시리얼 큐에 X 를 보냅니다. 그리고 sync 이기 때문에 이 "X" 가 끝나기만을 바라보는 상태로 들어갑니다.

  2. 이제 디스패치 큐는 이 X를 처리해줄 스레드를 찾는 것이 책임인데, 어차피 메인 큐는 메인 쓰레드에게 일을 시켜야 합니다.

  3. 메인 쓰레드는 아까 X 작업을 큐에 보내주면서 "나는 널 기다릴래" 라고 했는데 메인 큐가 다시 일을 시키려고 하는 상황입니다.

  4. 그래서 서로가 원하는 작업을 수행하지 못해 데드락이 발생합니다.


💁🏻‍♂️ 13-5 : Global DispatchQueue 의 Qos 에는 어떤 종류가 있는지, 각각 어떤 의미인지 설명하시오.


💁🏻‍♂️ 13-6 : DispatchGroup에 대해서 설명해보세요.

  • 여러개로 분배된 작업들을 하나로 그룹지어서 한번에 파악하고 싶을 때 Dispatch Group 을 사용합니다.

  • 즉 그룹안에 있는 작업들 중 마지막으로 끝난 작업의 시점에 초점을 둡니다.

let group = DispatchGroup()
queue1.async(group: group) {
    for i in 0...5 {
        print(i)
    }
}
queue2.async(group: group) {
    for i in 100...105 {
        print(i)
    }
}
let queueForGroup = DispatchQueue(label: "q", attributes: .concurrent)
group.notify(queue: queueForGroup) {
    print("group end")
}
// 0~5, 100~105 모두 출력후 group end 출력

💁🏻‍♂️ 13-7 : DispatchSemaphore를 사용하는 상황을 설명해주세요.

  • DispatchSemaphore 는 race condition 을 방지하고 싶을 때 사용합니다.

  • 운영체제의 semaphore와 같습니다.

  • 공유 자원에 동시 접근 가능한 작업 수를 명시하고, 임계 구역에 들어갈때는 semaphore의 wait()를, 작업을 수행하고 나올때는 signal() 을 호출합니다.

  • signal 은 작업을 수행하고 세마포어 값을 반환하기 때문에 +1

  • wait 은 열쇠를 획득하고 작업을 수행하러 가야하기 때문에 -1 입니다.

// semaphore 동시 작업 개수 1
let semaphore = DispatchSemaphore(value: 1)

DispatchQueue.global(qos: .background).async {
    print("task A start")
    print("task A end")
    semaphore.signal()
}

semaphore.wait()

DispatchQueue.global(qos: .background).async {
    print("task B start")
    print("task B end")
    semaphore.signal()
}

semaphore.wait()

DispatchQueue.global(qos: .background).async {
    print("task C start")
    print("task C end")
    semaphore.signal()
}

/* 
- 동시 접근 제한 1개인 경우
task A start
task A end
task B start
task B end
task C start
task C end

- 동시 접근 제한 2개 이상인 경우
task A start
task A end
task C start
task C end
task B start
task B end
순서가 보장되지 않고 동시 접근한다.

/*

💁🏻‍♂️ 13-8 : GCD의 Barrier에 대해 설명해주세요.

  • Concurrent 큐가 사용하고 있는 여러개의 쓰레드 중에서, 하나의 쓰레드만 제외하고 모든 쓰레드의 사용을 막는 것입니다.

  • 해당 시점에서는 쓰레드를 오직 하나만 사용하기 때문에 serial 큐처럼 사용이 가능합니다.

  • Concurrent 큐에 read 작업들은 일반적인 task 로, write 작업을 barrier task 로 넣는식으로 응용할 수 있습니다.

  • read 는 여러개의 쓰레드가 동시접근해도 상관이 없지만, write 는 그렇지 않기 때문입니다.


14. 함수형 프로그래밍

💁🏻‍♂️ 14-1 : 함수형 프로그래밍이 무엇인지 설명하시오.

  • 함수형 프로그래밍은 순수 함수를 기반으로 로직을 구성함으로써 휴먼 에러를 줄이고, 가독성을 높이는 프로그래밍 기법입니다.

  • 함수형 프로그래밍의 특징은 다음과 같습니다.

  1. 함수 안의 코드가 함수 외부의 상태를 변경하지 않습니다. 함수 외부에 영향을 끼치지 않습니다.

  2. 함수를 1급 객체로 간주합니다. 함수를 매개변수나 리턴값으로 사용할 수 있게 됩니다. 이로 인해 함수를 조합해서 새로운 로직을 만들어냅니다.


💁🏻‍♂️ 14-2 : 순수함수란 무엇인지 설명하시오.

  1. 순수 함수란 동일한 input에 대해 항상 같은 output을 내고,

  2. 함수 내부에서 함수 외부의 상태에 영향을 끼치지 않는 함수를 말합니다.


💁🏻‍♂️ 14-3 : 1급 객체(혹은 1급 시민)에 대해서 설명해보세요. Swift에는 어떤 1급 객체들이 있나요?

  • 함수형 프로그래밍에서 함수는 1급 객체로 취급됩니다.

  • 1급 객체는 변수로서, 그리고 반환값으로서 사용될 수 있는 객체를 말합니다.

  • Swift 에서는 함수와 클로저가 1급 객체에 해당합니다.


💁🏻‍♂️ 14-4 : 고차 함수가 무엇인지 설명하시오.


15. 캐싱

💁🏻‍♂️ 15-1 : Kingfisher 는 왜 사용했나요? 무슨 장점이 있나요?

  • Kingfisher 는 URLSession 기반의 이미지 처리 라이브러리로, 캐시 처리에 대해 편리한 기능을 제공하기 때문에 사용했습니다. 킹피셔는 메모리 캐싱과 디스크 캐싱을 모두 편하게 처리할 수 있게 합니다.

  • AlamofireImage 라이브러리에 비해서 캐시 처리에 편의성이 장점이기 때문에 사용했습니다.

🤓 앱의 메모리 캐싱과 디스크 캐싱
1. 메모리 캐싱
  - 앱의 메모리 중 일부분을 캐싱에 활용
  - 앱이 메모리에서 해제되면 함께 삭제

2. 디스크 캐싱
  - 메모리가 아닌 디스크에 캐싱 활용
  - 앱이 메모리에서 해제되어도 남아있음
  - ex) 카카오톡 종료 후 데이터 끄고 들어와도 사진 보임

💁🏻‍♂️ 15-2 : NSCache 동작 방법. 어디에 저장되나요?

  • NSCache 는 Key-Value 형태로 데이터를 저장하는 메모리 캐싱 클래스이고, 앱이 메모리에서 해제될 때 자동으로 캐시된 내용들이 함께 제거됩니다.

  • 그리고 NSCache 는 Thread-Safe 하기 때문에 여러 쓰레드에서 동시 접근해도 상관없습니다.

  • NSCache 는 링크드 리스트와 딕셔너리를 함께 사용하는 구조로 이루어져있습니다.

  • 링크드 리스트를 통해 key 를 관리하고, 그 key 에 대한 value 는 딕셔너리로 참조합니다.

  • https://beenii.tistory.com/187


💁🏻‍♂️ 15-4 : 같은 url 이지만 이미지가 바뀔 경우는 어떻게 처리할까요?

  • 같은 url 이지만 이미지가 변경되는 경우는 url 에 버전을 달아 관리하거나, 특정지을 수 있는 해시 값을 함께 내려줘야 합니다.

  • 이 모든 과정이 세팅되었음에도 불구하고, 이미지가 변경되어 보이지 않는다면 http response 자체를 캐싱하고 있지는 않은지 확인합니다.

  • Ace 님 👍🏻


16. 추가 개념

💁🏻‍♂️ 16-1 : 모듈화란 무엇이고 왜 하나요?

  1. 모듈화는 마치 객체지향에서 클래스 단위로 책임을 나누어 관리 하는 것처럼, 클래스의 모음을 모듈이라는 단위로 묶어, 프로젝트 단위의 책임을 나누어 관리하는 것을 말합니다.

  2. 모듈 별 분업이 용이하고, 잘 만들어진 모듈은 재활용할 수 있습니다.

  3. 모듈 별로 빌드가 가능해져, 개발 효율을 높일 수 있습니다.


💁🏻‍♂️ 16-2 : 의존성 주입에 대하여 설명하시오.

  • 의존성 주입이란, 한 클래스내에서 다른 클래스의 인스턴스를 직접 생성하지 않고, 외부에서 생성한 후 매개변수로 전달해주는 것을 말합니다.

  • 이 때, 인터페이스를 활용해서 객체지향의 의존 역전 원칙 (Dependency Inversion Principle)을 만족하도록 합니다.

🤓 의존 역전 원칙 (DIP)

- 의존 관계를 맺을 땐, "변화하기 쉬운 것보단 변화하기 어려운 것에 의존해야 한다"는 원칙.
- 여기서 변화하기 어려운 것이란 추상 클래스나 인터페이스를 말하고, 변화하기 쉬운 것은 구체화된 클래스를 의미.
- 따라서 DIP를 만족한다는 것은 구체적인 클래스가 아닌 인터페이스 또는 추상 클래스와 관계를 맺는다는 것을 의미.

💁🏻‍♂️ 16-3 : MVC 구조에 대해 블록 그림을 그리고, 각 역할과 흐름을 설명하시오.

  • 간단한 날씨 앱을 예로 들어보겠습니다.

  • Model : 앱에 필요한 데이터들을 구체화합니다. 예를 들어 Weather 라는 데이터 구조체는 안에 temperature, humid, image 등을 담는다고 구체화 할 수 있습니다. 또한 이러한 데이터를 가공합니다.

  • View : 앱 화면에 보이는 뷰 로직을 담당합니다. 예를 들어 일주일 날씨를 띄워주는 테이블 뷰, 컬렉션 뷰 등이 있습니다.

  • Controller : 사용자 입력을 받아들이고, 뷰와 모델 사이에 필요한 데이터들을 전달합니다. 예를들어 어떠한 버튼을 눌렀을 경우 모델의 데이터를 어떻게 변경하고 전달한다와 같은 역할을 합니다.


💁🏻‍♂️ 16-4 : 웹 서버와 HTTP 연결을 사용해서 데이터를 주거나 받으려면 사용해야 하는 클래스와 동작을 설명하시오.

  • URLSession 을 사용합니다. URLSession 은 Alamofire 나 Moya 의 기반이 되는 클래스입니다.
  1. Session Configuration 을 결정하고 Session 생성

    • Shared Session / Default Session 등 디스크에 저장할 건지, 메모리에 저장할 건지
  2. URL 과 Request 객체 설정

  3. Task 설정, Completion Handler 나 Delegate 설정

    • URLSessionDataTask / URLSessionUploadTask 등 웹 소켓인지 TCP 인지
  4. Task 실행 완료 후 그에 맞는 Completion Handler 클로저 호출


💁🏻‍♂️ 16-5 : Core Data와 Sqlite 같은 데이터 베이스의 차이점을 설명하시오

  • Core Data 는 디바이스에 데이터를 저장할 수 있습니다. UserDefault 와 비슷하지만, 보다 복잡하고 큰 데이터를 저장하는데 적합합니다.

  • 반면에 Sqlite 는 RDB DBMS 입니다. 따라서 Core Data 는 데이터베이스가 아니고 Sqlite 는 데이터베이스라는 차이점이 있습니다.

  • http://egloos.zum.com/scienart/v/2661925


💁🏻‍♂️ 16-6 : shallow copy와 deep copy의 차이점을 설명하시오.

  • shallow copy 는 주소 값을 복사하기 때문에 참조하고 있는 실제 값은 같습니다.

  • deep copy 는 실제 값을 새로운 메모리 공간에 복사합니다.

Swift에서 클래스 인스턴스의 딥 복사는 두 가지 방법으로 수행될 수 있습니다.
1. NSCopying 프로토콜을 구현하여 딥 복사하기
NSCopying 프로토콜을 구현하면 클래스 인스턴스를 복사할 수 있습니다. NSCopying 프로토콜을 구현하려면 클래스가 copy(with:) 메서드를 구현하도록 요구합니다.
class MyClass: NSCopying {
    var myProperty: Int
    init(myProperty: Int) {
        self.myProperty = myProperty
    }
    func copy(with zone: NSZone? = nil) -> Any {
        let copy = MyClass(myProperty: myProperty)
        return copy
    }
}
let original = MyClass(myProperty: 42)
let copied = original.copy() as! MyClass
2. Codable 프로토콜을 활용하여 딥 복사하기
Codable 프로토콜을 사용하여 클래스 인스턴스를 직렬화하고 다시 역직렬화하여 복사본을 만들 수 있습니다. 이 방법은 Swift 4에서 도입되었습니다.
class MyClass: Codable {
    var myProperty: Int
    init(myProperty: Int) {
        self.myProperty = myProperty
    }
}
let original = MyClass(myProperty: 42)
let data = try! JSONEncoder().encode(original)
let copied = try! JSONDecoder().decode(MyClass.self, from: data)
위의 두 가지 방법 중 하나를 사용하여 딥 복사를 수행할 수 있습니다. 
선택한 방법은 클래스에 따라 다르며, 클래스가 Codable을 구현하고 있는 경우 Codable 방법이 더 효율적일 수 있습니다.

💁🏻‍♂️ 16-7 : Synchronous 방식과 Asynchronous 방식으로 URL Connection을 처리할 경우의 장단점을 비교하시오.

  • 비동기로 처리할 경우, 네트워크 통신을 보내놓고 다른 작업을 수월하게 할 수 있다는 장점이 있지만, 어떤 작업들의 순서를 보장하기 힘들다는 단점이 있습니다.

  • 동기로 처리할 경우, 네트워크 통신을 보내놓고 다른 작업을 수행하기 힘들다는 단점이 있지만, 작업의 순서를 보장할 수 있다는 장점이 있습니다.


💁🏻‍♂️ 16-8 : 오토레이아웃을 코드로 작성하는 방법은 무엇인가? (3가지)

  1. NSLayoutConstraint

    • 객체간의 관계와 간격을 설정합니다.
  2. NSLayoutAnchor

    • NSLayoutConstraint 보다 가독성 좋게 사용할 수 있습니다.
  3. Visual Format Language

    • 시각적으로 표현하기 때문에 표현성이 좋습니다.
    • 표현성을 강조했기 때문에 디테일한 설정은 하기 어렵습니다.
// 1. NSLayoutConstraint
NSLayoutConstraint.init(item: aView,
                                attribute: .leading,
                                relatedBy: .equal,
                                toItem: view,
                                attribute: .leading,
                                multiplier: 1.0,
                                constant: 8).isActive = true
                                

// 2. NSLayoutAnchor
aView.leadingAnchor.constraint(equalTo: view.leadingAnchor,constant: 8).isActive = true


// 3. Visual Format Language
let format1 = "H:|-[a]-|"
var constraint = NSLayoutConstraint.constraints(withVisualFormat: format1,
                                                        options: [],
                                                        metrics: nil,
                                                        views: views)

💁🏻‍♂️ 16-9 : 멀티 쓰레드로 동작하는 앱을 작성하고 싶을 때 고려할 수 있는 방식들을 설명하시오.

  1. GCD 활용
    Swift 에서는 Dispatch Queue 를 통해 멀티 쓰레드 작업을 수행할 수 있습니다.
    예를 들어 UI 를 담당하는 main thread 이외에도 다른 작업을 수행하고 싶다면 Custom Queue 나 Global Queue 를 생성해서 sync / async 로 처리할 수 있습니다.

  2. 라이브러리 활용 RxSwift
    RxSwift 를 사용해본 경험이 있습니다. RxSwift 를 활용하면 멀티 쓰레드 및 비동기 작업들을 편리하게 수행할 수 있고, Scheduler, Subject, Relay 등이나 유용한 메서드를 사용할 수 있게 됩니다.


🍏 UIKit 프레임워크

1. Frame/Bounds

💁🏻‍♂️ 1-1 : frame과 bounds의 차이점에 대해서 설명해 주세요

  1. 프레임과 바운스 모두 view의 위치와 크기를 나타냅니다.
  2. frame은 super view를 기준으로 view의 위치를 나타냅니다. 만약, frame.origin = CGPoint(x:10, y:10)이라면 super view의 (0,0) 기준으로 (10,10)이동한 곳에 왼쪽 위 모서리 값을 찍게 됩니다.
  3. bounds는 자기 자신을 기준으로 view의 위치를 나타냅니다. 그래서 처음은 (0,0)으로 초기화 됩니다. bounds의 위치는 하위뷰의 시점을 이동하기 위해 사용합니다.
  4. frame은 view 영역을 모두 감싸는 사각형으로 크기를 나타냅니다. 만약 view가 회전하여 모서리의 위치가 바뀌는 경우에는 frame.size도 함께 변화합니다.
  5. bounds는 view 자체의 크기를 나타냅니다. 그래서 view가 회전을 하더라도 bounds.size의 크기는 변하지 않습니다.

image

💁🏻‍♂️💁🏻‍♂️ 하위뷰의 시점을 이동한다는게 무슨 말이죠?
1. bounds의 값이 (10,10)으로 옮겨지더라도 view는 이동하지 않습니다.
2. A뷰의 bounds.origin = CGPoint(x:10,y:10)으로 주고, B뷰의 super view를 A로 설정했다고 가정하겠습니다.
3. bounds의 위치는 하위뷰의 시점을 이동시킨다고 했으므로 A의 (10,10)위치에 있던 B를 A의 시작점 위치로 가져오게 됩니다.
4. 즉, 하위뷰는 super view의 (10,10)위치를 시작점으로 생각하게 되는 것입니다.

image

💁🏻‍♂️ 1-2 : frame과 bounds은 각각 언제 사용하나요?

  1. frame은 view의 위치와 크기를 지정할 때 사용합니다.
  2. frame은 super view를 기준으로 위치를 정하고, view 전체 영역의 크기를 지정하므로 시각적으로 설계하기 쉽습니다.
  3. bounds는 view를 회전했지만 실제 크기를 알고 싶을 때 사용할 수 있습니다.
  4. 또, bounds는 시점을 이동하므로 scrollView에서 스크롤 할 때 사용합니다.

💁🏻‍♂️💁🏻‍♂️ 스크롤뷰에서 bounds에 대해서 자세히 설명해 주세요

  1. scrollView에 하위뷰들을 넣어주면 화면을 넘어가는 UI를 스크롤을 통해 볼 수 있습니다.
  2. 이때, 하위뷰들이 위치를 이동하는 것이 아니라, scrollView가 하위뷰의 시점을 이동시키는 것입니다.
  3. 예를들어, 아래에 있는 데이터를 보고 싶을 때는 scrollView의 bound.origin.y값을 수정하여 superView가 이동하는 것입니다.

2. Frame과 AutoLayout 속도 차이

  1. autolayout은 결과적으로 내부에서 frame으로 계산되기 때문에 autolayout → frame으로 변환하는 과정을 거칩니다.
  2. 그래서 계산 속도는 frame이 더 빠를 수 밖에 없습니다. UI 속도(성능)에 민감한 서비스에 대해서는 frame을 사용할 수 있습니다.
  3. 단, frame의 origin x,y값을 그냥 주는 것 보다는 다양한 화면 크기에 대응하기 위해 화면의 크기를 불러와서 비율로 계산하는 방법을 지향해야 합니다.
  4. 그런 면에서 개발 속도는 view의 배치가 상대적으로 간단한 autolayout이 더 빠를 수 있습니다.

3. CGFloat과 Float의 차이

  1. Float는 실수타입을 나타내며 32비트를 사용합니다.
  2. CGFloat는 CPU 아키텍처에 따라 자동적으로 32비트가 될 수도, 64비트가 될 수도 있습니다.
  3. 멀티 플랫폼을 개발하는 경우 CGFloat를 사용하면, 실수타입에 대해 코드 수정을 할 필요가 없습니다.

4. Controller

💁🏻‍♂️ 4-1 : 모든 View Controller 객체의 상위 클래스는 무엇인가요?

  1. 모든 ViewController의 상위클래스는 UIViewController입니다.
  2. UIViewController는 UIKit에서 뷰의 계층을 관리해주는 객체 입니다.
  3. 데이터가 변화함에 따라 뷰 컨텐츠를 업데이트 하고, 뷰의 크기와 레이아웃을 관리합니다.
  4. UIViewController는 UIResponder을 상속하기 때문에 이벤트를 처리할 수도 있습니다.

💁🏻‍♂️ 4-2: UIResponder가 무엇인가요?

  1. UIKit에서 이벤트 처리에 대한 부분을 담당합니다.
  2. UIKit에서는 이벤트가 발생하면 이벤트를 Responder 객체에게 전달해서 처리합니다.
  3. Responder는 UIResponder의 인스턴스입니다. UIViewConroller도 Responder입니다.
  4. Responder는 UIResponder에 있는 이벤트 처리 메서드중에 사용할 메서드를 오버라이드 해서 구현해야 합니다.

💁🏻‍♂️💁🏻‍♂️ 그럼 first responder에 대해서 말해보실래요?

  1. UIKit에서는 Responder들을 연결해서 관리합니다. 이를 Responder Chain이라고 합니다. Responder Chain은 링크드 리스트 구조를 가지고 있습니다.
  2. 이벤트를 받은 Responder는 자신이 이벤트를 처리하거나 Responder Chain에 있는 다음 Responder에게 이벤트를 전달할 수 있습니다.
  3. First Responder는 이벤트를 가장 먼저 전달받는 Responder라는 의미입니다. 즉, 이벤트가 발생하면 가장 먼저 처리할 Responder라는 것입니다.

💁🏻‍♂️ 4-3: UINavigationController에 대해서 설명해 주세요

  1. 스택 계층구조를 기반으로 한 컨테이너 뷰컨트롤러 입니다.
  2. 뷰 컨트롤러들을 스택에 담아서 관리합니다. (자료구조 Stack의 FILO 특성을 그대로 반영합니다.)
  3. 가장 아래에 위치한 뷰컨트롤러는 rootViewController로 컨테이너를 빠져나올 수 없는 뷰컨트롤러입니다.
  4. UINavigationController의 메서드를 사용해서 뷰컨트롤러를 스택에 push, pop할 수 있습니다.

💁🏻‍♂️💁🏻‍♂️ present와 pushViewController는 어떤 차이점이 있죠?

  1. present는 현재 화면 위에 새로운 view controller을 모달창으로 띄웁니다.
  2. 네비게이션의 pushViewController 메서드는 네비게이션 스택에 view controller를 추가하고 최상단에 띄워주며, navigation bar를 자동 생성하여 보여줍니다.

💁🏻‍♂️ 4-4: ViewController의 생명주기를 설명해 주세요

  1. init(nibName:bundle:) or init(coder:): 뷰컨트롤러 객체가 생성됩니다.
  2. loadView(): 뷰컨트롤러의 뷰가 메모리에 로드됩니다.
  3. viewDidLoad(): 뷰컨트롤러의 뷰가 메모리에 로드된 직후 호출됩니다.
  4. viewWillAppear(_:): 뷰컨트롤러의 뷰가 화면에 나타나기 직전에 호출됩니다.
  5. viewWillLayoutSubviews(): 뷰컨트롤러의 뷰가 서브뷰들을 레이아웃하기 직전에 호출됩니다.
  6. viewDidLayoutSubviews(): 뷰컨트롤러의 뷰가 서브뷰들을 레이아웃한 직후 호출됩니다.
  7. viewDidAppear(_:): 뷰컨트롤러의 뷰가 화면에 나타난 직후 호출됩니다.
  8. viewWillDisappear(_:): 뷰컨트롤러의 뷰가 화면에서 사라지기 직전에 호출됩니다.
  9. viewDidDisappear(_:): 뷰컨트롤러의 뷰가 화면에서 사라진 직후 호출됩니다.
  10. deinit: 뷰컨트롤러 객체가 메모리에서 해제될 때 호출됩니다.

💁🏻‍♂️💁🏻‍♂️ 각각의 메서드에서 어떤 작업을 하면 좋을까요?

  1. viewDidLoad(): 뷰컨트롤러가 로드될 때 처음에 한 번만 호출됩니다. 예를 들어 뷰컨트롤러에서 사용할 데이터를 로드하거나, 뷰를 초기화하는 작업
  2. viewWillAppear(_:): 뷰가 나타날 때 마다 수행해야 하는작업. 예를 들어 새로운 데이터 가져오기, 화면 갱신
  3. viewDidAppear(_:): 뷰가 나타나자 마자 시작해야 하는 작업. 예를 들어 애니메이션
  4. viewWillDisappear(_:): 뷰가 사라지기 전에 시작해야 하는 작업. 예를 들어 애니메이션, 입력된 데이터저장
  5. viewDidDisappear(_:): 뷰가 사라질 때 마다 수행해야 하는 작업. 예를 들어 메모리에서 뷰컨트롤러 데이터를 해제하는 작업
  6. deinit: 뷰컨트롤러가 메모리에서 해제될 때 수행해야 하는 작업. 사용했던 리소스를 해제 (객체 참조 해제)

5. View

💁🏻‍♂️ 5-1 : UIView에 대해서 설명해 주세요

  1. UIView는 직사각형 모양의 화면을 관리하는 객체입니다.
  2. UIKit Core Graphics를 사용하여 직사각형 영역에 콘텐츠를 그립니다.
  3. Layout을 사용하여 뷰 계층 구조의변경에 따라 뷰의 크기, 위치를 조정하는 규칙을 만들 수 있습니다.
  4. 이벤트에 응답할 수 있습니다.

💁🏻‍♂️💁🏻‍♂️ UIView 에서 Layer 객체는 무엇이고 어떤 역할을 담당하나요?

  1. 그래픽을 그리기 위해서는 GPU에 직접 접근해서 그리면 렌더링 속도가 매우 빠르지만 코드의 양이 많은 단점이 있습니다.
  2. 이를 보완하기 위해 고수준 프레임워크인 Core Animation과 UIKit 프레임워크가 생기게 되었습니다.
  3. UIKit은 Core Animation보다 한 단계 높은 수준의 API 입니다.
  4. 즉, Core Animation보다 코드를 작성하기 쉽다는 말이기도 합니다.
  5. UIView.layer는 CALayer의 객체 입니다. CALayer는 Core Animation에서 제공하고, UIView는 UIKit에서 제공합니다.
  6. layer를 사용하면 조금더 복잡한 애니메이션과 퍼포먼스를 보여줄 수 있습니다.

💁🏻‍♂️ 5-2 : UIWindow에 대해서 설명해 주세요

  1. 뷰들을 담을 수 있는 비어있는 컨테이너 입니다. 이벤트를 전달해주는 매개체 역할을 합니다.
  2. iOS 앱은 최소 하나 이상의 윈도우를 가지고 있습니다.
  3. 스토리보드로 개발할 때는 윈도우가 자동 생성되지만, 그렇지 않은 경우에는 SceneDelegate 에 UIWindow를 만들어 rootViewController를 설정해주어야 합니다.

6. tableView & collectionView

💁🏻‍♂️ 6-1 : TableView를 동작 방식을 설명해 주세요

  1. 테이블뷰는 재사용큐를 통해 메모리를 효율적으로 사용할 수 있도록 합니다.
  2. 화면에 보이는 cell과 앞 뒤의 cell 몇 개만 만들어두고, 나머지는 스크롤에 의해 생성됩니다.
  3. dequeueReusableCell()을 사용해서 화면에 보여주고 싶은 cell이 재사용큐에 있는지 확인하고, 있으면 재사용큐에서 꺼내서 cell을 재활용합니다. (identifier를 통해 확인)
  4. cell이 화면에 보여지기 직전에 WillDisplayCell 함수가 호출됩니다.
  5. 스크롤을 통해 cell이 화면 밖으로 벗어나게되면 didEndDisplayingCell 함수가 호출되고, cell은 재사용큐에 들어가서 대기합니다.
  6. iOS 10+ 부터는 UITableViewDataSourcePrefetching 프로토콜을 채택함으로써 화면에 보이지 않는 cell의 정보를 미리 호출하여 테이블뷰가 스크롤될 때 cell을 더 빠르게 로드할 수 있습니다.

💁🏻‍♂️ 6-2 : TableView 화면에 Cell을 출력하기 위해 최소한 구현해야 하는 DataSource 메서드를 설명해 주세요

  • 섹션마다 표시할 셀의 개수를 반환하는 메서드
    • func tableView(UITableView, numberOfRowsInSection: Int)
  • 인덱스마다 어떤 셀을 사용할지 반환하는 메서드로, cell에 데이터를 바인딩하거나 조작하여 return 합니다.
    • func tableView(UITableView, cellForRowAt: IndexPath)

💁🏻‍♂️ 6-3 : TableView와 CollectionView의 차이점을 설명해 주세요

  1. tableView는 세로 스크롤만 지원하며, 단일 컬럼, 단일 섹션 레이아웃만 지원합니다.
  2. collectionView는 상하좌우 스크롤이 가능하며, 다중 컬럼을 가질 수 있고, 다양한 레이아웃을 옵션을 지원합니다.

💁🏻‍♂️ 6-4 : prepareForReuse에 대해서 설명해 주세요

  1. 테이블뷰는 같은 identifier를 가진 cell을 재사용하여 메모리를 효율적으로 사용합니다.
  2. 화면에서 사라진 셀은 재사용큐에 저장되고 같은 identifier를 가진 cell을 호출할 때 재사용됩니다.
  3. prepareForReuse는 테이블 뷰 cell이 재사용될 때 호출되는 메서드입니다.
  4. prepareForReuse는 cellForItemAt 이 호출되기 전에 호출되어서 셀의 설정들을 초기화할 수 있도록 도와줍니다.
🎯 설명에는 편의상 테이블뷰만 얘기했지만, 컬렉션뷰 cell도 가지는 메서드 입니다!!

7. URLSession

💁🏻‍♂️ 7-1 : URLSession에 대해서 설명해 주세요.

  1. URLSession은 네트워크 통신을 제공하는 기본 프레임워크의 클래스입니다. Foundation 프레임워크에 포함되어 있습니다.
  2. SessionTask를 만들고 여기에 통신에 대한 설정과 콜백을 정의해서 넘기면 네트워크 통신이 완료되었을 때 클로저가 실행됩니다.
  3. URLSession은 내부적으로 GCD를 시용하여 네크워크 작업을 비동기적으로 처리합니다.

💁🏻‍♂️💁🏻‍♂️ GCD로 어떻게요? 자세히 설명해주세요

  1. URLSession에서 데이터 요청을 시작하면, GCD는 백그라운드에서 작업을 수행합니다.
  2. FIFO방식을 사용하여 요청이 들어온 순서대로 처리합니다.
  3. 백그라운드에서 작업을 수행하기 때문에 메인 스레드를 차단하지 않습니다. 즉, 작업하는 동안 UI를 멈추지 않고 사용자와 계속해서 상호작용 할 수 있습니다.

💁🏻‍♂️ 7-2 : URLDownloadTask와 URLSessionDataTask를 비교해서 설명해 주세요.

  1. 둘 다 URLSession에서 제공하는 네트워크 데이터 다운로드 작업을 수행하는 데 사용되는 클래스입니다. 비동기 방식으로 작업을 수행합니다.
  2. downloadTask는 파일 다운로드에 특화된 작업 클래스 입니다.
  3. 로컬 저장소에 저장하기 때문에 특정 경로에 저장한다면 파일을 영구적으로 보관할 수 있습니다.
  4. 저장경로를 지정하지 않으면 기본적으로 앱의 캐시 디렉터리에 저장됩니다. 이는 앱이 종료될 때 자동으로 삭제됩니다.
  5. dataTask는 일반적인 데이터 다운로드를 할 때 사용됩니다.
  6. NSData 타입으로 데이터를 내려받기 때문에 로컬 저장소에는 저장하지 않고 메모리에 저장합니다.
  7. 메모리에 데이터를 저장하기 때문에 dataTask를 통해 용량이 큰 데이터를 받으면 메모리 부족 현상이 발생할 수 있습니다.
DownloadTaskDataTask
데이터의 종류영상, 음악, 이미지, PDFJSOM, XML
데이터의 크기큰거작은거
데이터의 저장로컬(영구 저장 가능)메모리

n. 추가 개념

💁🏻‍♂ n-1 : UIKit 클래스들을 다룰 때 꼭 처리해야하는 애플리케이션 쓰레드 이름은 무엇인가?

  • mainThread : UIKit은 UI를 다루는 프레임워크 입니다. UI와 관련된 코드는 반드시 mainThread에서 동작해야 합니다.

💁🏻‍♂️ n-2 : 자신만의 Custom View를 만들려면 어떻게 해야하는지 설명하시오.

  1. custom view를 만들기 위해서는 UIView를 반드시 상속해야 합니다.
  2. 이니셜라이저를 만들고 싶은 경우에는 super.init(frame: CGRect)를 반드시 호출해야 하며, required init?(coder: NSCoder)도 함께 작성해야 합니다.

💁🏻‍♂ n-3 : 뷰의 위치나 크기를 재조정하려면 어떻게 해야하나요?

  1. frame을 사용합니다. frame.origin을 통해 x,y 좌표값으로 view를 이동하고, frame.size를 통해 width, height값을 통해 크기를 조절할 수 있습니다.
    • frame을 사용하면 정확한 위치와 크기를 지정할 수 있습니다.
  2. autolayout을 사용합니다. 뷰의 위치와 크기를 constraints로 지정하여 상위 뷰의 크기에 따라 뷰의 위치와 크기를 자동으로 조절합니다.
    • autolayout을 사용하면 다양한 화면 크기에 대응하여 뷰를 유연하게 배치할 수 있습니다.
  3. CGAffineTransform을 사용합니다. 뷰의 변환을 나타내는 구조체로, 뷰의 회전, 확대, 축소 등의 변형을 줄 수 있습니다.
    • 복잡한 뷰의 이동, 애니메이션을 구현해야 하는 경우에 사용하는 것이 적합합니다.

💁🏻‍♂ n-4 : Foundation Kit은 무엇이고 포함되어 있는 클래스들은 어떤 것이 있는지 설명해 주세요

  1. Cocoa프레임워크에서 제공되는 기본 프레임워크 입니다.
  2. objc와 swift에서 모두 사용할 수 있습니다.
  3. 다양한 플랫폼 (macOS, iOS, watchOS, tvOS)에서 공통으로 사용할 수 있습니다.
  4. 데이터 관리, 파일 및 디렉터리 관리, 네크워킹, 문자열 처리, 날짜 및 시간 처리 등의 기능을 제공합니다.
  • 대표적인 클래스들
- NSString / NSMutableString : 문자열 처리 클래스
- NSArray / NSMutableArray : 배열 처리 클래스
- NSDictionary / NSMutableDictionary : 키-값 쌍 데이터 처리 클래스
- NSUserDefaults : 사용자 설정 데이터 처리 클래스
- NSURL / NSURLRequest / NSURLSession : 네트워킹 처리 클래스
- NSFileManager : 파일 및 디렉터리 관리 클래스
- NSDate / NSDateComponents / NSDateFormatter : 날짜 및 시간 처리 클래스
- NSTimer : 타이머 클래스
- NSNotificationCenter : 알림 처리 클래스
- NSRegularExpression : 정규 표현식 처리 클래스
- NSJSONSerialization : JSON 데이터 처리 클래스

💁🏻‍♂ n-5 : NSCache와 딕셔너리로 캐시를 구성했을때의 차이를 설명해 주세요.

  1. key-value 쌍으로 데이터를 저장하는 자료구조라는 공통점이 있습니다.
  2. NSCache는 메모리 공간이 부족하면 자동으로 캐시된 객체를 제거함으로 메모리 관리 측면에서 유용합니다.
  3. NSCache는 동시에 여러 스레드에서 접근하여 데이터를 수정할 수 있습니다.
  4. 딕셔너리는 thread-safe하지 않아서 여러 스레드에서 동시 접근하면 오류가 발생할 수 있습니다.
  5. 메모리 관리와 성능 측면에서 NSCache를 쓰는 것을 권장하지만 오버헤드가 발생하므로 단일 스레드 환경이거나 작은 데이터를 캐싱할 때는 딕셔너리를 사용할 수 있습니다.

💁🏻‍♂️💁🏻‍♂️ NSCache는 왜 thread-safe한가요?

  • 딕셔너리와 달리 NSCache는 내부적으로 뮤텍스와 같은 동시성 제어 메커니즘을 사용합니다.

💁🏻‍♂️💁🏻‍♂️ NSCache는 왜 오버헤드가 발생하나요?

  1. 캐시된 객체가 많아지면 메모리 부족 현상이 발생할 수 있습니다.
  2. NSCache는 이를 방지하기 위해 캐시된 객체의 총 크기를 제한하고, 제한에 도달하면 LRU알고리즘을 사용하여 오래 전에 사용된 객체를 제거합니다.
  3. 이런 추가적인 기능 때문에 딕셔너리보다 많은 오버헤드가 발생합니다.

💁🏻‍♂️ n-6 : setNeedsDisplay와 setNeedsLayout 차이점을 말해주세요.

  1. 가장 큰 차이점은 ‘어떤 것을 다시 표현하는가’ 입니다.
  2. 두 함수는 모두 update cycle에 동작합니다. 이 지점에서는 view를 배치하고 다시 그리는 작업을 요청합니다.
  3. setNeedsDisplay는 view를 다시 그리는 작업을, setNeedLayout은 layout을 재배치하는 작업을 요청합니다.

💁🏻‍♂️💁🏻‍♂️ 두 함수를 호출하지 않아도 View가 변경 되는데요?

  1. UIView에서는 내부에서 setNeedDisplay를 호출하는 경우가 있습니다.
  2. 예를들어 layoutSubview가 호출 될 때, 자동으로 setNeedsDisplay가 호출되어 view를 다시 그릴 것을 요청합니다.

💁🏻‍♂️ n-7 : 하나의 View Controller 코드에서 여러 TableView Controller 역할을 해야 할 경우 어떻게 구분하는게 좋을까요?

  • tableView에 tag를 달아 switch문을 사용해서 tag번호 별 데이터소스를 달리 사용할 수 있습니다.
  • ViewController에 childController을 여러개 만들어 TableView를 하나씩 맡아 관리할 수 있습니다.
  • Section을 구분하여 switch문을 사용해서 섹션별로 tableView를 달리 보여줄 수도 있습니다.
  • 방법은 워낙 많으니 택1하면 될듯..?
profile
안녕하세요, iOS 와 알고리즘에 대한 글을 씁니다.

6개의 댓글

comment-user-thumbnail
2024년 4월 3일

정말 정리가 잘 되어있네요~!
블로그 보면서 많이 배우고 있습니다.
혹시 해당 글에 질문들을 보면서 제 블로그에 저만의 답변을 조금 정리하고 싶은데 질문 목록을 써도 괜찮을까요?

1개의 답글
comment-user-thumbnail
2024년 10월 21일

안녕하세요! 글정말 감사합니다!! 잘읽었습니다
하나질문드릴게 있는데요
ARC의 경우 컴파일타임에 자동으로 코드를 삽입하지만, 실제 참조카운팅을 통한 메모리해제는 런타임에 진행됨으로 런타임에 동작한다고 볼 수있지 않을까요??

1개의 답글