UIDocument

Panther·2021년 7월 27일
0

https://developer.apple.com/documentation/uikit/uidocument

"An abstract base class for managing discrete portions of your app's data."

앱의 데이터 부분들을 관리하는 추상 베이스 클래스입니다.

Declaration

@MainActor class UIDocument : NSObject

Overview

UIDucumentUIDocument의 기본 아키텍처의 사용을 담은 앱은 문서에서 여러 가지 이점을 갖습니다.

  • 백그라운드 큐에서 비동기로 데이터의 읽기 및 쓰기가 가능합니다. 사용자에 대한 앱의 반응은 읽기 및 쓰기 작업이 진행되는 동안 영향을 받지 않습니다.
  • 클라우드 서비스에 자동으로 통합된 문서 파일의 공동 읽기 및 쓰기가 가능합니다.
  • 만약 다른 버전의 무서 사이에 충돌이 발생하는 경우 충돌을 발견하는 것에 대한 지원을 해줍니다.
  • 임시 파일에 우선적으로 데이터를 씀으로써 문서 데이터의 안전 저장이 가능합니다. 그리고 현재 문서 파일로 대체시킵니다.
  • 적절한 순간에 문서 데이터의 자동 저장이 됩니다. 이 메커니즘은 중지 동작을 다루기 위한 지원을 포함합니다.

MVC 디자인 패턴에서 UIDocument 객체는 모델 객체이거나 모델 컨트롤러 객체입니다. 이는 문서의 데이터를 관리하거나 문서의 데이터를 함께 구성하는 모델 객체를 통합합니다. 문서의 컨텐츠를 보여주는 뷰를 관리하기 위한 뷰 컨트롤러와 짝을 이루게 될 것입니다. UIDocument는 문서 뷰를 관리하는 것에 대해서 지원하지는 않습니다.

문서 베이스의 앱은 파일 시스템 위치를 가지고 있는 각각의 문서 여럿을 생성할 수 있는 것들을 포함합니다. 문서 베이스 앱은 문서를 위한 UIDocument 서브클래스를 생성해야 합니다. 자세한 내용은 아래에 소개하고 있는 Subclassing Notes를 살펴보시기 바랍니다.

Note
문서 데이터 저장을 위한 데이터베이스를 사용하는 경우 UIDocument 대신UIManagedDocument 클래스의 서브클래스를 생성해야 합니다. UIKManagedDocumentUIDocument의 서브클래스입니다.

UIDocument 아키텍처에 있는 문서의 기본 속성은 파일 URL입니다.init(fileURL:)을 호출함으로써 문서 서브클래스의 인스턴스를 초기화할 때, 앱 샌드박스에서 문서 파일을 찾는 파일 URL을 전달해야 합니다. UIDocument는 파일 타입(파일 확장과 관련이 있는 유니폼 타입 아이덴티파이어)을 결정하고, 파일 URL로부터 문서 이름(파일이름 요소)을 결정합니다. fileTypelocalizedName 속성의 accessor 메소드를 오버라이드해서 다른 값을 제공하도록 할 수 있습니다.

아래 아웃라인은 전형적인 문서의 생명주기입니다. (구현에 대한 내용은 Subclassing Notes를 살펴보시기 바랍니다.)

  1. 새 문서를 생성하거나 기존 문서를 엽니다.
    • 새 문서를 생성하려면 서브클래스의 인스턴스를 할당 및 초기화하고, 인스턴스에 대한 save(to:for:completionHandler:)를 호출합니다.
    • 기존 문서(사용자에 의해 선택된)를 열려면, 서브클래스의 항당 및 초기화를 하고, 인스턴스에 대한 open(completionHandler:)를 호출합니다.
  2. 사용자가 문서를 편집하는 경우
    사용자가 편집을 할때, 문서 변경사항을 추적해야 합니다. UIDocument는 주기적으로 저장되지 않은 변경사항이 있을 때 이를 기억하고, 파일에 문서 데이터를 씁니다.
  3. 사용자가 클라우드 서비스에 통합되길 요청하는 경우입니다(선택적).
    클라우드 저장소에 대한 문서가 가능하도록 해야 합니다. 같은 문서의 다른 버전에 대한 모든 충돌을 해결해야 합니다.
  4. 사용자가 문서를 닫는 경우입니다.
    문서 인스턴스에 close(completionHandler:)를 호출합니다. UIDocument는 저장되지 않은 변경사항이 있는 경우 문서를 저장합니다.

