Skip to content

Commit af0facf

Browse files
committed
Enable strict concurrency
1 parent f6ded12 commit af0facf

26 files changed

+159
-66
lines changed

.swiftlint.yml

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ included:
33
- Tests
44
- Example
55
- Package.swift
6+
67
excluded:
78
- .build
89
- .docarchives

Makefile

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ GENERIC_PLATFORM_VISIONOS = generic/platform=visionOS
1313
GENERIC_PLATFORM_MACOS = platform=macOS
1414
GENERIC_PLATFORM_MAC_CATALYST = platform=macOS,variant=Mac Catalyst
1515

16-
SIM_PLATFORM_IOS = platform=iOS Simulator,id=$(call udid_for,iOS 18.0,iPhone \d\+ Pro [^M])
17-
SIM_PLATFORM_TVOS = platform=tvOS Simulator,id=$(call udid_for,tvOS 18.0,TV)
18-
SIM_PLATFORM_VISIONOS = platform=visionOS Simulator,id=$(call udid_for,visionOS 2.0,Vision)
16+
SIM_PLATFORM_IOS = platform=iOS Simulator,id=$(call udid_for,iOS 18.2,iPhone \d\+ Pro [^M])
17+
SIM_PLATFORM_TVOS = platform=tvOS Simulator,id=$(call udid_for,tvOS 18.2,TV)
18+
SIM_PLATFORM_VISIONOS = platform=visionOS Simulator,id=$(call udid_for,visionOS 2.2,Vision)
1919
SIM_PLATFORM_MACOS = platform=macOS
2020
SIM_PLATFORM_MAC_CATALYST = platform=macOS,variant=Mac Catalyst
2121

Package.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version:5.9
1+
// swift-tools-version:5.8
22
import PackageDescription
33
import Foundation
44

@@ -52,5 +52,6 @@ let package = Package(
5252
.copy("Resources/Subs")
5353
]
5454
)
55-
]
55+
],
56+
swiftLanguageVersions: [.v5]
5657
)

[email protected]

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// swift-tools-version:6.0
2+
import PackageDescription
3+
import Foundation
4+
5+
let package = Package(
6+
name: "swift-ass-renderer",
7+
platforms: [
8+
.iOS(.v15),
9+
.tvOS(.v15),
10+
.visionOS(.v1),
11+
.macOS(.v12),
12+
.macCatalyst(.v15)
13+
],
14+
products: [
15+
.library(name: "SwiftAssRenderer", targets: ["SwiftAssRenderer"])
16+
],
17+
dependencies: [
18+
.package(url: "https://github.com/mihai8804858/swift-snapshot-testing", branch: "main"),
19+
.package(url: "https://github.com/mihai8804858/swift-libass", .upToNextMajor(from: "1.0.0")),
20+
.package(url: "https://github.com/pointfreeco/combine-schedulers", .upToNextMajor(from: "1.0.0"))
21+
],
22+
targets: [
23+
.target(
24+
name: "SwiftAssRenderer",
25+
dependencies: [
26+
.target(name: "SwiftAssBlend"),
27+
.product(name: "SwiftLibass", package: "swift-libass"),
28+
.product(name: "CombineSchedulers", package: "combine-schedulers")
29+
],
30+
path: "Sources/SwiftAssRenderer",
31+
resources: [
32+
.copy("Resources/PrivacyInfo.xcprivacy")
33+
],
34+
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
35+
),
36+
.target(
37+
name: "SwiftAssBlend",
38+
dependencies: [
39+
.product(name: "SwiftLibass", package: "swift-libass")
40+
],
41+
path: "Sources/SwiftAssBlend",
42+
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
43+
),
44+
.testTarget(
45+
name: "SwiftAssRendererTests",
46+
dependencies: [
47+
.target(name: "SwiftAssRenderer"),
48+
.product(name: "SnapshotTesting", package: "swift-snapshot-testing")
49+
],
50+
path: "Tests",
51+
exclude: ["Pipeline/__Snapshots__"],
52+
resources: [
53+
.copy("Resources/Fonts"),
54+
.copy("Resources/Subs")
55+
]
56+
)
57+
],
58+
swiftLanguageModes: [.v6]
59+
)

