Minor tweaks
This commit is contained in:
parent
bc1af5d872
commit
176aa63666
|
@ -1,5 +1,4 @@
|
|||
only_rules:
|
||||
- anyobject_protocol
|
||||
- array_init
|
||||
- block_based_kvo
|
||||
- class_delegate_protocol
|
||||
|
@ -10,6 +9,7 @@ only_rules:
|
|||
- collection_alignment
|
||||
- colon
|
||||
- comma
|
||||
- comma_inheritance
|
||||
- compiler_protocol_init
|
||||
- computed_accessors_order
|
||||
- conditional_returns_on_newline
|
||||
|
@ -105,6 +105,7 @@ only_rules:
|
|||
- required_enum_case
|
||||
- return_arrow_whitespace
|
||||
- return_value_from_void_function
|
||||
- self_binding
|
||||
- self_in_property_initialization
|
||||
- shorthand_operator
|
||||
- sorted_first_last
|
||||
|
@ -136,9 +137,9 @@ only_rules:
|
|||
- unused_setter_value
|
||||
- valid_ibinspectable
|
||||
- vertical_parameter_alignment
|
||||
- vertical_parameter_alignment_on_call
|
||||
- vertical_whitespace_closing_braces
|
||||
- vertical_whitespace_opening_braces
|
||||
- void_function_in_ternary
|
||||
- void_return
|
||||
- xct_specific_matcher
|
||||
- yoda_condition
|
||||
|
@ -192,3 +193,6 @@ custom_rules:
|
|||
final_class:
|
||||
regex: '^class [a-zA-Z\d]+[^{]+\{'
|
||||
message: 'Classes should be marked as final whenever possible. If you actually need it to be subclassable, just add `// swiftlint:disable:next final_class`.'
|
||||
no_alignment_center:
|
||||
regex: '\b\(alignment: .center\b'
|
||||
message: 'This alignment is the default.'
|
||||
|
|
|
@ -191,7 +191,7 @@ extension Defaults.AnySerializable: ExpressibleByDictionaryLiteral {
|
|||
}
|
||||
}
|
||||
|
||||
extension Defaults.AnySerializable: _DefaultsOptionalType {
|
||||
extension Defaults.AnySerializable: _DefaultsOptionalProtocol {
|
||||
// Since `nil` cannot be assigned to `Any`, we use `Void` instead of `nil`.
|
||||
public var isNil: Bool { value is Void }
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ public enum Defaults {
|
|||
|
||||
super.init(name: key, suite: suite)
|
||||
|
||||
if (defaultValue as? _DefaultsOptionalType)?.isNil == true {
|
||||
if (defaultValue as? _DefaultsOptionalProtocol)?.isNil == true {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,10 @@ import Combine
|
|||
|
||||
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||
extension Defaults {
|
||||
@MainActor
|
||||
final class Observable<Value: Serializable>: ObservableObject {
|
||||
private var cancellable: AnyCancellable?
|
||||
private var task: Task<Void, Never>?
|
||||
private let key: Defaults.Key<Value>
|
||||
|
||||
let objectWillChange = ObservableObjectPublisher()
|
||||
|
@ -21,16 +23,34 @@ extension Defaults {
|
|||
init(_ key: Key<Value>) {
|
||||
self.key = key
|
||||
|
||||
self.cancellable = Defaults.publisher(key, options: [.prior])
|
||||
.sink { [weak self] change in
|
||||
guard change.isPrior else {
|
||||
return
|
||||
}
|
||||
// We only use this on the latest OSes (as of adding this) since the backdeploy library has a lot of bugs.
|
||||
if #available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) {
|
||||
// The `@MainActor` is important as the `.send()` method doesn't inherit the `@MainActor` from the class.
|
||||
self.task = .detached(priority: .userInitiated) { @MainActor [weak self] in
|
||||
for await _ in Defaults.events(key) {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.mainSafeAsync {
|
||||
self?.objectWillChange.send()
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.cancellable = Defaults.publisher(key, options: [.prior])
|
||||
.sink { [weak self] change in
|
||||
guard change.isPrior else {
|
||||
return
|
||||
}
|
||||
|
||||
Task { @MainActor in
|
||||
self?.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
task?.cancel()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,7 +97,7 @@ public struct Default<Value: Defaults.Serializable>: DynamicProperty {
|
|||
*/
|
||||
public init(_ key: Defaults.Key<Value>) {
|
||||
self.key = key
|
||||
self.observable = Defaults.Observable(key)
|
||||
self.observable = .init(key)
|
||||
}
|
||||
|
||||
public var wrappedValue: Value {
|
||||
|
@ -178,7 +198,7 @@ extension Defaults {
|
|||
|
||||
public init(key: Defaults.Key<Bool>, @ViewBuilder label: @escaping () -> Label) {
|
||||
self.label = label
|
||||
self.observable = Defaults.Observable(key)
|
||||
self.observable = .init(key)
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
|
@ -194,7 +214,7 @@ extension Defaults {
|
|||
extension Defaults.Toggle<Text> {
|
||||
public init(_ title: some StringProtocol, key: Defaults.Key<Bool>) {
|
||||
self.label = { Text(title) }
|
||||
self.observable = Defaults.Observable(key)
|
||||
self.observable = .init(key)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,6 +229,29 @@ extension Defaults.Toggle {
|
|||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||
extension Defaults {
|
||||
// TODO: Expose this publicly at some point.
|
||||
private static func events<Value: Serializable>(
|
||||
_ key: Defaults.Key<Value>,
|
||||
initial: Bool = true
|
||||
) -> AsyncStream<Value> { // TODO: Make this `some AsyncSequence<Value>` when Swift 6 is out.
|
||||
.init { continuation in
|
||||
let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in
|
||||
// TODO: Use the `.deserialize` method directly.
|
||||
let value = KeyChange(change: change, defaultValue: key.defaultValue).newValue
|
||||
continuation.yield(value)
|
||||
}
|
||||
|
||||
observation.start(options: initial ? [.initial] : [])
|
||||
|
||||
continuation.onTermination = { _ in
|
||||
observation.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||
@propertyWrapper
|
||||
private struct ViewStorage<Value>: DynamicProperty {
|
||||
|
|
|
@ -10,7 +10,7 @@ extension UserDefaults {
|
|||
}
|
||||
|
||||
func _set<Value: Defaults.Serializable>(_ key: String, to value: Value) {
|
||||
if (value as? _DefaultsOptionalType)?.isNil == true {
|
||||
if (value as? _DefaultsOptionalProtocol)?.isNil == true {
|
||||
removeObject(forKey: key)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ extension Decodable {
|
|||
}
|
||||
|
||||
|
||||
final class ObjectAssociation<T: Any> {
|
||||
final class ObjectAssociation<T> {
|
||||
subscript(index: AnyObject) -> T? {
|
||||
get {
|
||||
objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T?
|
||||
|
@ -124,32 +124,18 @@ A protocol for making generic type constraints of optionals.
|
|||
|
||||
- Note: It's intentionally not including `associatedtype Wrapped` as that limits a lot of the use-cases.
|
||||
*/
|
||||
public protocol _DefaultsOptionalType: ExpressibleByNilLiteral {
|
||||
public protocol _DefaultsOptionalProtocol: ExpressibleByNilLiteral {
|
||||
/**
|
||||
This is useful as you cannot compare `_OptionalType` to `nil`.
|
||||
*/
|
||||
var isNil: Bool { get }
|
||||
}
|
||||
|
||||
extension Optional: _DefaultsOptionalType {
|
||||
extension Optional: _DefaultsOptionalProtocol {
|
||||
public var isNil: Bool { self == nil }
|
||||
}
|
||||
|
||||
|
||||
extension DispatchQueue {
|
||||
/**
|
||||
Performs the `execute` closure immediately if we're on the main thread or asynchronously puts it on the main thread otherwise.
|
||||
*/
|
||||
static func mainSafeAsync(execute work: @escaping () -> Void) {
|
||||
if Thread.isMainThread {
|
||||
work()
|
||||
} else {
|
||||
main.async(execute: work)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Sequence {
|
||||
/**
|
||||
Returns an array containing the non-nil elements.
|
||||
|
|
|
@ -2,7 +2,7 @@ import Foundation
|
|||
import Defaults
|
||||
import XCTest
|
||||
|
||||
private enum FixtureCodableEnum: String, Defaults.Serializable & Codable & Hashable {
|
||||
private enum FixtureCodableEnum: String, Hashable, Codable, Defaults.Serializable {
|
||||
case tenMinutes = "10 Minutes"
|
||||
case halfHour = "30 Minutes"
|
||||
case oneHour = "1 Hour"
|
||||
|
|
|
@ -349,7 +349,12 @@ final class DefaultsNSSecureCodingTests: XCTestCase {
|
|||
.collect(expectedArray.count)
|
||||
.sink { result in
|
||||
print("Result array: \(result)")
|
||||
result == expectedArray ? expect.fulfill() : XCTFail("Expected Array is not matched")
|
||||
|
||||
if result == expectedArray {
|
||||
expect.fulfill()
|
||||
} else {
|
||||
XCTFail("Expected Array is not matched")
|
||||
}
|
||||
}
|
||||
|
||||
inputArray.forEach {
|
||||
|
@ -378,7 +383,12 @@ final class DefaultsNSSecureCodingTests: XCTestCase {
|
|||
.collect(expectedArray.count)
|
||||
.sink { result in
|
||||
print("Result array: \(result)")
|
||||
result == expectedArray ? expect.fulfill() : XCTFail("Expected Array is not matched")
|
||||
|
||||
if result == expectedArray {
|
||||
expect.fulfill()
|
||||
} else {
|
||||
XCTFail("Expected Array is not matched")
|
||||
}
|
||||
}
|
||||
|
||||
inputArray.forEach {
|
||||
|
|
|
@ -499,7 +499,12 @@ final class DefaultsTests: XCTestCase {
|
|||
.collect(expectedArray.count)
|
||||
.sink { result in
|
||||
print("Result array: \(result)")
|
||||
result == expectedArray ? expect.fulfill() : XCTFail("Expected Array is not matched")
|
||||
|
||||
if result == expectedArray {
|
||||
expect.fulfill()
|
||||
} else {
|
||||
XCTFail("Expected Array is not matched")
|
||||
}
|
||||
}
|
||||
|
||||
inputArray.forEach {
|
||||
|
@ -527,7 +532,12 @@ final class DefaultsTests: XCTestCase {
|
|||
.collect(expectedArray.count)
|
||||
.sink { result in
|
||||
print("Result array: \(result)")
|
||||
result == expectedArray ? expect.fulfill() : XCTFail("Expected Array is not matched")
|
||||
|
||||
if result == expectedArray {
|
||||
expect.fulfill()
|
||||
} else {
|
||||
XCTFail("Expected Array is not matched")
|
||||
}
|
||||
}
|
||||
|
||||
inputArray.forEach {
|
||||
|
|
Loading…
Reference in New Issue