생각창고

iOS 개발자 면접 질문 리스트 본문

iOS/개발자 면접질문

iOS 개발자 면접 질문 리스트

Ugly_Developer 2020. 6. 24. 14:36

iOS


ARC(Automatic Reference Counting)에 대해 설명하세요.

  • ARC란?
    • ARC (Automatic Reference Counting)란,  자동 레퍼런스 카운팅으로서 자동으로 메모리를 관리해주는 방식을 말한다. 참조 카운팅이 0이 될때만 메모리에서 해제한다는 뜻이다.
  • 동작 원리
    • 클래스의 새로운 인스턴스를 만들 때 ARC는 인스턴스의 정보를 저장하기 위해 메모리를 할당합니다. 또한 ARC는 인스턴스가 더 이상 사용되지 (참조 카운팅 0) 않는다고 판단하면 메모리를 해제합니다. 레퍼런스 프로퍼티에 인스턴스를 할당하면 ARC는 참조되는 프로퍼티의 갯수를 카운팅하여 참조하는 모든 변수가 인스턴스를 해제하기 전에 ARC는 인스턴스를 메모리에서 해제하지 않습니다.
  • 동작 시점
    • 컴파일 시점에 동작한다. 
  • 구체적인 예시 설명

 

class Person {
    let name: String
    init(name: String) {
        self.name = name
        println("\(name) is being initialized")
    }

    deinit {
        println("\(name) is being deinitialized")
    }
}

Person 클래스는 이니셜라이저를 가지며 인스턴스의 name 속성을 설정하고 초기화 진행 중이다고 표시하는 메시지를 출력합니다.

Person 클래스는 디이니셜라이저를 가지며 클래스의 인스턴스가 해제될 때 메시지를 출력합니다.

 

// Person 클래스 타입을 갖는 reference 변수 3개를 선언. 모두 옵셔널 변수이므로 초기값은 nil을 갖고 있습니다.
var reference1: Person?
var reference2: Person?
var reference3: Person?

//reference1 변수에 Person 인스턴스 생성하여 참조하게됩니다.
reference1 = Person(name: "John Appleseed")

//나머지 두 변수를 reference1를 참조하게 합니다.
reference2 = reference1
reference3 = reference1

/*이시점의 인스턴스에 대한 참조 횟수는 3이된다. 그런 후 reference1, reference2 참조 해지합니다. 
그렇게 되면 Person 인스턴스에 대한 참조 횟수는 아직 1이어서 Person 인스턴스는 해지되지 않습니다.*/
reference1 = nil
reference2 = nil

/*
Pesron 인스턴스를 참조하고 있는 나머지 변수 refernce3의 참조 해지하면 
더이상 Person 인스턴스를 참조하고 있는 것이 없으므로 ARC가 Person 인스턴스 메모리를 해지합니다.
*/
reference3 = nil

ARC와 GC(Garbage Collection)의 차이점

메모리 관리 기법 ARC GC
참조 카운팅 시점 컴파일 시 프로그램 동작 중
장점 1. 컴파일 당시 이미 인스턴스의 해제 시점이 정해져 있어서 인스턴스가 언제 메모리에서 해제될지 예측할 수 있습니다.
2. 컴파일 당시 이미 인스턴스의 해제 시점이 정해져 있어서 메모리 관리를 위한 시스템 자원을 추가할 필요가 없습니다. 
1. 상호 참조 상황 등의 복잡한 상황에서도 인스턴스를 해제할 수 있는 가능성이 더 높습니다.
2. 특별히 규칙에 신경 쓸 필요가 없습니다.
단점 ARC의 작동 규칙을 모르고 사용하면 인스턴스가 메모리에서 영원히 해제되지 않을 가능성이 있습니다. 1. 프로그램 동작 외에 메모리 감시를 위한 추가 자원이 필요하므로 한정적인 자원 환경에서는 성능 저하가 발생할 수 있습니다.
2. 명확한 규칙이 없기 때문에 인스턴스가 정확히 언제 메모리에서 해제될지 예측하기 어렵습니다.

