Skip to content

Commit 4f22ba4

Browse files
committed
Support Swift 6 language mode
- Bump minimum iOS version to 14.0
1 parent 44a73af commit 4f22ba4

File tree

8 files changed

+155
-78
lines changed

8 files changed

+155
-78
lines changed

Gifu.xcodeproj/project.pbxproj

+9-5
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@
233233
attributes = {
234234
BuildIndependentTargetsInParallel = YES;
235235
LastSwiftUpdateCheck = 0920;
236-
LastUpgradeCheck = 1540;
236+
LastUpgradeCheck = 1620;
237237
ORGANIZATIONNAME = "Kaishin & Co";
238238
TargetAttributes = {
239239
009BD1351BBC7F6500FC982B = {
@@ -335,7 +335,7 @@
335335
DEVELOPMENT_TEAM = 5G38N4D8G2;
336336
GCC_NO_COMMON_BLOCKS = YES;
337337
INFOPLIST_FILE = Tests/Info.plist;
338-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
338+
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
339339
LD_RUNPATH_SEARCH_PATHS = (
340340
"$(inherited)",
341341
"@executable_path/Frameworks",
@@ -363,7 +363,7 @@
363363
DEVELOPMENT_TEAM = 5G38N4D8G2;
364364
GCC_NO_COMMON_BLOCKS = YES;
365365
INFOPLIST_FILE = Tests/Info.plist;
366-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
366+
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
367367
LD_RUNPATH_SEARCH_PATHS = (
368368
"$(inherited)",
369369
"@executable_path/Frameworks",
@@ -386,6 +386,7 @@
386386
isa = XCBuildConfiguration;
387387
buildSettings = {
388388
ALWAYS_SEARCH_USER_PATHS = NO;
389+
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
389390
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
390391
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
391392
CLANG_CXX_LIBRARY = "libc++";
@@ -447,6 +448,7 @@
447448
isa = XCBuildConfiguration;
448449
buildSettings = {
449450
ALWAYS_SEARCH_USER_PATHS = NO;
451+
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
450452
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
451453
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
452454
CLANG_CXX_LIBRARY = "libc++";
@@ -511,7 +513,7 @@
511513
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
512514
INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/Info.plist";
513515
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
514-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
516+
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
515517
LD_RUNPATH_SEARCH_PATHS = (
516518
"$(inherited)",
517519
"@executable_path/Frameworks",
@@ -527,6 +529,7 @@
527529
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
528530
SWIFT_VERSION = 5.0;
529531
TARGETED_DEVICE_FAMILY = "1,2,3";
532+
TVOS_DEPLOYMENT_TARGET = 15.6;
530533
};
531534
name = Debug;
532535
};
@@ -545,7 +548,7 @@
545548
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
546549
INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/Info.plist";
547550
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
548-
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
551+
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
549552
LD_RUNPATH_SEARCH_PATHS = (
550553
"$(inherited)",
551554
"@executable_path/Frameworks",
@@ -561,6 +564,7 @@
561564
SWIFT_OPTIMIZATION_LEVEL = "-O";
562565
SWIFT_VERSION = 5.0;
563566
TARGETED_DEVICE_FAMILY = "1,2,3";
567+
TVOS_DEPLOYMENT_TARGET = 15.6;
564568
};
565569
name = Release;
566570
};

Package.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import PackageDescription
55
let package = Package(
66
name: "Gifu",
77
platforms: [
8-
.iOS(.v12),
9-
.tvOS(.v12),
8+
.iOS(.v14),
9+
.tvOS(.v14),
1010
.visionOS(.v1),
1111
],
1212
products: [

[email protected]

+7
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,10 @@ let package = Package(
2424
],
2525
swiftLanguageModes: [.v6]
2626
)
27+
28+
for target in package.targets {
29+
target.swiftSettings = target.swiftSettings ?? []
30+
target.swiftSettings?.append(contentsOf: [
31+
.enableUpcomingFeature("ExistentialAny")
32+
])
33+
}

Sources/Gifu/Classes/Animator.swift

+40-17
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import UIKit
22

33
/// Responsible for parsing GIF data and decoding the individual frames.
4+
@MainActor
45
public class Animator {
56
/// Total duration of one animation loop
67
var loopDuration: TimeInterval {
@@ -20,7 +21,7 @@ public class Animator {
2021
private var displayLinkInitialized: Bool = false
2122

2223
/// A delegate responsible for displaying the GIF frames.
23-
private weak var delegate: (any GIFAnimatable)!
24+
private weak var delegate: (any GIFAnimatable)?
2425

2526
/// Callback for when all the loops of the animation are done (never called for infinite loops)
2627
private var animationBlock: (() -> Void)? = nil
@@ -31,6 +32,7 @@ public class Animator {
3132
/// Responsible for starting and stopping the animation.
3233
private lazy var displayLink: CADisplayLink = { [unowned self] in
3334
self.displayLinkInitialized = true
35+
3436
let display = CADisplayLink(
3537
target: DisplayLinkProxy(target: self),
3638
selector: #selector(DisplayLinkProxy.onScreenUpdate)
@@ -71,7 +73,7 @@ public class Animator {
7173

7274
store.shouldChangeFrame(with: displayLink.duration) {
7375
if $0 {
74-
delegate.animatorHasNewFrame()
76+
delegate?.animatorHasNewFrame()
7577
if store.isLoopFinished, let loopBlock {
7678
loopBlock()
7779
}
@@ -88,8 +90,12 @@ public class Animator {
8890
/// - parameter loopCount: Desired number of loops, <= 0 for infinite loop.
8991
/// - parameter completionHandler: Completion callback function
9092
func prepareForAnimation(
91-
withGIFNamed imageName: String, inBundle bundle: Bundle = .main, size: CGSize,
92-
contentMode: UIView.ContentMode, loopCount: Int = 0, completionHandler: (() -> Void)? = nil
93+
withGIFNamed imageName: String,
94+
inBundle bundle: Bundle = .main,
95+
size: CGSize,
96+
contentMode: UIView.ContentMode,
97+
loopCount: Int = 0,
98+
completionHandler: (@Sendable () -> Void)? = nil
9399
) {
94100
guard let extensionRemoved = imageName.components(separatedBy: ".")[safe: 0],
95101
let imagePath = bundle.url(forResource: extensionRemoved, withExtension: "gif"),
@@ -101,7 +107,8 @@ public class Animator {
101107
size: size,
102108
contentMode: contentMode,
103109
loopCount: loopCount,
104-
completionHandler: completionHandler)
110+
completionHandler: completionHandler
111+
)
105112
}
106113

107114
/// Prepares the animator instance for animation.
@@ -116,7 +123,7 @@ public class Animator {
116123
size: CGSize,
117124
contentMode: UIView.ContentMode,
118125
loopCount: Int = 0,
119-
completionHandler: (() -> Void)? = nil
126+
completionHandler: (@Sendable () -> Void)? = nil
120127
) {
121128
frameStore = FrameStore(
122129
data: imageData,
@@ -136,12 +143,18 @@ public class Animator {
136143
displayLink.add(to: .main, forMode: RunLoop.Mode.common)
137144
}
138145

139-
deinit {
140-
if displayLinkInitialized {
146+
private func invalidateDisplayLink() {
147+
Task { [displayLink] in
141148
displayLink.invalidate()
142149
}
143150
}
144151

152+
deinit {
153+
MainActor.assumeIsolated {
154+
invalidateDisplayLink()
155+
}
156+
}
157+
145158
/// Start animating.
146159
func startAnimating() {
147160
if frameStore?.isAnimatable ?? false {
@@ -164,9 +177,13 @@ public class Animator {
164177
/// - parameter animationBlock: Callback for when all the loops of the animation are done (never called for infinite loops)
165178
/// - parameter loopBlock: Callback for when a loop is done (at the end of each loop)
166179
func animate(
167-
withGIFNamed imageName: String, size: CGSize, contentMode: UIView.ContentMode,
168-
loopCount: Int = 0, preparationBlock: (() -> Void)? = nil, animationBlock: (() -> Void)? = nil,
169-
loopBlock: (() -> Void)? = nil
180+
withGIFNamed imageName: String,
181+
size: CGSize,
182+
contentMode: UIView.ContentMode,
183+
loopCount: Int = 0,
184+
preparationBlock: (@Sendable () -> Void)? = nil,
185+
animationBlock: (@Sendable () -> Void)? = nil,
186+
loopBlock: (@Sendable () -> Void)? = nil
170187
) {
171188
self.animationBlock = animationBlock
172189
self.loopBlock = loopBlock
@@ -175,7 +192,8 @@ public class Animator {
175192
size: size,
176193
contentMode: contentMode,
177194
loopCount: loopCount,
178-
completionHandler: preparationBlock)
195+
completionHandler: preparationBlock
196+
)
179197
startAnimating()
180198
}
181199

@@ -189,9 +207,13 @@ public class Animator {
189207
/// - parameter animationBlock: Callback for when all the loops of the animation are done (never called for infinite loops)
190208
/// - parameter loopBlock: Callback for when a loop is done (at the end of each loop)
191209
func animate(
192-
withGIFData imageData: Data, size: CGSize, contentMode: UIView.ContentMode, loopCount: Int = 0,
193-
preparationBlock: (() -> Void)? = nil, animationBlock: (() -> Void)? = nil,
194-
loopBlock: (() -> Void)? = nil
210+
withGIFData imageData: Data,
211+
size: CGSize,
212+
contentMode: UIView.ContentMode,
213+
loopCount: Int = 0,
214+
preparationBlock: (@Sendable () -> Void)? = nil,
215+
animationBlock: (@Sendable () -> Void)? = nil,
216+
loopBlock: (@Sendable () -> Void)? = nil
195217
) {
196218
self.animationBlock = animationBlock
197219
self.loopBlock = loopBlock
@@ -200,7 +222,8 @@ public class Animator {
200222
size: size,
201223
contentMode: contentMode,
202224
loopCount: loopCount,
203-
completionHandler: preparationBlock)
225+
completionHandler: preparationBlock
226+
)
204227
startAnimating()
205228
}
206229

@@ -232,5 +255,5 @@ private class DisplayLinkProxy {
232255
init(target: Animator) { self.target = target }
233256

234257
/// Lets the target update the frame if needed.
235-
@objc func onScreenUpdate() { target?.updateFrameIfNeeded() }
258+
@MainActor @objc func onScreenUpdate() { target?.updateFrameIfNeeded() }
236259
}

Sources/Gifu/Classes/FrameStore.swift

+4-5
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import ImageIO
22
import UIKit
33

44
/// Responsible for storing and updating the frames of a single GIF.
5-
class FrameStore {
5+
final class FrameStore: @unchecked Sendable {
66
/// The strategy to use for frame cache.
7-
enum FrameCachingStrategy: Equatable {
7+
enum FrameCachingStrategy: Equatable, Sendable {
88
// Cache only a given number of upcoming frames.
99
case cacheUpcoming(Int)
1010

@@ -152,7 +152,7 @@ class FrameStore {
152152

153153
// MARK: - Frames
154154
/// Loads the frames from an image source, resizes them, then caches them in `animatedFrames`.
155-
func prepareFrames(_ completionHandler: (() -> Void)? = nil) {
155+
func prepareFrames(_ completionHandler: (@Sendable () -> Void)? = nil) {
156156
frameCount = Int(CGImageSourceGetCount(imageSource))
157157
lock.lock()
158158
animatedFrames.reserveCapacity(frameCount)
@@ -228,8 +228,7 @@ extension FrameStore {
228228
/// Updates the frames by preloading new ones and replacing the previous frame with a placeholder.
229229
private func updateFrameCache() {
230230
if case let .cacheUpcoming(size) = cachingStrategy,
231-
size < frameCount - 1
232-
{
231+
size < frameCount - 1 {
233232
deleteCachedFrame(at: previousFrameIndex)
234233
}
235234

0 commit comments

Comments
 (0)