Skip to content

Commit

Permalink
Prepare for Swift 6 Language Mode (#11)
Browse files Browse the repository at this point in the history
* Fix Swift 6 language mode issues.

* Enable strict concurrency checking, momentarily.

* Add a comment.

* Revert an unwanted change.
  • Loading branch information
jaredsinclair authored Jul 15, 2024
1 parent 3dc4efd commit cb91e24
Show file tree
Hide file tree
Showing 11 changed files with 68 additions and 33 deletions.
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.8
// swift-tools-version:5.10

import PackageDescription

Expand Down
2 changes: 1 addition & 1 deletion Sources/Etcetera/BackgroundTasks/BackgroundTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ private typealias Internals = UnsupportedBackgroundTask
#endif

/// A cross-platform wrapper for requesting background execution time.
public final class BackgroundTask: @unchecked Sendable {
public final class BackgroundTask: Sendable {

/// Convenience for initializing a task with a default expiration handler.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import UIKit

/// For environments that do not support background tasks.
final class UnsupportedBackgroundTask: @unchecked Sendable {
final class UnsupportedBackgroundTask: Sendable {

@MainActor static func start() -> UnsupportedBackgroundTask? {
return nil
Expand Down
9 changes: 7 additions & 2 deletions Sources/Etcetera/BackgroundTasks/iOSBackgroundTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import UIKit

/// A quality-of-life wrapper around requesting iOS background execution time.
class iOSBackgroundTask: @unchecked Sendable {
final class iOSBackgroundTask: Sendable {

/// Convenience for initializing a task with a default expiration handler.
///
Expand Down Expand Up @@ -50,7 +50,12 @@ class iOSBackgroundTask: @unchecked Sendable {

init() {}

private var taskId: UIBackgroundTaskIdentifier = .invalid
private var taskId: UIBackgroundTaskIdentifier {
get { _taskId.current }
set { _taskId.current = newValue }
}

private let _taskId = Protected(UIBackgroundTaskIdentifier.invalid)

}

Expand Down
13 changes: 11 additions & 2 deletions Sources/Etcetera/Foundation/Locking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import os.lock
/// A high-performance lock supported by all Apple platforms.
///
/// This lock is **not** recursive.
public final class Lock: Sendable {
public final class Lock: @unchecked Sendable {

/// See WWDC 2016 Session 720. Using C struct locks like `pthread_mutex_t`
/// or `os_unfair_lock` directly from Swift code is discouraged because of
Expand Down Expand Up @@ -64,9 +64,18 @@ public final class Protected<T>: @unchecked Sendable {
}
}

extension Protected {

/// Convenience initializer that defaults to `nil`.
public convenience init<O>() where T == Optional<O> {
self.init(nil)
}

}

/// A dictionary-like object that provides synchronized read/writes via an
/// underlying `Protected` value.
public final class ProtectedDictionary<Key: Hashable, Value> {
public final class ProtectedDictionary<Key: Hashable, Value>: Sendable {

private let protected: Protected<[Key: Value]>

Expand Down
28 changes: 24 additions & 4 deletions Sources/Etcetera/Foundation/NotificationObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import Foundation
/// and trust ARC to release the observer at the appropriate time, which will
/// remove all observations. This assumes, of course, that all blocks passed to
/// `when(_:perform:)` do not strongly capture `self`.
public class NotificationObserver: NSObject, @unchecked Sendable {
public final class NotificationObserver: NSObject, Sendable {

// MARK: - Typealiases

Expand All @@ -35,8 +35,18 @@ public class NotificationObserver: NSObject, @unchecked Sendable {
/// The target queue for all observation callbacks.
private let queue: OperationQueue

/// The (optional) target object to be used when observing notifications.
private weak var object: AnyObject?
/// The target object to be used when observing notifications.
private var object: AnyObject? {
get { _object.access {
$0.object
}}
set { _object.access {
$0.object = newValue
}}
}

/// The backing storage for `object`.
private let _object: Protected<WeakReferencingBox>

/// A tote bag of observation tokens.
private let tokens = Protected<[Token]>([])
Expand All @@ -55,7 +65,7 @@ public class NotificationObserver: NSObject, @unchecked Sendable {
///
/// - parameter queue: The target queue for all observation callbacks.
public init(object: AnyObject? = nil, queue: OperationQueue = .main) {
self.object = object
self._object = Protected(WeakReferencingBox(object: object))
self.wasInitializedWithTargetObject = (object != nil)
self.queue = queue
}
Expand Down Expand Up @@ -118,4 +128,14 @@ public class NotificationObserver: NSObject, @unchecked Sendable {
return when(name, perform: { _ in block() })
}

// MARK: - Nested Types

private final class WeakReferencingBox {
weak var object: AnyObject?

init(object: AnyObject? = nil) {
self.object = object
}
}

}
13 changes: 9 additions & 4 deletions Sources/Etcetera/Foundation/ProcessInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,16 @@ extension ProcessInfo {
/// or:
///
/// if ProcessInfo.Argument.resetCachesOnLaunch.isEnabled { ... }
public struct Argument: RawRepresentable, ExpressibleByStringLiteral {
public struct Argument: RawRepresentable, ExpressibleByStringLiteral, Sendable {

/// Supply your own "-com.domain.MyApp." prefix which must be present in
/// all the custom arguments defined in your target's scheme editor.
public static var commonPrefix: String {
public static nonisolated var commonPrefix: String {
get { _commonPrefix.current }
set { _commonPrefix.current = newValue }
}

/// Backing storage for `commonPrefix`.
private static let _commonPrefix = Protected<String>("")

/// Required by `RawRepresentable`.
Expand All @@ -62,23 +64,26 @@ extension ProcessInfo {
return ProcessInfo.isArgumentEnabled(self)
}

/// Required by `RawRepresentable`.
public init(rawValue: String) {
self.rawValue = rawValue
}

/// Required by `RawRepresentable`.
public init(stringLiteral value: String) {
self.rawValue = value
}

}

/// - returns: Returns `true` if `argument` is found among the arguments.
public static func isArgumentEnabled(_ argument: Argument) -> Bool {
public static nonisolated func isArgumentEnabled(_ argument: Argument) -> Bool {
let string = Argument.commonPrefix + argument.rawValue
return processInfo.arguments.contains(string)
}

/// Returns `true` if the app is running as a test runner for unit tests.
public static var isRunningInUnitTests: Bool {
public static nonisolated var isRunningInUnitTests: Bool {
processInfo.environment["XCTestConfigurationFilePath"] != nil
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/Etcetera/Global/Global.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
/// directly when declaring an `@Global`-wrapped property. Instead, Global
/// wrappers are regularly initialized via init methods declared in
/// extensions (see the README for details).
public init(initializer: @MainActor @escaping (Container) -> Wrapped) {
public init(initializer: @MainActor @escaping @Sendable (Container) -> Wrapped) {
resolver = {
Container.shared.resolveInstance(via: initializer)
}
Expand All @@ -62,7 +62,7 @@
/// directly when declaring an `@Global`-wrapped property. Instead, Global
/// wrappers are regularly initialized via init methods declared in
/// extensions (see the README for details).
public init<InstanceIdentifier: Hashable>(instanceIdentifier: InstanceIdentifier, initializer: @MainActor @escaping (Container) -> Wrapped) {
public init<InstanceIdentifier: Hashable & Sendable>(instanceIdentifier: InstanceIdentifier, initializer: @MainActor @escaping @Sendable (Container) -> Wrapped) {
resolver = {
let someKey = AnyInstanceIdentifier<Wrapped, InstanceIdentifier>(instanceIdentifier)
return Container.shared.resolveInstance(for: someKey, via: initializer)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Etcetera/Global/GloballyIdentifiable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
public protocol GloballyIdentifiable {

/// The type to be used for per-instance identifiers.
associatedtype InstanceIdentifier: Hashable
associatedtype InstanceIdentifier: Hashable & Sendable

/// Produces an instance of `Self` on demand. This method is called from the
/// shared dependency container when a cached instance of `Self` cannot be
Expand Down
24 changes: 10 additions & 14 deletions Sources/Etcetera/Networking/Reachability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension Notification.Name {
}

/// A class that reports whether or not the network is currently reachable.
public class Reachability: NSObject {
public final class Reachability: NSObject, Sendable {

/// A more accurate alternative to using a Bool
public enum Status {
Expand All @@ -40,33 +40,29 @@ public class Reachability: NSObject {
return .probablyNotButWhoKnows
}

private let reachability: SCNetworkReachability?
private var lock = os_unfair_lock()
private var _flags: SCNetworkReachabilityFlags?
private let reachability: Protected<SCNetworkReachability?>
private let _flags = Protected<SCNetworkReachabilityFlags?>()

private var flags: SCNetworkReachabilityFlags? {
get {
os_unfair_lock_lock(&lock)
let value = _flags
os_unfair_lock_unlock(&lock)
return value
_flags.current
}
set {
os_unfair_lock_lock(&lock)
_flags = newValue
os_unfair_lock_unlock(&lock)
_flags.current = newValue
NotificationCenter.default.post(name: .ReachabilityChanged, object: nil)
}
}

public init(host: String = "www.google.com") {
self.reachability = SCNetworkReachabilityCreateWithName(nil, host)
let optionalReachability = SCNetworkReachabilityCreateWithName(nil, host)
self.reachability = Protected(optionalReachability)
super.init()
guard let reachability = reachability else { return }
guard let reachability = optionalReachability else { return }

// Populate the current flags asap.
var flags = SCNetworkReachabilityFlags()
SCNetworkReachabilityGetFlags(reachability, &flags)
_flags = flags
self.flags = flags

// Then configure the callback.
let callback: SCNetworkReachabilityCallBack = { (_, flags, infoPtr) in
Expand Down
2 changes: 1 addition & 1 deletion Sources/Etcetera/UnifiedLogging/OSActivity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public struct Activity: @unchecked Sendable {
// MARK: - Nested Types

/// Swift-native type that corresponds to OS_ACTIVITY_FLAGs.
public struct Options: OptionSet {
public struct Options: OptionSet, Sendable {
public let rawValue: UInt32

public init(rawValue: UInt32) {
Expand Down

0 comments on commit cb91e24

Please sign in to comment.