전형적인 문서 베이스 앱은 open(completionHandler:), close(completionHandler:), save(to:for:completionHandler:) 메소드를 메인 스레드에서 호출합니다. 읽기 혹은 저장 작업이 종료되면, 컴플리션 핸들러 블록은 메소드가 호출된 같은 디스패치 뷰에서 실행됩니다. 이는 읽기 혹은 저장 작업에 대해 모든 작업을 완료시킬 수 있도록 해줍니다. 만약 작업이 성공적이지 않다면, false가 컴플리션 핸들러를 통해 전달됩니다.

Implementation of the NSFilePresenter Protocol

UIDocument 클래스는 NSFilePresenter 프로토콜을 채택합니다. UIdocument 베이스 앱의 문서를 다른 클라이언트가 일기를 시도하는 경우 해당 읽기는 UIDocument 객체가 문서에 만들어진 모든 변경사항을 저장할 수 있는 기회를 얻기까지 중지됩니다.

몇 가지 구현은 아무것도 하지 않지만, UIDocument는 모든 NSFilePresenter 메소드르 구현합니다. 구체적으로 UIDocument는 아래 내용을 구현합니다.

  • incoming 블록을 performAsynchronousFileAccess(_:)에 전달하기 위해 relinquishPresentedItem(toReader:)를 구현합니다.
  • 파일 수정 날짜가 변경되었는지를 확인하기 위해 relinquishPresentedItem(toWriter:)를 구현합니다. 이는 URL 파라미터로써 파일 URL의 값과 함께 revert(toContentsOf:completionHandler:)를 호출합니다. 문서의 파일 URL을 업데이트하기 위해 presentedItemDidMove(to:)를 구현합니다.

UIDocument 서브클래스에서 만약 NSFilePresenter를 오버라이드하면, 슈퍼클래스 구현을 항상 호출할 수 있습니다.

Subclassing Notes

모든 문서 베이스 앱은 문서를 나타내는 인스턴스를 갖는 UIDocument 서브클래스를 생성해야 합니다. 대부분의 앱에 대한 서브클래싱 요구사항은 간단합니다.

  • 쓰기 작업의 경우 문서 데이터의 스냅샷을 제공하기 위해 contents(forType:)을 구현해야 합니다. 데이터는 NSData 객체(플랫 파일을 위해) 형식이거나 FileWrapper 객체(파일 패키지를 위해) 형식이어야 합니다. 쓰기 작업은 보통 자동 저장 기능을 통해 초기화됩니다.
  • 읽기 작업의 경우 NSData 혹은 FileWrapper 를 받기 위해 load(fromContents:ofType:) 메소드를 구현해야 하고, 앱의 데이터 구조를 초기화해야 합니다.
  • 자동 저장 기능을 가능하게 하도록 변경사항 추적을 구현해야 합니다. 자세한 내용은 Change Tracking을 살펴보시기 바랍니다.
  • 문서에 대해 클라우드 서비스가 활성화되어 있는 경우 문서의 다른 버전에 대한 충돌을 해결해야 합니다. 자세한 내용은 Conflict Resolution and Error Handling을 살펴보시기 바랍니다.

Change Tracking과 Conflict Resolution and Error Handling은 아래에 나옵니다.

