총평

  1. iPhone 3GS, iPhone 4/4S 시절에 잠시 했던 경험은 전혀(!) 도움이 되지 않았습니다. 옛 경험은 잊고 새로운 환경에 적응할 수 있는 자신만의 방법이 필요합니다.
  2. 나의 사수가 iOS 개발에 특화된 개발자라 학습 시간이 대폭 줄었습니다. 아마 시간적으로 따지자면 20~30시간 정도는 줄일 수 있다고 판단됩니다. Issue 3개를 5일간 처리했는데, 이 중에서 2개는 전혀 모르는 상태로 시작했다는 것을 감안하면 매우 훌륭한 출발입니다.
  3. 시간만 허락한다면 짝코딩을 통해서 좀 더 개발 시간을 단축할 수 있을 것 같습니다. 큰 틀에 대해서 같이 논의하고 세부적인 사항에 대한 내용을 코드 작성 후 리뷰를 하는 방식은 초급자가 해당 코드에 대한 이해도를 높이고, 코드 설계에 대한 눈높이를 맞출 수 있기 때문입니다(제 개인적인 견해입니다.)
  4. 하지만 우리 사수가 combine을 도입... 난 어쩔...

Cliplog 관련

카메라 & 마이크 퍼미션 요청 #12

현대의 모바일 App은 해당 기능이나 센서가 필요할 때 권한을 요청하는 것을 원칙으로 합니다. 따라서 plist.info에서 권한을 일괄처리 하는 방식은 사용하지 않습니다. 그리고 권한 요청시 사용자가 해당 권한을 거부한 이후에 해당 기능이나 센서를 사용하고자 할 경우 사용자가 직접 설정 설정해야 합니다.

// 대략적인 흐름

// 1. 권한 인증에 필요한 상태 정의
enum SessionSetupResult {
  case success
  case notAuthorized
  case configurationFailed
}

// 1. 초기 상태
var isVideoPermission: SessionSetupResult = .success
var isAudioPermission: SessionSetupResult = .success

// 2. 권한 검사   
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
  break
case .notDetermined:
  AVCaptureDevice.requestAccess(for: .video, completionHandler: { granted in
      if !granted {
          isVideoPermission = .notAuthorized
      }
  })
default:
  isVideoPermission = .notAuthorized
}

switch AVCaptureDevice.authorizationStatus(for: .audio) {
case .authorized:
  break
case .notDetermined:
  AVCaptureDevice.requestAccess(for: .audio, completionHandler: { granted in
      if !granted {
          isAudioPermission = .notAuthorized
      }
  })
default:
  isAudioPermission = .notAuthorized
}

// 3. 권한 검증
if ((isVideoPermission != .success) || (isAudioPermission != .success )) {
  print("Audio and Video not permission")
}

참고자료

카메라 화면을 View에 출력 #2

UIViewController에 카메라 Preview 화면을 붙여야 했으며, 이를 위해서 AVFoundation을 사용하였습니다(물론 나의 사수께서 MetalKit으로 나중에 변경). 이걸 하려니 UIViewController의 Life Cycle과 App의 상태를 알아야 했기 때문에 관련 내용을 정리하였습니다.

화면을 구성하는 요소인 UIView와 UIController에 대한 이해도가 너무 낮아서 기본적으로 주어진 것들을 제대로 사용하지 못하고 코드를 잔망스럽게 만들었습니다. 아래 코드에서 확인 할 수 있듯이 iOS에서 제공하는 기본적인 구성요소에 대한 내용을 충실하게 알아둘 필요가 있습니다.

Life Cycle을 명확한게 이해하지 못하면, 필요한 코드를 적절한 곳에 사용하지 못합니다. 모바일 앱의 경우 대부분의 제어권이 외부에 있기 때문에 Life Cycle에 대해서 심도 깊은 학습이 필요합니다. 한번만 잘 해두면 두 번 괴로울 일이 없으니 잘 알아두도록 합시다.

// 나의 사수(jaemyeong.dev@gmail.com) 코드
class PreviewView: UIView {
        // 
    override class var layerClass: AnyClass {
        return AVCaptureVideoPreviewLayer.self
    }