Weak와 Strong에 대해 설명하세요. ( + unowned)

  • Strong(강한참조) - strong은 객체를 소유하여 레퍼런스 카운트가 증가하는 프로퍼티이다. 값 지정 시점에 retain이 되고 참조가 종료되는 시점에 release가 된다.
  • Weak(약한참조) - weak은 객체를 소유하지 않고 주소값만을 가지고 있는 포인터 개념이다. 자신이 참조는 하지만 weak 메모리를 해제시킬 수 있는 권한은 다른 클래스에 있다. 값 지정시 리테인이 발생하지 않는다. 따라서 릴리즈도 발생하지 않는다. 그래서 언제 어떻게 메모리가 해제될 지 알 수가 없다. 다만 메모리가 해제될 경우 자동으로 레퍼런스가 nil로 초기화를 해 준다. 그렇기 때문에 Weak 속성을 사용하는 객체는 항상 옵셔널타입이여야 한다.
  • Unowned(미소유 참조) - weak와 매우 비슷한 역할을 한다. 차이점으로는 Unowned로 선언된 변수는 nil이 될수가 없습니다. 그러므로 Unowned 변수는 옵셔널로 선언되어서는 안된다. 해제된 메모리 영역을 접근하지 않는다는 확실한 경우에만 사용해야한다.

  어느 상황에 사용하는가

  • strong - 레퍼런스 객체가 할당되는 순간 해당 객체의 레퍼런스 카운트를 증가 시킨다. 레퍼런스 카운트를 증가시켜 ARC로 인한 메모리 해제를 피하고 객체를 안전하게 사용하고자 할 때 쓰인다.
  • weak - 객체가 할당될 때 레퍼런스 카운트를 증가시키지 않는다. 이 키워드는 Optional 타입에만 적용이 된다. 객체가 ARC에 의해 해제가 되면 nil 값이 할당된다. 대표적으로 retain cycle에 의해 메모리가 누수되는 문제를 막기 위해 사용되며, iOS 프레임워크에서 대표적인 예로는 Delegate패턴이 있다.
  • unowned - 객체가 할당될 때 레퍼런스 카운트를 증가시키지 않는다. 그러나 Non-Optional 타입으로 선언되어야 하며, 객체가 ARC에 의해 메모리해제가 되더라도, 해당 객체 값을 존재하는 것으로 인지하며, 해당 객체에 액세스 할 경우 런타임 오류를 발생시킨다. 객체의 라이플사이클이 명확하고 개발자에 의해 제어 가능이 명확한 경우 , weak Optional 타입 대신 사용하여 좀더 간결한 코딩이 가능하다.

클로저 블럭내에서 [weak self] in 코드를 넣는 방법으로 순환 참조를 해결하는데 이때 [weak self] in 코드가 하는 역할과 그 이유는?

  • [weak self]의 역할로는 ARC가 프로퍼티의 갯수를 카운팅 하지 않도록 만들며 카운팅이 되지 않기에 순환참조가 일어나지 않도록 만드는 역할을 합니다. 그 이유로는 weak 참조는 ARC에 의해 참조되는 인스턴스가 메모리에서 해제 될 때 프로퍼티의 값을 nil로 만들기 때문에 순환 참조가 발생하지 않습니다.

Escaping Closure의 개념이 무엇인가요?

메서드 파라미터로 전달받은 closure 를 메서드의 라이프사이클 내에서 실행하여 끝내지 않고, 메서드 scope 의 외부에 전달하려 할 때는 해당 closure 를 escaping 해야한다.

해당 메서드의 호출이 끝난 이후에도 closure 는 메모리 어딘가에 저장되어야 하며, 이는 closure 안에서 사용된 outer object (self 와 같은) 에 weak 와 같은 레퍼런스타입을 사용해야할 수 있음을 주의하도록 한다.

