Skip to content

Commit

Permalink
Support Swift 6 language mode
Browse files Browse the repository at this point in the history
- Bump minimum iOS version to 14.0
  • Loading branch information
kaishin committed Jan 18, 2025
1 parent 44a73af commit 4f22ba4
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 78 deletions.
14 changes: 9 additions & 5 deletions Gifu.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1540;
LastUpgradeCheck = 1620;
ORGANIZATIONNAME = "Kaishin & Co";
TargetAttributes = {
009BD1351BBC7F6500FC982B = {
Expand Down Expand Up @@ -335,7 +335,7 @@
DEVELOPMENT_TEAM = 5G38N4D8G2;
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = Tests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -363,7 +363,7 @@
DEVELOPMENT_TEAM = 5G38N4D8G2;
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = Tests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -386,6 +386,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
Expand Down Expand Up @@ -447,6 +448,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
Expand Down Expand Up @@ -511,7 +513,7 @@
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -527,6 +529,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,3";
TVOS_DEPLOYMENT_TARGET = 15.6;
};
name = Debug;
};
Expand All @@ -545,7 +548,7 @@
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -561,6 +564,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,3";
TVOS_DEPLOYMENT_TARGET = 15.6;
};
name = Release;
};
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import PackageDescription
let package = Package(
name: "Gifu",
platforms: [
.iOS(.v12),
.tvOS(.v12),
.iOS(.v14),
.tvOS(.v14),
.visionOS(.v1),
],
products: [
Expand Down
7 changes: 7 additions & 0 deletions [email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,10 @@ let package = Package(
],
swiftLanguageModes: [.v6]
)

for target in package.targets {
target.swiftSettings = target.swiftSettings ?? []
target.swiftSettings?.append(contentsOf: [
.enableUpcomingFeature("ExistentialAny")
])
}
57 changes: 40 additions & 17 deletions Sources/Gifu/Classes/Animator.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import UIKit

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

/// A delegate responsible for displaying the GIF frames.
private weak var delegate: (any GIFAnimatable)!
private weak var delegate: (any GIFAnimatable)?

/// Callback for when all the loops of the animation are done (never called for infinite loops)
private var animationBlock: (() -> Void)? = nil
Expand All @@ -31,6 +32,7 @@ public class Animator {
/// Responsible for starting and stopping the animation.
private lazy var displayLink: CADisplayLink = { [unowned self] in
self.displayLinkInitialized = true

let display = CADisplayLink(
target: DisplayLinkProxy(target: self),
selector: #selector(DisplayLinkProxy.onScreenUpdate)
Expand Down Expand Up @@ -71,7 +73,7 @@ public class Animator {

store.shouldChangeFrame(with: displayLink.duration) {
if $0 {
delegate.animatorHasNewFrame()
delegate?.animatorHasNewFrame()
if store.isLoopFinished, let loopBlock {
loopBlock()
}
Expand All @@ -88,8 +90,12 @@ public class Animator {
/// - parameter loopCount: Desired number of loops, <= 0 for infinite loop.
/// - parameter completionHandler: Completion callback function
func prepareForAnimation(
withGIFNamed imageName: String, inBundle bundle: Bundle = .main, size: CGSize,
contentMode: UIView.ContentMode, loopCount: Int = 0, completionHandler: (() -> Void)? = nil
withGIFNamed imageName: String,
inBundle bundle: Bundle = .main,
size: CGSize,
contentMode: UIView.ContentMode,
loopCount: Int = 0,
completionHandler: (@Sendable () -> Void)? = nil
) {
guard let extensionRemoved = imageName.components(separatedBy: ".")[safe: 0],
let imagePath = bundle.url(forResource: extensionRemoved, withExtension: "gif"),
Expand All @@ -101,7 +107,8 @@ public class Animator {
size: size,
contentMode: contentMode,
loopCount: loopCount,
completionHandler: completionHandler)
completionHandler: completionHandler
)
}

/// Prepares the animator instance for animation.
Expand All @@ -116,7 +123,7 @@ public class Animator {
size: CGSize,
contentMode: UIView.ContentMode,
loopCount: Int = 0,
completionHandler: (() -> Void)? = nil
completionHandler: (@Sendable () -> Void)? = nil
) {
frameStore = FrameStore(
data: imageData,
Expand All @@ -136,12 +143,18 @@ public class Animator {
displayLink.add(to: .main, forMode: RunLoop.Mode.common)
}

deinit {
if displayLinkInitialized {
private func invalidateDisplayLink() {
Task { [displayLink] in
displayLink.invalidate()
}
}

deinit {
MainActor.assumeIsolated {
invalidateDisplayLink()
}
}

/// Start animating.
func startAnimating() {
if frameStore?.isAnimatable ?? false {
Expand All @@ -164,9 +177,13 @@ public class Animator {
/// - parameter animationBlock: Callback for when all the loops of the animation are done (never called for infinite loops)
/// - parameter loopBlock: Callback for when a loop is done (at the end of each loop)
func animate(
withGIFNamed imageName: String, size: CGSize, contentMode: UIView.ContentMode,
loopCount: Int = 0, preparationBlock: (() -> Void)? = nil, animationBlock: (() -> Void)? = nil,
loopBlock: (() -> Void)? = nil
withGIFNamed imageName: String,
size: CGSize,
contentMode: UIView.ContentMode,
loopCount: Int = 0,
preparationBlock: (@Sendable () -> Void)? = nil,
animationBlock: (@Sendable () -> Void)? = nil,
loopBlock: (@Sendable () -> Void)? = nil
) {
self.animationBlock = animationBlock
self.loopBlock = loopBlock
Expand All @@ -175,7 +192,8 @@ public class Animator {
size: size,
contentMode: contentMode,
loopCount: loopCount,
completionHandler: preparationBlock)
completionHandler: preparationBlock
)
startAnimating()
}

Expand All @@ -189,9 +207,13 @@ public class Animator {
/// - parameter animationBlock: Callback for when all the loops of the animation are done (never called for infinite loops)
/// - parameter loopBlock: Callback for when a loop is done (at the end of each loop)
func animate(
withGIFData imageData: Data, size: CGSize, contentMode: UIView.ContentMode, loopCount: Int = 0,
preparationBlock: (() -> Void)? = nil, animationBlock: (() -> Void)? = nil,
loopBlock: (() -> Void)? = nil
withGIFData imageData: Data,
size: CGSize,
contentMode: UIView.ContentMode,
loopCount: Int = 0,
preparationBlock: (@Sendable () -> Void)? = nil,
animationBlock: (@Sendable () -> Void)? = nil,
loopBlock: (@Sendable () -> Void)? = nil
) {
self.animationBlock = animationBlock
self.loopBlock = loopBlock
Expand All @@ -200,7 +222,8 @@ public class Animator {
size: size,
contentMode: contentMode,
loopCount: loopCount,
completionHandler: preparationBlock)
completionHandler: preparationBlock
)
startAnimating()
}

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

/// Lets the target update the frame if needed.
@objc func onScreenUpdate() { target?.updateFrameIfNeeded() }
@MainActor @objc func onScreenUpdate() { target?.updateFrameIfNeeded() }
}
9 changes: 4 additions & 5 deletions Sources/Gifu/Classes/FrameStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import ImageIO
import UIKit

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

Expand Down Expand Up @@ -152,7 +152,7 @@ class FrameStore {

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

Expand Down
Loading

0 comments on commit 4f22ba4

Please sign in to comment.