    var previewLayer:AVCaptureVideoPreviewLayer {
        return self.layer as! AVCaptureVideoPreviewLayer
    }
}

// 이것은 나의 잔망스러운 코드
// 잘 만들어진 코드 같지만 사수꼐서 자바처럼 만들지 말라하셨음
class LiveFilterViewController: UIViewController {

    private var window: UIWindow?
    private var mainView: UIImageView!

    private let captureSession = AVCaptureSession()
    private let previewLayer = AVCaptureVideoPreviewLayer()

    var position:AVCaptureDevice.Position = .back
    var perferredDeviceType:AVCaptureDevice.DeviceType = .builtInWideAngleCamera
    var cameraDeviceInput:AVCaptureDeviceInput?
    var audioDeviceInput:AVCaptureDeviceInput?
    var videoOutput:AVCaptureVideoDataOutput?
    var audioOutput:AVCaptureAudioDataOutput?

    override func viewDidLoad() {
        super.viewDidLoad()
        self.window = UIWindow(frame: UIScreen.main.bounds)
        mainView = UIImageView(frame: (self.window?.frame)!)
        self.view.addSubview(mainView)
        setPreViewLayer()
        getAuthrized(for: [.video, .audio])
    }

   func setPreViewLayer() {
        previewLayer.session = self.captureSession
        previewLayer.videoGravity = .resizeAspectFill
        previewLayer.frame = view.bounds
        mainView.layer.addSublayer(previewLayer)
    }

   ...

Application 실행 상태(execution states)와 관련된 내용 정리

App은 (언제나 그러하듯이) 5가지 상태(state) 중 하나로 iOS 시스템(system)에서 동작합니다.

실행 상태

  • Not Running
    • 실행되지 않았거나 완전히 종료된 상태입니다.
  • Inactive(Foreground)
    • App이 foreground 상태에 있지만 이벤트를 받지 않으며, Active 상태로 넘어가기 전에 잠시동안 이 상태에 있게 됩니다.
  • Active(Foreground)
    • App이 foreground 상태에 있고 이벤트를 받으며, Foreground 상태에 있는 app들은 평소 이 상태에 머물러 있습니다.
  • Background
    • App이 background 상태에서 실행하는 것으로 Background 상태로 실행되는 app은 inactive 대신 background 상태로 진입하며 Suspended 상태로 넘어가기 전에 잠깐 머무르는 상태이며, Suspended 상태로 진입하기 전에 추가적인 코드 수행이 필요하면 좀 더 머무를 수도 있습니다.
  • Suspended
    • App이 background 상태에서 메모리에만 올라가 있고 코드를 실행하지 않습니다. Background에서 추가적인 작업이 없다면 자동으로 suspended 상태로 진입합니다. 주의할 점은 다른 App을 실행하면서 메모리가 부족해지면 iOS system은 suspended 상태에 있는 app들을 별도의 알림 없이 종료시켜서 메모리를 확보합니다.

Application 실행 순서

저는 iOS 꼬꼬마라서 다른 상태는 관심이 별로 없고, Active 상태에 지대한 관심이 있습니다. 기본적으로 App이 화면에 출력되는 과정은 아래와 같습니다.

  1. App이 샐행된다.
  2. UIApplication 객체(@UIApplicationMain) 를 생성한다.
  3. info.plist 를 참고하여 App에 필요한 정보를 가져온다.
  4. AppDelegate 객체 생성 후 UIApplication 객체와 연결한다.
    • AppDelegateUIApplication 객체가 이벤트 및 관련 코드에 관한 것을 위임한 클래스입니다. App당 하나의 위임 객체를 갖도록 보장되며, UIApplicationDelegate 프로토콜(protocol)을 사용합니다.
  5. 주요 객체 및 이벤트-루프(event loop)를 생성하여 실행 준비를 완료한 후, UIApplication 객체는 application(_:willFinishLaunchingWithOptions:)application(_:didFinishLaunchingWithOptions:) 을 차례대로 호출한다.
  6. 이벤트 루프가 실행되고 사용자로부터 받은 event를 event queue를 통해 순서대로 처리하기 위해서 이벤트 핸들(@IBOutlet, @IBAction 등)과 연결한다.
  7. App을 더 이상 사용하지 않으면 applicationWillTerminate(_:) 호출하고, App을 종료한다.

Delegate 호출

App의 상태가 변하면 AppDelegate에 구현된 UIApplicationDelegate 메서드가 호출됩니다. App의 실행 상태에 따라 필요한 메서드를 실행할 수 있습니다.

Not Running