escaping 이 명시되어있지 않으면 기본적으로 non-escaping 이며, 이는 메서드의 실행이 끝나기 전에 closure 의 사용이 모두 완료됨을 보장하며,

따라서 closure 내에서 weak 을 굳이 사용하지 않아도 안전할 수 있음을 의미하기도 한다.


타입 캐스팅을 할 때 사용하는 키워드인 as, as?, as! 이 셋의 차이는 무엇인가요?

  • as
    • 컴파일러가 타입 변환의 성공을 보장. 컴파일타임에 가능/불가능 여부를 알 수 있음
  • as?
    • 타입변환에 실패하는 경우 nil 을 리턴. 컴파일타임에 가능/불가능 여부를 알 수 없음
  • as!
    • 타입변환에 실패하는 경우 실행시간(Runtime) 오류를 발생시킴. 컴파일타임에 가능/불가능 여부를 알 수 없음

Swift에서 Class와 Struct의 차이는 무엇인가요?

Class - Reference type

  • 객체화 시 힙 메모리영역에 저장되며 ARC 로 객체의 메모리해제가 관리된다.
  • 대입 연산 시 레퍼런스가 복사되어 할당됨. (공유 가능)
  • 멀티스레딩 시 적절한 Lock 활용이 필요.
  • 상속 가능.

Struct - Value type

  • 대입 연산 시 값 자체가 복제되어 할당됨(공유가 불가능).
  • 불변성(Immutable) 구현에 유리.
  • 멀티스레딩에 안전함.
  • 상속이 불가능. (protocol 은 사용 가능)

Frame 과 Bounds 의 차이는 무엇인가요?

  • Frame
    • SuperView(상위뷰) 좌표시스템 내에서의 view 의 위치(origin) 과 크기(size)
  • Bounds
    • view 자기 자신의 좌표시스템에서의 위치와 크기. 부모부와의 위치관계와는 아무런 관계가 없다.
    • 자기 자신의 좌표시스템을 가리키기 때문에 기본적으로 origin 은 x:0, y:0 을 가리킨다.
    • bounds 의 origin 을 변경한다는 것은 곧, subview 들이 화면상에서 drawing 되는 위치가 변경됨을 의미한다
    • 이게 subview 들의 frame 값을 변화시키는게 아니다. 부모뷰 좌표축이 변하면서 subview 가 그려져야하는 위치가 달라졌기 떄문이다.
    • ScrollView/TabeView 등을 스크롤 할때, scrollView.bounds 가 변하고, 그리하여 subview 들의 그려지는 위치가 달라지는 것이 대표적인 예 이다. (subview 들의 frame 이 달라지는게 아님!)

UIStackView의 장점은 무엇이라고 생각하시나요?

여러 뷰를 가로방향 또는 세로방향으로 배치할 때, 복잡한 컨스트레인트 설정 없이, 또는 컨스트레인트 만으로 설정하기 어려운 뷰의 배치등을 구현할 때 쓰일 수 있는 뷰.

aggangedSubview 로 하위뷰들이 관리되며, 이 하위뷰들에 Axis(가로 세로 방향), Alignment(세로방향 정렬), Distribution(가로방향 정렬), Spacing(하위뷰들간의 간격) 의 규칙을 적용할 수 있다.


Autolayout Constraint의 Priority의 개념이 무엇이고, 어떤상황에 사용하나요?

말그대로 제약들간의 우선순위를 말한다.

다수의 뷰들에 여러제약이 걸려있을 때, 보통은 제약간의 충돌이 일어나지 않게끔 제약들을 설계하는게 일반적이지만,

상황에 따라서는 뷰들의 크기가 유동적으로 변하는 경우가 있는데, 이럴때 어떤 제약들이 서로간에 충돌이 일어나는 경우가 있을 수 있다.