contents(forType:), load(fromContents:ofType:) 메소드는 보통 메인 큐에서 호출됩니다. 더 구체적인 내용은 아래와 같습니다.

  • contents(forType:) 메소드는 save(to:for:completionHandler:) 메소두가 호출되었던 큐에서 호출됩니다. 데이터 쓰기는 백그라운드 스레드에서 발생합니다.
  • load(fromContents:ofType:) 메소드는 open(completionHandler:) 메소드가 호출되었던 큐에서 호출됩니다.

contents(forType:), load(fromContents:ofType:) 메소드로는 충분하지 않아 문서 데이터 읽기 쓰기에 대해 특별한 요구사항을 갖는 경우 UIDocument 클래스의 다른 메소드를 오버라이드할 수 있습니다. 이에 관련한 내용은 Advanced Overrides를 살펴보시기 바랍니다.

Advanced Overrides는 아래에 나옵니다.

Change Tracking

UIDocument의 자동 저장 기능을 가능하게 하려면, 사용자에게 문서 변경사항이 발생했을 때 이를 알려줘야 합니다. UIDocument는 주기적으로 hasUnsavedChanges 메소드가 true를 반환하는지 확인합니다. 만약 그러할 경우 문서를 위해 저장 작업을 초기화합니다.

UIDocument 서브클래스에서 변경사항 추적을 구현하는 두 가지 기본적인 방법이 있습니다.

  • UndoManager 클래스의 메소드를 호출해 문서에 대한 undo와 redo를 구현할 수 있도록 합니다. undoManager 속성으로부터 기본값 UndoManager 객체에 접근할 수 있습니다. 이 접근방식이 권장되는 사항입니다. 특히 이미 undo와 redo를 지원하고 있는 기존 앱에서 그렇습니다.
  • 코드의 적절한 지점에 updateChangeCount(_:) 메소드를 호출합니다.

Conflict Resolution and Error Handling

UIDocument 객체는 스스로 갖는 생명주기 내에서 모든 순간에 특정 상태를 갖고 있습니다. documentState 속성을 쿼리함으로써 현재 상태를 확인할 수 있고, stateChangedNotification 노티피케이션을 관찰함으로써 변경사항에 대한 내용을 전달받을 수 있습니다.

아이클라우드가 가능한 문셔의 경우 버전 충돌을 확인하는 것은 중요합니다. 그리고 충돌을 해결하려는 시도 역시 중요합니다. stateChangedNotification 노티피케이션에 대한 전달 내용을 통해 이를 수행할 수 있습니다. 그리고 문서 상태가 inConflict인지 확인함으로써 이를 수행할 수 있습니다. 이 상태는 문서의 버전에 충돌이 있다는 것을 나타냅니다. 이는 문서의 파일 URL을 전달하는 NSFileVersion 메소드인 unresolvedConflictVersionsOfItem(at:) 메소드를 호출하는 것을 통해 접근할 수 있습니다. 만약 사용자 상호작용 없이 충돌을 정확하게 해결할 수 있다면 그렇게 할 수 있도록 해야 합니다. 그렇지 않은 경우 충돌이 존재한다는 것을 사용자에게 알려야 하고, 사용자가 어떻게 해결할 것인지를 선택할 수 있도록 해야 합니다. 가능한 접근방법은 아래와 같습니다.

  • 사용자가 유지할 버전 한 가지 혹은 모든 버전을 선택할 수 있도록 충돌 버전을 보여주는 것입니다.
  • 병합된 버전을 보여주는 것과 한 가지를 선택할 수 있도록 옵션을 주는 것이 있습니다.
  • 파일 수정일자를 보여주는 것과 하나 혹은 모두를 선택할 수 있는 옵션을 사용자에게 주는 것입니다.

인터파일 충돌을 나타내는 것과 더불어 문서 상태는 에러를 나타낼 수 있습니다. 에를 들어 closed는 읽기에서 에러를 나타냅니다. 그리고 savingError는 저장 혹은 되돌리는 작업에서 에러를 나타냅니다. 앱은 open(completionHandler:), close(completionHandler:), revert(toContentsOf:completionHandler:), save(to:for:completionHandler:) 메소드의 컴플리션 핸들러에 의해 전달되는 성공 파라미터로 읽기와 쓰기 에러를 알려줍니다.

