Fix security-scoped URL access balancing

This commit is contained in:
世界
2026-02-04 22:32:02 +08:00
parent 20e22b691a
commit 4ae421cd04
8 changed files with 77 additions and 32 deletions
@@ -0,0 +1,57 @@
import Foundation
public extension URL {
/// Best-effort security-scoped access:
/// - If `startAccessingSecurityScopedResource()` succeeds, access is relinquished via `stop...` when `body` completes.
/// - If it fails, `body` still runs (useful for non-security-scoped but otherwise accessible URLs).
@discardableResult
func withSecurityScopedAccess<T>(_ body: () throws -> T) rethrows -> T {
let didStart = startAccessingSecurityScopedResource()
defer {
if didStart {
stopAccessingSecurityScopedResource()
}
}
return try body()
}
/// Async best-effort variant of `withSecurityScopedAccess`.
@discardableResult
func withSecurityScopedAccess<T>(_ body: () async throws -> T) async rethrows -> T {
let didStart = startAccessingSecurityScopedResource()
defer {
if didStart {
stopAccessingSecurityScopedResource()
}
}
return try await body()
}
/// Required security-scoped access:
/// - If `startAccessingSecurityScopedResource()` fails, throws `error` and does not run `body`.
/// - Otherwise, guarantees a balanced `stop...` when `body` completes.
@discardableResult
func withRequiredSecurityScopedAccess<T>(
or error: @autoclosure () -> any Error,
_ body: () throws -> T
) throws -> T {
guard startAccessingSecurityScopedResource() else {
throw error()
}
defer { stopAccessingSecurityScopedResource() }
return try body()
}
/// Async required variant of `withRequiredSecurityScopedAccess`.
@discardableResult
func withRequiredSecurityScopedAccess<T>(
or error: @autoclosure () -> any Error,
_ body: () async throws -> T
) async throws -> T {
guard startAccessingSecurityScopedResource() else {
throw error()
}
defer { stopAccessingSecurityScopedResource() }
return try await body()
}
}