이럴때에는 어떤 제약의 우선순위를 더 우위에 둘것이냐를 결정함으로써 이러한 충돌을 해결할 수 있다.


UICollectionViewLayout클래스에 prepare 메소드는 어떤 역할을 하나요?

레이아웃관련 연산이 일어날 때마다 가장 먼저 호출된다. 이 메소드에서 셀의 위치/크기 등을 계산하기 위한 사전처리를 할 수 있다.

UICollectionViewLayout 를 상속받아 Custom 한 CollectionView Layout 을 구성하고자 할때, 데이터소스를 참조하여 셀의 위치 및 크기를 미리 계산하여 캐싱해두고, CollectionView 로부터 셀의 위치 및 크기 요청이 들어올때, 미리 계산하여 캐싱해둔 데이터를 전달해주는 방식으로 커스텀 레이아웃을 구성하는 방식이 있겠다.


UITableView를 구성할때 셀의 컨텐츠에 따라 높이를 설정하고싶다면 어떻게 해야하나요?

델리게이트 메서드 rowHeight 에서는 UITableView.automaticDimension 값을 리턴하고 ,estimatedRowHeight 에서는 셀의 예측 높이값을 리턴한다. 이렇게 하면 오토레이아웃 테이블뷰 셀 구현이 가능하다.

마찬가지로 테이블뷰 셀은, 고정 높이가 아닌, 셀 안의 서브뷰들의 제약 구성으로 셀의 크기가 결정될 수 있도록 해야한다.


ViewController의 생명 주기

  • loadView - 컨트롤러가 관리하는 뷰를 만든다. 뷰컨트롤러가 생성되고 순차적으로 완성되었을때만 호출된다.
  • viewDidLoad - 컨트롤러의 뷰가 메모리에 올라간 뒤에 호출된다. 뷰가 생성될때만 호출된다.
  • viewWillAppear - 화면에 뷰가 표시될때마다 호출된다. 이 단계는 뷰는 정의된 바운드를 가지고 있지만 화면회전은 적용되지않는다.
  • viewWillLayoutSubviews - 뷰컨트롤러에게 그 자식뷰의 레이아웃을 조정하는 것에 대한 것을 알려주기위해 호출된다. 이 메소드는 frame이 바뀔때마다 호출된다.
  • viewDidLayoutSubviews - 뷰가 그 자식 뷰의 레이아웃에 영향을 준 것을 뷰컨트롤러에게 알려주기 위해 호출된다. 뷰가 그 자식 View의 레이아웃을 바꾸고난 뒤에 추가적인 변경을 하고 싶을때 사용하는 이벤트 함수
  • viewDidAppear - 뷰가 나타났다는 것을 컨트롤러에게 알리는 역할을 한다. 호출되는 시점으로는 뷰가 화면에 나타난 직후에 실행된다.
  • viewWillDisAppear - 뷰가 사라지기 직전에 호출되는 함수이다. 뷰가 삭제 되려고하고있는 것을 ViewController에게 알린다.
  • viewDidDisappear - ViewController에게 View가 제거되었음을 알린다. 호출시점은 viewWillDisAppear 다음에 호출된다.

App의 생명주기 (AppDelegate)

Not Running: 앱이 실행되지 않은 상태
(Inactive와 Active 상태를 합쳐서 Foreground 라고 함)
Inactive: 앱이 실행중인 상태 그러나 아무런 이벤트를 받지 않는 상태
Active: 앱이 실행중이며 이벤트가 발생한 상태
Background: 앱이 백그라운드에 있는 상태 그러나 실행되는 코드가 있는 상태
Suspened: 앱이 백그라운드에 있고 실행되는 코드가 없는 상태
  • application(_:didFinishLaunching:) - 앱이 처음 시작될 때 실행
  • applicationWillResignActive: - 앱이 active 에서 inactive로 이동될 때 실행
  • applicationDidEnterBackground: - 앱이 background 상태일 때 실행
  • applicationWillEnterForeground: - 앱이 background에서 foreground로 이동 될때 실행 (아직 foreground에서 실행중이진 않음)
  • applicationDidBecomeActive: - 앱이 active상태가 되어 실행 중일 때
  • applicationWillTerminate: - 앱이 종료될 때 실행 