  • application(_:willFinishLaunchingWithOptions:)
    • App 실행을 위한 초기화가 완료되기 직전에 호출되며, App 실행 시 최초로 실행할 코드를 작성합니다.
  • application(_:didFinishLaunchingWithOptions:)
    • App이 실행되고 화면이 사용자에게 보여지기 직전에 호출되며, 주로 App 실행 후에 최종 초기화 코드를 작성합니다.

Foreground

  • applicationDidBecomeActive(_:)
    • App이 Not Running 상태에서 Foreground(Inactive, Active) 상태로 진입할 때 호출되며, App이 실제로 사용되기 전에 마지막으로 준비할 수 있는 코드를 작성합니다.
  • applicationWillResignActive(_:)
    • App이 Foreground(Active) 상태에서 다른 상태로 진입할 때 호출되며, App이 quiescent 상태로 변환될 때의 작업을 진행합니다.

Background

  • applicationDidEnterBackground(_:)
    • App이 Background 상태에 진입했을 때 호출되며, 특별한 처리가 없으면 Background 상태에서 Suspended 상태로 전화됩니다.
    • App이 Suspended 상태로 진입하기 전에 중요한 데이터를 저장하거나 점유하고 있는 공유 자원을 해제하는 등 종료되기 전에 준비 작업을 진행하며, App이 종료된 이후 다시 실행될 때 직전 상태를 복구할 수 있는 정보를 저장합니다.
  • applicationWillEnterForeground(_:)
    • App이 Background에서 Foreground로 돌아오기 직전 호출되며, Method가 호출된 뒤 Inactive 상태를 거쳐 Active 상태로 진입합니다.

Terminate

  • applicationWillTerminate(_:)
    • App이 종료되기 직전에 호출됩니다. App이 곧 종료될 것임을 알려줍니다.
    • 다음 경우에는 호출되지 않습니다.
      • 메모리 확보를 위해 Suspended 상태에 있는 app을 종료시킬 때
      • 사용자가 multitasking UI를 통해 종료할 때
      • 오류로 인해 app이 종료될 때
      • Deivce를 재부팅할 때

UIViewController의 Life Cycle

  • loadview
    • UIViewController의 view를 코드를 사용해서 생성하고 싶다면 오버라이드 사용하는 것으로 직접 호출해선 안되는 코드입니다.
    • UIViewController의 속성(property)인 view를 다른 커스텀 뷰 클래스의 인스턴스나 혹은 다른 뷰로 변경해서 실행하고 싶을 때 사용합니다.
    • 만약 해당 메서드를 오버라이드 하면 IB에서 연결한 뷰들은 UIViewController에서 사용할 수 없습니다.
  • loadViewIfNeeded
    • ViewController에서 관련 View를 호출 할 때 사용하는 메서드입니다.
  • viewDidLoad
    • View의 controller가 메모리에 로드되고 난 후에 호출되는 것으로 View가 메모리에 적재되어 해당 객체를 사용할 수 있을 때 시스템에 의해서 자동으로 호출되는 메서드입니다. 한번만 실행되기 떄문에 초기화 작업을 진행할 때 작성하면 됩니다.
  • viewWillAppear
    • View 객체가 화면 출력되기 직전에 호출되는 것으로 다른 View에서 복귀할 때 처리해야 할 내용이 있다면 이 부분에서 처리합니다.
  • viewDidAppear
    • viewDidAppear는 View가 화면체 출력되었을 때 알리는 역할을 하며, 화면에 적용될 애니메이션을 적용할 때 나타납니다.
  • viewWillDisappear
    • viewWillDisAppear는 View가 사라지기 직전에 호출되는 함수이며, 해당 View가 삭제 될 때 호출됩니다.
  • viewDidDisappear
    • view가 제거되었을 때 호출되는 됩니다.