Swift
iOS Development
UIDocument
Synchronous Programming
Mobile App Development

Open UIDocument synchronously

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

UIDocument is designed around asynchronous file coordination, so there is no official synchronous open API you should call on the main thread. If your code wants "synchronous-looking" behavior, the safer answer is to adapt the asynchronous API into async and await, not to force blocking semantics. You can block on a background queue with a semaphore, but that should be treated as a last resort.

Why UIDocument Opens Asynchronously

Opening a UIDocument may involve file coordination, disk I/O, iCloud-backed files, conflict resolution, and state updates. Apple exposes this through open(completionHandler:) because the framework does not want UI code to freeze while the document is being prepared.

The normal API shape is:

swift
1import UIKit
2
3final class TextDocument: UIDocument {
4    var text: String = ""
5
6    override func load(fromContents contents: Any, ofType typeName: String?) throws {
7        if let data = contents as? Data,
8           let value = String(data: data, encoding: .utf8) {
9            text = value
10        }
11    }
12}
13
14let url = FileManager.default.temporaryDirectory.appendingPathComponent("note.txt")
15let document = TextDocument(fileURL: url)
16
17document.open { success in
18    print("Opened:", success)
19}

That completion handler is the intended control flow.

Preferred Modern Solution: Wrap It In async And await

If you want code that reads top-to-bottom, wrap the completion-based API in a Swift concurrency helper.

swift
1import UIKit
2
3extension UIDocument {
4    func openAsync() async throws {
5        try await withCheckedThrowingContinuation { continuation in
6            self.open { success in
7                if success {
8                    continuation.resume()
9                } else {
10                    continuation.resume(throwing: CocoaError(.fileReadUnknown))
11                }
12            }
13        }
14    }
15}

Now usage becomes much cleaner:

swift
1import UIKit
2
3func loadDocument(at url: URL) async {
4    let document = TextDocument(fileURL: url)
5
6    do {
7        try await document.openAsync()
8        print(document.text)
9        document.close(completionHandler: nil)
10    } catch {
11        print("Failed to open document:", error)
12    }
13}

This is still asynchronous under the hood, but it gives you the linear style most people actually want when they say "synchronous."

If You Absolutely Must Block

Sometimes you are integrating with older code that cannot be rewritten immediately. In that case, you can create synchronous behavior by blocking a background thread until the completion handler fires.

swift
1import UIKit
2
3func openSynchronouslyOnBackgroundQueue(document: UIDocument) -> Bool {
4    let semaphore = DispatchSemaphore(value: 0)
5    var result = false
6
7    document.open { success in
8        result = success
9        semaphore.signal()
10    }
11
12    semaphore.wait()
13    return result
14}

This works mechanically, but it has strict conditions:

  • do not call it on the main thread
  • do not use it where blocking can deadlock surrounding code
  • treat it as a migration bridge, not the preferred architecture

If you block the main thread, the app can become unresponsive and you may introduce subtle coordination problems.

A Better Mental Model

The real question is usually not "how do I force UIDocument to be synchronous" but "how do I make the next step wait for the document correctly?"

There are three good answers:

  • use the completion handler directly
  • wrap it in async and await
  • move later work into a callback or task that starts after the document is open

All three preserve the framework's intended non-blocking behavior.

Reading Data After Open

Remember that UIDocument subclasses usually expose the loaded content after load(fromContents:ofType:) runs during the open sequence. That means your code should read document state only after open succeeds.

swift
1Task {
2    let document = TextDocument(fileURL: url)
3    try await document.openAsync()
4    print("Loaded text:", document.text)
5}

Trying to read the document before the open sequence completes is a logic bug, not a timing inconvenience.

Common Pitfalls

  • Blocking the main thread with a semaphore to simulate synchronous open.
  • Treating UIDocument like a plain file read when it also participates in file coordination.
  • Reading document properties before open has completed successfully.
  • Forgetting to close the document when finished.
  • Writing new code in callback-blocking style instead of adopting Swift concurrency.

Summary

  • 'UIDocument does not provide a true synchronous open API for normal app code.'
  • The intended API is open(completionHandler:).
  • If you want linear control flow, wrap it in async and await.
  • A semaphore-based synchronous bridge can work only on a background queue and should be used sparingly.
  • The right fix is usually to adapt your control flow, not to fight the framework's asynchronous design.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.