고차함수(Map, Reduce, Filter)에 대해 설명하세요

Swift에서 함수는 일급시민으로 취급되기 때문에 다른 함수의 전달인자로 사용될 수 있다. 고차함수란 매개변수로 함수를 받는 함수를 말한다.

  • Map - 데이터를 변형하고자 할 때 사용한다. 기존 컨테이너의 값들은 변경되지 않고 새로운 컨테이너를 생성하여 반환한다.  
    장점 - 코드 재사용 용이  / 컴파일러 최적화 측면에서 성능이 좋다 / 다중 스레드 환경에서, 하나의 컨테이너에 여러 스레드들이 동시에 변경을 하려고 할 때 예측하지 못한 결과 발생을 방지
let numbers = [0, 1, 2, 3, 4]

var doubledNumbers = [Int]()
var strings = [String]()

// for-in
for number in numbers {
    doubledNumbers.append(number * 2)
    strings.append("\(number)")
}

// map
doubledNumbers = numbers.map{ $0 * 2 }
strings = numbers.map{ "\($0)" }

 

  • Fileter -  컨테이너 내부의 값들을 걸러서 추출하고자 할 때 사용한다. filter의 매개변수로 전달되는 함수의 반환 타입은 Bool이다. true라면 값을 포함하고 false면 배제하여, map과 마찬가지로 새로운 컨테이너를 생성하여 반환한다.
let numbers = [0, 1, 2, 3, 4, 5]

let evens = numbers.filter{ $0 % 2 == 0 } // [0, 2, 4]
let odds = numbers.map{ $0 + 3 }.filter{ $0 % 2 != 0 } // [3, 5, 7]

 

  • Reduce - 컨테이너 내부를 하나로 합쳐주는 기능을 한다. 정수 배열이라면 전달받은 함수의 연산 결과로 합쳐주고, 문자열 배열이라면 문자열을 하나로 합쳐준다. 첫 번째 매개변수를 통해 초깃값을 지정할 수 있다. 이 초깃값이 최초의 $0 으로 사용된다.
let numbers = [1, 2, 3]
var sum = numbers.reduce(10) { $0 + $1 } // 16

 


동기 비동기에 대해 설명하세요.

 

동기 (Synchronize) - 주어진 명령을 차례대로 처리하되 하나의 업무가 완료될 때 까지는 다른 업무로 넘어가지 않는 방식입니다.

특징 : 중간에 대기하는 시간때문에 효율은 떨어지지만 일관된 업무 보장과 동시다발적 업무가 발생하지 않으므로 대응이 불필요하여 업무구성이 단순화됨

 

비동기 (Asynchronize) -  주어진 명령을 차례대로 처리하되 시간이 걸리는 업무는 진행 해둔 채 기다리는 동안 다른 업무를 처리하는 방식입니다.

특징 : 일관적인 업무 흐름이 깨지고 응답에 대한 대응이 필요하다.


GCD에 대해 설명하세요.

GCD - Multicore Process를 위한 Thread programming의 방법이다. 스레드를 관리하면서 동시적으로 작업을 실행시키는 저수준 API를 제공하는 라이브러리이다.


스레드에 대한 개념을 설명해주세요.

 

하나의 프로세스 내에서 실행되는 작업흐름의 단위로 프로세스가 시작하는 동시에 동작하는 스레드를 메인 스레드라하고 이외의 추가로 생성되는 스레드를 서브 스레드라 부릅니다. 

 

그렇다면 멀티 스레드란 무엇인가요.

 

