https://developer.apple.com/documentation/uikit/uidocument
"An abstract base class for managing discrete portions of your app's data."
앱의 데이터 부분들을 관리하는 추상 베이스 클래스입니다.
@MainActor class UIDocument : NSObject
UIDucument
및 UIDocument
의 기본 아키텍처의 사용을 담은 앱은 문서에서 여러 가지 이점을 갖습니다.
MVC 디자인 패턴에서 UIDocument
객체는 모델 객체이거나 모델 컨트롤러 객체입니다. 이는 문서의 데이터를 관리하거나 문서의 데이터를 함께 구성하는 모델 객체를 통합합니다. 문서의 컨텐츠를 보여주는 뷰를 관리하기 위한 뷰 컨트롤러와 짝을 이루게 될 것입니다. UIDocument
는 문서 뷰를 관리하는 것에 대해서 지원하지는 않습니다.
문서 베이스의 앱은 파일 시스템 위치를 가지고 있는 각각의 문서 여럿을 생성할 수 있는 것들을 포함합니다. 문서 베이스 앱은 문서를 위한 UIDocument
서브클래스를 생성해야 합니다. 자세한 내용은 아래에 소개하고 있는 Subclassing Notes를 살펴보시기 바랍니다.
Note
문서 데이터 저장을 위한 데이터베이스를 사용하는 경우UIDocument
대신UIManagedDocument
클래스의 서브클래스를 생성해야 합니다.UIKManagedDocument
는UIDocument
의 서브클래스입니다.
UIDocument
아키텍처에 있는 문서의 기본 속성은 파일 URL입니다.init(fileURL:)
을 호출함으로써 문서 서브클래스의 인스턴스를 초기화할 때, 앱 샌드박스에서 문서 파일을 찾는 파일 URL을 전달해야 합니다. UIDocument
는 파일 타입(파일 확장과 관련이 있는 유니폼 타입 아이덴티파이어)을 결정하고, 파일 URL로부터 문서 이름(파일이름 요소)을 결정합니다. fileType
과 localizedName
속성의 accessor 메소드를 오버라이드해서 다른 값을 제공하도록 할 수 있습니다.
아래 아웃라인은 전형적인 문서의 생명주기입니다. (구현에 대한 내용은 Subclassing Notes를 살펴보시기 바랍니다.)
save(to:for:completionHandler:)
를 호출합니다.open(completionHandler:)
를 호출합니다.UIDocument
는 주기적으로 저장되지 않은 변경사항이 있을 때 이를 기억하고, 파일에 문서 데이터를 씁니다.close(completionHandler:)
를 호출합니다. UIDocument
는 저장되지 않은 변경사항이 있는 경우 문서를 저장합니다.전형적인 문서 베이스 앱은 open(completionHandler:)
, close(completionHandler:)
, save(to:for:completionHandler:)
메소드를 메인 스레드에서 호출합니다. 읽기 혹은 저장 작업이 종료되면, 컴플리션 핸들러 블록은 메소드가 호출된 같은 디스패치 뷰에서 실행됩니다. 이는 읽기 혹은 저장 작업에 대해 모든 작업을 완료시킬 수 있도록 해줍니다. 만약 작업이 성공적이지 않다면, false
가 컴플리션 핸들러를 통해 전달됩니다.
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 메소드를 호출할 수 있음을 인식하시기 바랍니다. 그러므로 오버라이드 구현은 스레드 세이프한 상태가 되어야 합니다.