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 @@
+
+
+
+
+
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]
+)
diff --git a/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookRegistry.swift b/Sources/InterposeKit/Hooks/HookStrategy/ObjectHookStrategy/ObjectHookRegistry.swift
index 15385a1..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 81eef05..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 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/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/Interpose.swift b/Sources/InterposeKit/Interpose.swift
index fa6f298..a2f7c3d 100644
--- a/Sources/InterposeKit/Interpose.swift
+++ b/Sources/InterposeKit/Interpose.swift
@@ -85,10 +85,18 @@ public enum Interpose {
// MARK: Logging
// ============================================================================ //
- /// The flag indicating whether logging is enabled.
+ /// 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.
+ #if compiler(>=5.10)
+ public nonisolated(unsafe) static var isLoggingEnabled = false
+ #else
public static var isLoggingEnabled = false
+ #endif
- internal static func log(
+ internal nonisolated static func log(
_ message: @autoclosure () -> String
) {
if self.isLoggingEnabled {
@@ -96,7 +104,7 @@ public enum Interpose {
}
}
- internal static func fail(
+ internal nonisolated static func fail(
_ message: @autoclosure () -> String
) -> Never {
fatalError("[InterposeKit] \(message())")
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
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()
+ }
+}
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
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()