여러 개의 스레드가 동시에 진행되는 것을 의미합니다. 하나의 프로세스 내에서 여러 개의 스레드가 존재하고, 스레드들이 프로세스의 자원을 공유하되 실행은 독립적으로 이루어지는 구조입니다.

 

추가로 Concurrency한 작업을 하기 위해 iOS에서 지원해주는 API에 대해 설명해주세요.

1.GCD

Grand Central Dispatch(GCD)는 멀티코어와 멀티 프로세싱 환경에서 최적화된 프로그래밍을 할 수 있도록 애플이 지원하는 저수준 API입니다. 기본적으로 스레드 풀의 관리를 프로그래머가 아닌 운영체제에서 관리하기 때문에 프로그래머가 태스크(작업)을 비동기적으로 쉽게 사용할 수 있습니다. 프로그래머가 실행할 태스크(작업)을 생성하고 Dispatch Queue에 추가하면 GCD는 태스크(작업)에 맞는 스레드를 자동으로 생성해서 실행하고 작업이 종료되면 해당 스레드를 제거합니다.

 

디스패치 대기열(Dispatch Queue)

디스패치 대기열(Dispatch Queue)은 작업을 연속적 혹은 동시에 진행하기는 하지만, 언제나 먼저 들어오면 먼저 나가는 순서로 실행됩니다. Serial Dispatch Queue는 한 번에 하나의 작업만을 실행하며, 해당 작업이 대기열에서 제외되고 새로운 작업이 시작되기 전까지 기다립니다. 이와는 반대로 Concurrent Dispatch Queue는 이미 시작된 작업이 완료될 때까지 기다리지 않고 가능한 많은 작업을 진행합니다. 디스패치 대기열(Dispatch Queue)은 GCD 기술 일부입니다

 

2.NSOperation 

NSOperation은 어떤 하나의 작업을 나타냅니다. NSOperation은 모델링 상태, 우선순위, 의존성 그리고 관리를 지원하는 유용하고 Thread safe한 추상 클래스입니다.

 

예를 들어 네트워크 요청, 이미지 리사이즈, 텍스트 처리, 또는 기타 다양한 반복처리등 오래 걸리는 작업을 처리해야하는 NSOperation이 있다고 할 때, 이 특정 작업이 담겨있는 객체는 감독 없이 많은 일을 할 수 없습니다. 이러한 작업을 진행하는 감독을 NSOperationQueue가 담당합니다.


동시성 프로그래밍 과 병렬성 프로그래밍에 대하여 설명해주세요.

 

 

동시성 프로그래밍

말 그대로 동시에 여러 프로그램을 돌리는 것이 아닌 그렇게 보이게끔 하는 기법으로 시간을 분할하여 스레드들에게 서로 번갈아가며 제공하여 실행되게끔 하는 방식입니다. 코어에 상관 없이 실행 가능한 기법으로 싱글 코어에서도 멀티스레딩을 가능케 합니다.

 

병렬성 프로그래밍

동시성 프로그래밍과 다르게 동시에 돌아가는 것처럼 보이는 것이 아닌 실제로 동시에 실행되는 것을 의미합니다! 멀티 코어에서 멀티 스레드를 동작시키는 방식으로 데이터 병렬성과 작업 병렬성으로 나뉘게 됩니다.

 

  • 데이터 병렬성 - 처리해야 할 데이터를 나누어 나누어진 데이터들을 병렬 처리하여 작업을 빠르게 수행하는 기법
  • 작업 병렬성 - 서로 다른 작업을 병렬 처리하는 기법

동시성과 병렬성 차이

동시성 프로그래밍과 병렬성 프로그래밍 모두 비동기적으로 구현할 수 있습니다. 하지만 둘은 다음과 같은 차이가 존재합니다.

 동시성병렬성

개념 논리적 물리적
동작 가능 환경 싱글코어, 멀티 코어 멀티 코어