Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/InterposeKit.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1630"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "InterposeKit"
BuildableName = "InterposeKit"
BlueprintName = "InterposeKit"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "InterposeKitTests"
BuildableName = "InterposeKitTests"
BlueprintName = "InterposeKitTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "InterposeKit"
BuildableName = "InterposeKit"
BlueprintName = "InterposeKit"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
7 changes: 7 additions & 0 deletions InterposeKit.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.9
// swift-tools-version: 5.9

import PackageDescription

Expand Down
31 changes: 31 additions & 0 deletions Package@swift-6.0.swift
Original file line number Diff line number Diff line change
@@ -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]
)
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: AnyObject>: NSObject {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand All @@ -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<UInt64>(0)

}
14 changes: 11 additions & 3 deletions Sources/InterposeKit/Interpose.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,26 @@ 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 {
print("[InterposeKit] \(message())")
}
}

internal static func fail(
internal nonisolated static func fail(
_ message: @autoclosure () -> String
) -> Never {
fatalError("[InterposeKit] \(message())")
Expand Down
2 changes: 1 addition & 1 deletion Sources/InterposeKit/InterposeError.swift
Original file line number Diff line number Diff line change
@@ -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
Expand Down
41 changes: 41 additions & 0 deletions Sources/InterposeKit/Utilities/Concurrency/LockIsolated.swift
Original file line number Diff line number Diff line change
@@ -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<Value>: @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<T: Sendable>(
_ 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<R>(work: () throws -> R) rethrows -> R {
self.lock()
defer { self.unlock() }
return try work()
}
}
6 changes: 3 additions & 3 deletions Tests/InterposeKitTests/UtilitiesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down