handleError(_:userInteractionPermitted:) 메소드를 호출하거나 구현함으로써 에러를 처리할 수 있습니다. 이 메소드는 UIDocument 객체가 읽기 혹은 쓰기 에러 각각을 마주할 때, open(completionHandler:), save(to:for:completionHandler:) 메소드의 기본값 구현에 의해 호출되는 메소드입니다. 만약 상황이 허용하는 경우 사용자에게 읽기, 저장, 되돌림 에러를 알려줌으로써 에러를 처리할 수 있습니다. 이는 에러로부터 복구를 시도하게 될 것입니다.

문서 저장 동안 마주할 수 있는 에러를 처리하는 가이드 내용인, contents(forType:) 메소드에 있는 설명을 읽으시길 바랍니다.

Advanced Overrides

앱이 문서 데이터에 대한 읽기 혹은 쓰기에서 특별한 요구사항을 갖는 경우 load(fromContents:ofType:), contents(forType:) 메소드가 아닌 다른 메소드를 오버라이드할 수 있습니다. 아래에 이를 포함하는 요구사항이 있습니다.

  • 큰 데이터 파일의 점차 증가하는 읽기 및 쓰기에 대한 내용입니다. read(from:), writeContents(_:to:for:originalContentsURL:) 메소드를 각각 구현해야 합니다.
  • 문서 데이터의 커스텀 표현방법(즉 NSData 혹은 NSFileWrapper 객체가 아닌 경우)입니다.
    read(from:) 메소드를 오버라이드 해야 합니다.(문서 데이터를 읽을 때에 해당합니다.) 그리고 writeContents(_:to:for:originalContentsURL:) 메소드를 오버라이드해야 합니다.(문서 데이터를 쓰는 경우에 해당합니다.)
  • 데이터를 읽기 혹은 쓰기 전이나 후에 액션을 수행하는 경우입니다.
    open(completionHandler:), save(to:for:completionHandler:) 메소드를 오버라이드해야 합니다.
  • 안전 저장을 위한 커스텀 접근방식입니다.
    writeContents(_:andAttributes:safelyTo:for:) 메소드를 오버라이드해야 합니다.
  • 파일이 저장되기 전 문서의 파일 타입을 변경시키는 것입니다.
    기본값(fileType)이 아닌 다른 파일 타입을 반환하기 위해 savingFileType 메소드를 오버라이드해야 합니다. 이 경우의 예시는 사용자가 이미지를 추가한 후 RTF 문서가 RTFD 문서로 저장되어야 하는 경우입니다.

이와 같은 메소드들 대부분을 오버라이드하는 경우 문서 데이터의 모든 읽기와 쓰기는 백그라운드 큐에서 완료되어야 합니다. 그리고 동일한 문서 파일에 대해 읽거나 쓰려는 시도를 조정할 수 있도록 해야 합니다. 이러한 이유로 오버라이드의 부분으로써 슈퍼클래스 구현(super)을 호출해야 합니다. 그리고 다른 UIDocument 메소드를 호출하는 경우 performAsynchronousFileAccess(_:) 메소드의 호출에서 전달되는 블록에 호출해야 합니다. 더 자세한 사항은 메소드 설명을 읽으시길 바랍니다.

Thread Safety Considerations

accessor 메소드를 오버라이드함으로써 문서 속성(Accessing Document Attributes에 목록으로 나와있는)의 오버라이드를 하는 모든 경우 UIKit 프레임워크가 백그라운드 스레드에서 이와 같은 accessor 메소드를 호출할 수 있음을 인식하시기 바랍니다. 그러므로 오버라이드 구현은 스레드 세이프한 상태가 되어야 합니다.

0개의 댓글