Sources/SwiftAssRenderer/Composition/AssSubtitlesRenderer+Composition.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import UIKit
33
#elseif canImport(AppKit)
44
import AppKit
55
#endif
6-
import AVFoundation
6+
@preconcurrency import AVFoundation
77

88
@available(iOS 16.0, tvOS 16.0, visionOS 1.0, macCatalyst 16.0, macOS 13.0, *)
99
public extension AssSubtitlesRenderer {

Sources/SwiftAssRenderer/Composition/AssVideoComposition.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import UIKit
44
import AppKit
55
#endif
66
@preconcurrency import AVFoundation
7-
import CoreImage
7+
@preconcurrency import CoreImage
88

99
@available(iOS 16.0, tvOS 16.0, visionOS 1.0, macCatalyst 16.0, macOS 13.0, *)
1010
final class AssVideoComposition: Sendable {

Sources/SwiftAssRenderer/Extensions/AVPlayer.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import AVFoundation
2-
import Combine
2+
@preconcurrency import Combine
33

44
extension AVPlayer {
55
func periodicTimeObserver(interval: CMTime, queue: DispatchQueue = .main) -> AnyPublisher<CMTime, Never> {

Sources/SwiftAssRenderer/Extensions/DispatchQueue.swift

+2-7
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,11 @@ import Dispatch
22
import Foundation
33

44
protocol DispatchQueueType: Sendable {
5-
func executeAsync(_ work: @escaping () -> Void)
5+
func executeAsync(_ work: @escaping @Sendable () -> Void)
66
}
77

88
extension DispatchQueue: DispatchQueueType {
9-
func executeAsync(_ work: @escaping () -> Void) {
9+
func executeAsync(_ work: @escaping @Sendable () -> Void) {
1010
async(execute: work)
1111
}
1212
}
13-
14-
func UI(_ perform: @escaping () -> Void) {
15-
if Thread.isMainThread { return perform() }
16-
DispatchQueue.main.async(execute: perform)
17-
}

Sources/SwiftAssRenderer/Extensions/FileManager.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ protocol FileManagerType: Sendable {
1010
func createItem(at path: URL, contents: String, override: Bool) throws
1111
}
1212

13-
extension FileManager: FileManagerType, @retroactive @unchecked Sendable {
13+
#if hasAttribute(retroactive)
14+
extension FileManager: @unchecked @retroactive Sendable {}
15+
#endif
16+
17+
extension FileManager: FileManagerType {
1418
var cachesDirectory: URL {
1519
if #available(iOS 16.0, tvOS 16.0, visionOS 1.0, macOS 13.0, *) {
1620
URL.cachesDirectory

Sources/SwiftAssRenderer/Overlay/AssSubtitlesView.swift

+35-35
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public final class AssSubtitlesView: PlatformView {
6262
}
6363
#endif
6464

65+
@MainActor
6566
private func resizeCanvas() {
6667
resizeImageAtLayout()
6768
renderer.setCanvasSize(bounds.size, scale: canvasScale)
@@ -75,6 +76,7 @@ public extension AssSubtitlesView {
7576
/// - callback: Callback to call.
7677
///
7778
/// Calling this multiple times will override previous callbacks.
79+
@MainActor
7880
@discardableResult
7981
func onImageChanged(_ callback: AssSubtitlesImageCallback?) -> Self {
8082
imageCallback = callback
@@ -83,11 +85,13 @@ public extension AssSubtitlesView {
8385
}
8486

8587
private extension AssSubtitlesView {
88+
@MainActor
8689
func configure() {
8790
setupView()
8891
subscribeToEvents()
8992
}
9093

94+
@MainActor
9195
func setupView() {
9296
addSubview(imageView)
9397
#if canImport(UIKit)
@@ -99,6 +103,7 @@ private extension AssSubtitlesView {
99103
#endif
100104
}
101105

106+
@MainActor
102107
func subscribeToEvents() {
103108
renderer
104109
.framesPublisher()
@@ -107,51 +112,46 @@ private extension AssSubtitlesView {
107112
.store(in: &cancellables)
108113
}
109114

115+
@MainActor
110116
func handleFrameChanged(_ image: ProcessedImage?) {
111-
UI { [weak self] in
112-
guard let self else { return }
113-
if let image {
114-
resizeImageView(for: image)
115-
#if canImport(UIKit)
116-
imageView.image = PlatformImage(cgImage: image.image)
117-
#elseif canImport(AppKit)
118-
imageView.image = PlatformImage(cgImage: image.image, size: image.imageRect.size)
119-
#endif
120-
imageView.isHidden = false
121-
} else {
122-
imageView.isHidden = true
123-
imageView.image = nil
124-
}
125-
if let imageCallback {
126-
let dialogues = renderer.dialogues(at: renderer.currentOffset)
127-
imageCallback(self, imageView, image, dialogues)
128-
}
117+
if let image {
118+
resizeImageView(for: image)
119+
#if canImport(UIKit)
120+
imageView.image = PlatformImage(cgImage: image.image)
121+
#elseif canImport(AppKit)
122+
imageView.image = PlatformImage(cgImage: image.image, size: image.imageRect.size)
123+
#endif
124+
imageView.isHidden = false
125+
} else {
126+
imageView.isHidden = true
127+
imageView.image = nil
128+
}
129+
if let imageCallback {
130+
let dialogues = renderer.dialogues(at: renderer.currentOffset)
131+
imageCallback(self, imageView, image, dialogues)
129132
}
130133
}
131134

135+
@MainActor
132136
func resizeImageView(for image: ProcessedImage) {
133-
UI { [weak self] in
134-
guard let self else { return }
135-
lastRenderBounds = bounds
136-
imageView.frame = imageFrame(for: image.imageRect)
137-
}
137+
lastRenderBounds = bounds
138+
imageView.frame = imageFrame(for: image.imageRect)
138139
}
139140

141+
@MainActor
140142
func resizeImageAtLayout() {
141-
UI { [weak self] in
142-
guard let self else { return }
143-
if lastRenderBounds.isEmpty || bounds.isEmpty || imageView.image == nil { return }
144-
let ratioX = 1 / (lastRenderBounds.width / bounds.width)
145-
let ratioY = 1 / (lastRenderBounds.height / bounds.height)
146-
let newOrigin = CGPoint(x: imageView.frame.origin.x * ratioX, y: imageView.frame.origin.y * ratioY)
147-
let newSize = CGSize(width: imageView.frame.width * ratioX, height: imageView.frame.height * ratioY)
148-
let newFrame = CGRect(origin: newOrigin, size: newSize).integral
149-
CATransaction.begin()
150-
imageView.frame = newFrame
151-
CATransaction.commit()
152-
}
143+
if lastRenderBounds.isEmpty || bounds.isEmpty || imageView.image == nil { return }
144+
let ratioX = 1 / (lastRenderBounds.width / bounds.width)
145+
let ratioY = 1 / (lastRenderBounds.height / bounds.height)
146+
let newOrigin = CGPoint(x: imageView.frame.origin.x * ratioX, y: imageView.frame.origin.y * ratioY)
147+
let newSize = CGSize(width: imageView.frame.width * ratioX, height: imageView.frame.height * ratioY)
148+
let newFrame = CGRect(origin: newOrigin, size: newSize).integral
149+
CATransaction.begin()
150+
imageView.frame = newFrame
151+
CATransaction.commit()
153152
}
154153

154+
@MainActor
155155
func imageFrame(for rect: CGRect) -> CGRect {
156156
#if canImport(UIKit)
157157
rect

Sources/SwiftAssRenderer/Renderer/AssSubtitlesRenderer.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ public final class AssSubtitlesRenderer: Sendable {
220220
/// - Parameters:
221221
/// - offset: Time interval (in seconds) where to load the subtitle frame.
222222
/// - completion: Completion handler.
223-
public func loadFrame(offset: TimeInterval, completion: @escaping (ProcessedImage?) -> Void = { _ in }) {
223+
public func loadFrame(offset: TimeInterval, completion: @escaping @Sendable (ProcessedImage?) -> Void = { _ in }) {
224224
workQueue.executeAsync { [weak self] in
225225
defer { completion(self?.currentFrame.value) }
226226
guard let self, var currentTrack else { return }

Sources/SwiftAssRenderer/Wrapper/LibraryWrapper.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ protocol LibraryWrapperType: Sendable {
4747
enum LibraryWrapper: LibraryWrapperType {
4848
private static let lock = NSLock()
4949

50-
static var libraryLogger: (Int, String) -> Void = { _, message in
50+
nonisolated(unsafe) static var libraryLogger: (Int, String) -> Void = { _, message in
5151
print("[swift-ass] \(message)")
5252
}
5353

Tests/Helpers/Snapshots.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func platformName() -> String {
1515
#endif
1616
}
1717

18-
func snapshotsDirectory(file: StaticString = #file, pathComponents: String...) -> URL {
18+
func snapshotsDirectory(file: StaticString = #filePath, pathComponents: String...) -> URL {
1919
let fileURL = URL(fileURLWithPath: "\(file)", isDirectory: false)
2020
let fileName = fileURL.deletingPathExtension().lastPathComponent
2121
let snapshotsDirectory = fileURL
@@ -33,6 +33,7 @@ func assertSnapshot<Value, Format>(
3333
snapshotDirectory: URL? = nil,
3434
record recording: Bool = false,
3535
file: StaticString = #file,
36+
filePath: StaticString = #filePath,
3637
testName: String = #function,
3738
line: UInt = #line
3839
) {
@@ -47,5 +48,5 @@ func assertSnapshot<Value, Format>(
4748
line: line
4849
)
4950
guard let message = failure else { return }
50-
XCTFail(message, file: file, line: line)
51+
XCTFail(message, file: filePath, line: line)
5152
}

Tests/Mocks/MockLibraryWrapper.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ import SwiftLibass
33
@testable import SwiftAssRenderer
44

55
final class MockLibraryWrapper: LibraryWrapperType {
6-
static var libraryLogger: (Int, String) -> Void = { _, _ in }
6+
nonisolated(unsafe) static var libraryLogger: (Int, String) -> Void = { _, _ in }
77

88
static let setLogCallbackFunc = FuncCheck<OpaquePointer>()
99
static func setLogCallback(_ library: OpaquePointer) {
1010
setLogCallbackFunc.call(library)
1111
}
1212

1313
static let libraryInitFunc = FuncCheck<Void>()
14-
static var libraryInitStub: OpaquePointer?
14+
nonisolated(unsafe) static var libraryInitStub: OpaquePointer?
1515
static func libraryInit() -> OpaquePointer? {
1616
libraryInitFunc.call()
1717
return libraryInitStub
@@ -23,7 +23,7 @@ final class MockLibraryWrapper: LibraryWrapperType {
2323
}
2424

2525
static let rendererInitFunc = FuncCheck<OpaquePointer>()
26-
static var rendererInitStub: OpaquePointer?
26+
nonisolated(unsafe) static var rendererInitStub: OpaquePointer?
2727
static func rendererInit(_ library: OpaquePointer) -> OpaquePointer? {
2828
rendererInitFunc.call(library)
2929
return rendererInitStub
@@ -56,14 +56,14 @@ final class MockLibraryWrapper: LibraryWrapperType {
5656
}
5757

5858
static let readTrackFunc = FuncCheck<(OpaquePointer, String)>()
59-
static var readTrackStub: ASS_Track?
59+
nonisolated(unsafe) static var readTrackStub: ASS_Track?
6060
static func readTrack(_ library: OpaquePointer, content: String) -> ASS_Track? {
6161
readTrackFunc.call((library, content))
6262
return readTrackStub
6363
}
6464

6565
static let renderImageFunc = FuncCheck<(OpaquePointer, ASS_Track, TimeInterval)>()
66-
static var renderImageStub: LibraryRenderResult?
66+
nonisolated(unsafe) static var renderImageStub: LibraryRenderResult?
6767
static func renderImage(
6868
_ renderer: OpaquePointer,
6969
track: inout ASS_Track,
Loading
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)