From 408b7e09eaaf243a6307cabbc1030f81475ec36b Mon Sep 17 00:00:00 2001 From: Lukas Kubanek Date: Fri, 4 Apr 2025 13:28:54 +0200 Subject: [PATCH 1/9] Added package manifest specific to Swift 6 --- Package.swift | 2 +- Package@swift-6.0.swift | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 Package@swift-6.0.swift diff --git a/Package.swift b/Package.swift index f0f4e94..fb2b112 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.9 +// swift-tools-version: 5.9 import PackageDescription diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift new file mode 100644 index 0000000..ba62c5c --- /dev/null +++ b/Package@swift-6.0.swift @@ -0,0 +1,31 @@ +// swift-tools-version: 6.0 + +import PackageDescription + +let package = Package( + name: "InterposeKit", + platforms: [ + .iOS(.v13), + .macOS(.v10_15), + .tvOS(.v13), + .watchOS(.v6) + ], + products: [ + .library( + name: "InterposeKit", + targets: ["InterposeKit"] + ), + ], + targets: [ + .target(name: "ITKSuperBuilder"), + .target( + name: "InterposeKit", + dependencies: ["ITKSuperBuilder"] + ), + .testTarget( + name: "InterposeKitTests", + dependencies: ["InterposeKit"] + ), + ], + swiftLanguageModes: [.v6] +) From 3eb60e427b9cbbc1fce7bebc929febbb3e2ee665 Mon Sep 17 00:00:00 2001 From: Lukas Kubanek Date: Fri, 4 Apr 2025 13:34:16 +0200 Subject: [PATCH 2/9] Added Xcode workspace --- .../xcschemes/InterposeKit.xcscheme | 79 +++++++++++++++++++ .../contents.xcworkspacedata | 7 ++ 2 files changed, 86 insertions(+) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/InterposeKit.xcscheme create mode 100644 InterposeKit.xcworkspace/contents.xcworkspacedata diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/InterposeKit.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/InterposeKit.xcscheme new file mode 100644 index 0000000..8c31f0b --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/InterposeKit.xcscheme @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/InterposeKit.xcworkspace/contents.xcworkspacedata b/InterposeKit.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..ca3329e --- /dev/null +++ b/InterposeKit.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + From c4be64904aa157dd9da3a1847b0a03c636b273cb Mon Sep 17 00:00:00 2001 From: Lukas Kubanek Date: Fri, 4 Apr 2025 14:26:42 +0200 Subject: [PATCH 3/9] Made logging compatible with Swift 6 --- Sources/InterposeKit/Interpose.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Sources/InterposeKit/Interpose.swift b/Sources/InterposeKit/Interpose.swift index fa6f298..7d069fa 100644 --- a/Sources/InterposeKit/Interpose.swift +++ b/Sources/InterposeKit/Interpose.swift @@ -85,10 +85,14 @@ public enum Interpose { // MARK: Logging // ============================================================================ // - /// The flag indicating whether logging is enabled. - public static var isLoggingEnabled = false + /// The flag that enables logging of InterposeKit internal operations to standard output + /// using the `print(…)` function. Defaults to `false`. + /// + /// It is recommended to set this flag only once early in your application lifecycle, + /// e.g. at app startup or in test setup. + public nonisolated(unsafe) static var isLoggingEnabled = false - internal static func log( + internal nonisolated static func log( _ message: @autoclosure () -> String ) { if self.isLoggingEnabled { @@ -96,7 +100,7 @@ public enum Interpose { } } - internal static func fail( + internal nonisolated static func fail( _ message: @autoclosure () -> String ) -> Never { fatalError("[InterposeKit] \(message())") From f61f377cb2c18722473599ca43dd3c057ec9fc85 Mon Sep 17 00:00:00 2001 From: Lukas Kubanek Date: Fri, 4 Apr 2025 14:37:47 +0200 Subject: [PATCH 4/9] Made associated object keys compatible with Swift 6 --- .../HookStrategy/ObjectHookStrategy/ObjectHookRegistry.swift | 2 +- .../HookStrategy/ObjectHookStrategy/ObjectHookStrategy.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookRegistry.swift b/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookRegistry.swift index 15385a1..f86ef15 100644 --- a/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookRegistry.swift +++ b/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookRegistry.swift @@ -34,7 +34,7 @@ internal enum ObjectHookRegistry { } -fileprivate var ObjectHookRegistryKey: UInt8 = 0 +fileprivate nonisolated(unsafe) var ObjectHookRegistryKey: UInt8 = 0 fileprivate class WeakReference: NSObject { diff --git a/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookStrategy.swift b/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookStrategy.swift index 81eef05..bbd8a28 100644 --- a/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookStrategy.swift +++ b/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookStrategy.swift @@ -294,4 +294,4 @@ extension NSObject { } -private var ObjectHookCountKey: UInt8 = 0 +private nonisolated(unsafe) var ObjectHookCountKey: UInt8 = 0 From 7a9a6a7dc4c0ea6bb896de6bce1b26c7e6500191 Mon Sep 17 00:00:00 2001 From: Lukas Kubanek Date: Fri, 4 Apr 2025 14:50:19 +0200 Subject: [PATCH 5/9] Introduced LockIsolated into the codebase --- .../ObjectSubclassManager.swift | 13 +++--- .../Utilities/Concurrency/LockIsolated.swift | 41 +++++++++++++++++++ 2 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 Sources/InterposeKit/Utilities/Concurrency/LockIsolated.swift diff --git a/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectSubclassManager.swift b/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectSubclassManager.swift index 52a4ed2..a776973 100644 --- a/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectSubclassManager.swift +++ b/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectSubclassManager.swift @@ -136,9 +136,9 @@ internal enum ObjectSubclassManager { ) -> String { let className = NSStringFromClass(perceivedClass) - let counterSuffix: String = self.subclassCounterQueue.sync { - self.subclassCounter &+= 1 - return String(format: "%04llx", self.subclassCounter) + let counterSuffix: String = self.subclassCounter.withValue { counter in + counter &+= 1 + return String(format: "%04llx", counter) } return "\(self.namePrefix)_\(className)_\(counterSuffix)" @@ -147,10 +147,7 @@ internal enum ObjectSubclassManager { /// The prefix used for all dynamically created subclass names. private static let namePrefix = "InterposeKit" - /// A global counter for generating unique subclass name suffixes. - private static var subclassCounter: UInt64 = 0 - - /// A serial queue to ensure thread-safe access to `subclassCounter`. - private static let subclassCounterQueue = DispatchQueue(label: "com.steipete.InterposeKit.subclassCounter") + /// A lock-isolated global counter for generating unique subclass name suffixes. + private static let subclassCounter = LockIsolated(0) } diff --git a/Sources/InterposeKit/Utilities/Concurrency/LockIsolated.swift b/Sources/InterposeKit/Utilities/Concurrency/LockIsolated.swift new file mode 100644 index 0000000..2eb5bb4 --- /dev/null +++ b/Sources/InterposeKit/Utilities/Concurrency/LockIsolated.swift @@ -0,0 +1,41 @@ +// A modified version from Point-Free’s Concurrency Extras package: +// https://github.com/pointfreeco/swift-concurrency-extras +// +// Copyright (c) 2023 Point-Free +// Licensed under the MIT license. + +import Foundation + +internal final class LockIsolated: @unchecked Sendable { + private var _value: Value + private let lock = NSRecursiveLock() + + internal init(_ value: @autoclosure @Sendable () throws -> Value) rethrows { + self._value = try value() + } + + internal var value: Value { + self.lock.sync { + self._value + } + } + + internal func withValue( + _ operation: @Sendable (inout Value) throws -> T + ) rethrows -> T { + try self.lock.sync { + var value = self._value + defer { self._value = value } + return try operation(&value) + } + } +} + +extension NSRecursiveLock { + @discardableResult + fileprivate func sync(work: () throws -> R) rethrows -> R { + self.lock() + defer { self.unlock() } + return try work() + } +} From 8fdad1c92b28d984c2ee83e546060ca20f0eef72 Mon Sep 17 00:00:00 2001 From: Lukas Kubanek Date: Fri, 4 Apr 2025 14:51:50 +0200 Subject: [PATCH 6/9] Made tests compatible with Swift 6 --- Tests/InterposeKitTests/UtilitiesTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/InterposeKitTests/UtilitiesTests.swift b/Tests/InterposeKitTests/UtilitiesTests.swift index 1fcaae1..8a38df0 100644 --- a/Tests/InterposeKitTests/UtilitiesTests.swift +++ b/Tests/InterposeKitTests/UtilitiesTests.swift @@ -20,12 +20,12 @@ extension NSObject { final class UtilitiesTests: XCTestCase { - static var hasRunTestSetPerceivedClass = false + static let hasRunTestSetPerceivedClass = LockIsolated(false) func test_setPerceivedClass() throws { // Runs only once to avoid leaking class swizzling across test runs. - try XCTSkipIf(Self.hasRunTestSetPerceivedClass, "Class override already applied.") - Self.hasRunTestSetPerceivedClass = true + try XCTSkipIf(Self.hasRunTestSetPerceivedClass.value, "Class override already applied.") + Self.hasRunTestSetPerceivedClass.withValue{ $0 = true } let object = RealClass() From dc245a9f91104a21fcbab3e0ef3879e1a4cb6b6a Mon Sep 17 00:00:00 2001 From: Lukas Kubanek Date: Fri, 4 Apr 2025 14:54:19 +0200 Subject: [PATCH 7/9] Silenced Swift Concurrency warning in InterposeError --- Sources/InterposeKit/InterposeError.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/InterposeKit/InterposeError.swift b/Sources/InterposeKit/InterposeError.swift index 2b55410..0211b67 100644 --- a/Sources/InterposeKit/InterposeError.swift +++ b/Sources/InterposeKit/InterposeError.swift @@ -1,6 +1,6 @@ import Foundation -public enum InterposeError: Error { +public enum InterposeError: Error, @unchecked Sendable { /// A hook operation failed and the hook is no longer usable. case hookInFailedState From dc0aa370ec0d325ab05e0195fcb7a2d14a90c75d Mon Sep 17 00:00:00 2001 From: Lukas Kubanek Date: Fri, 4 Apr 2025 15:05:13 +0200 Subject: [PATCH 8/9] Grouped Obj-C utilities --- .../{ => ObjectiveC}/class_implementsInstanceMethod.swift | 0 .../Utilities/{ => ObjectiveC}/class_setPerceivedClass.swift | 0 .../InterposeKit/Utilities/{ => ObjectiveC}/object_getClass.swift | 0 .../Utilities/{ => ObjectiveC}/object_isKVOActive.swift | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename Sources/InterposeKit/Utilities/{ => ObjectiveC}/class_implementsInstanceMethod.swift (100%) rename Sources/InterposeKit/Utilities/{ => ObjectiveC}/class_setPerceivedClass.swift (100%) rename Sources/InterposeKit/Utilities/{ => ObjectiveC}/object_getClass.swift (100%) rename Sources/InterposeKit/Utilities/{ => ObjectiveC}/object_isKVOActive.swift (100%) diff --git a/Sources/InterposeKit/Utilities/class_implementsInstanceMethod.swift b/Sources/InterposeKit/Utilities/ObjectiveC/class_implementsInstanceMethod.swift similarity index 100% rename from Sources/InterposeKit/Utilities/class_implementsInstanceMethod.swift rename to Sources/InterposeKit/Utilities/ObjectiveC/class_implementsInstanceMethod.swift diff --git a/Sources/InterposeKit/Utilities/class_setPerceivedClass.swift b/Sources/InterposeKit/Utilities/ObjectiveC/class_setPerceivedClass.swift similarity index 100% rename from Sources/InterposeKit/Utilities/class_setPerceivedClass.swift rename to Sources/InterposeKit/Utilities/ObjectiveC/class_setPerceivedClass.swift diff --git a/Sources/InterposeKit/Utilities/object_getClass.swift b/Sources/InterposeKit/Utilities/ObjectiveC/object_getClass.swift similarity index 100% rename from Sources/InterposeKit/Utilities/object_getClass.swift rename to Sources/InterposeKit/Utilities/ObjectiveC/object_getClass.swift diff --git a/Sources/InterposeKit/Utilities/object_isKVOActive.swift b/Sources/InterposeKit/Utilities/ObjectiveC/object_isKVOActive.swift similarity index 100% rename from Sources/InterposeKit/Utilities/object_isKVOActive.swift rename to Sources/InterposeKit/Utilities/ObjectiveC/object_isKVOActive.swift From 2a0bad8031b314d6ac60f9a7c45c40b9d0e84541 Mon Sep 17 00:00:00 2001 From: Lukas Kubanek Date: Fri, 4 Apr 2025 15:23:31 +0200 Subject: [PATCH 9/9] Attempt to fix issues in Swift 5.9 --- .../ObjectHookStrategy/ObjectHookRegistry.swift | 4 ++++ .../ObjectHookStrategy/ObjectHookStrategy.swift | 6 +++++- Sources/InterposeKit/Interpose.swift | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookRegistry.swift b/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookRegistry.swift index f86ef15..5a81e3f 100644 --- a/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookRegistry.swift +++ b/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookRegistry.swift @@ -34,7 +34,11 @@ internal enum ObjectHookRegistry { } +#if compiler(>=5.10) fileprivate nonisolated(unsafe) var ObjectHookRegistryKey: UInt8 = 0 +#else +fileprivate var ObjectHookRegistryKey: UInt8 = 0 +#endif fileprivate class WeakReference: NSObject { diff --git a/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookStrategy.swift b/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookStrategy.swift index bbd8a28..0c294fb 100644 --- a/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookStrategy.swift +++ b/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookStrategy.swift @@ -294,4 +294,8 @@ extension NSObject { } -private nonisolated(unsafe) var ObjectHookCountKey: UInt8 = 0 +#if compiler(>=5.10) +fileprivate nonisolated(unsafe) var ObjectHookCountKey: UInt8 = 0 +#else +fileprivate var ObjectHookCountKey: UInt8 = 0 +#endif diff --git a/Sources/InterposeKit/Interpose.swift b/Sources/InterposeKit/Interpose.swift index 7d069fa..a2f7c3d 100644 --- a/Sources/InterposeKit/Interpose.swift +++ b/Sources/InterposeKit/Interpose.swift @@ -90,7 +90,11 @@ public enum Interpose { /// /// It is recommended to set this flag only once early in your application lifecycle, /// e.g. at app startup or in test setup. + #if compiler(>=5.10) public nonisolated(unsafe) static var isLoggingEnabled = false + #else + public static var isLoggingEnabled = false + #endif internal nonisolated static func log( _ message: @autoclosure () -> String