Minor tweaks
This commit is contained in:
parent
bc1af5d872
commit
176aa63666
|
@ -1,5 +1,4 @@
|
||||||
only_rules:
|
only_rules:
|
||||||
- anyobject_protocol
|
|
||||||
- array_init
|
- array_init
|
||||||
- block_based_kvo
|
- block_based_kvo
|
||||||
- class_delegate_protocol
|
- class_delegate_protocol
|
||||||
|
@ -10,6 +9,7 @@ only_rules:
|
||||||
- collection_alignment
|
- collection_alignment
|
||||||
- colon
|
- colon
|
||||||
- comma
|
- comma
|
||||||
|
- comma_inheritance
|
||||||
- compiler_protocol_init
|
- compiler_protocol_init
|
||||||
- computed_accessors_order
|
- computed_accessors_order
|
||||||
- conditional_returns_on_newline
|
- conditional_returns_on_newline
|
||||||
|
@ -105,6 +105,7 @@ only_rules:
|
||||||
- required_enum_case
|
- required_enum_case
|
||||||
- return_arrow_whitespace
|
- return_arrow_whitespace
|
||||||
- return_value_from_void_function
|
- return_value_from_void_function
|
||||||
|
- self_binding
|
||||||
- self_in_property_initialization
|
- self_in_property_initialization
|
||||||
- shorthand_operator
|
- shorthand_operator
|
||||||
- sorted_first_last
|
- sorted_first_last
|
||||||
|
@ -136,9 +137,9 @@ only_rules:
|
||||||
- unused_setter_value
|
- unused_setter_value
|
||||||
- valid_ibinspectable
|
- valid_ibinspectable
|
||||||
- vertical_parameter_alignment
|
- vertical_parameter_alignment
|
||||||
- vertical_parameter_alignment_on_call
|
|
||||||
- vertical_whitespace_closing_braces
|
- vertical_whitespace_closing_braces
|
||||||
- vertical_whitespace_opening_braces
|
- vertical_whitespace_opening_braces
|
||||||
|
- void_function_in_ternary
|
||||||
- void_return
|
- void_return
|
||||||
- xct_specific_matcher
|
- xct_specific_matcher
|
||||||
- yoda_condition
|
- yoda_condition
|
||||||
|
@ -192,3 +193,6 @@ custom_rules:
|
||||||
final_class:
|
final_class:
|
||||||
regex: '^class [a-zA-Z\d]+[^{]+\{'
|
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`.'
|
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`.
|
// Since `nil` cannot be assigned to `Any`, we use `Void` instead of `nil`.
|
||||||
public var isNil: Bool { value is Void }
|
public var isNil: Bool { value is Void }
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ public enum Defaults {
|
||||||
|
|
||||||
super.init(name: key, suite: suite)
|
super.init(name: key, suite: suite)
|
||||||
|
|
||||||
if (defaultValue as? _DefaultsOptionalType)?.isNil == true {
|
if (defaultValue as? _DefaultsOptionalProtocol)?.isNil == true {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,10 @@ import Combine
|
||||||
|
|
||||||
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||||
extension Defaults {
|
extension Defaults {
|
||||||
|
@MainActor
|
||||||
final class Observable<Value: Serializable>: ObservableObject {
|
final class Observable<Value: Serializable>: ObservableObject {
|
||||||
private var cancellable: AnyCancellable?
|
private var cancellable: AnyCancellable?
|
||||||
|
private var task: Task<Void, Never>?
|
||||||
private let key: Defaults.Key<Value>
|
private let key: Defaults.Key<Value>
|
||||||
|
|
||||||
let objectWillChange = ObservableObjectPublisher()
|
let objectWillChange = ObservableObjectPublisher()
|
||||||
|
@ -21,17 +23,35 @@ extension Defaults {
|
||||||
init(_ key: Key<Value>) {
|
init(_ key: Key<Value>) {
|
||||||
self.key = key
|
self.key = key
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
self.objectWillChange.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
self.cancellable = Defaults.publisher(key, options: [.prior])
|
self.cancellable = Defaults.publisher(key, options: [.prior])
|
||||||
.sink { [weak self] change in
|
.sink { [weak self] change in
|
||||||
guard change.isPrior else {
|
guard change.isPrior else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.mainSafeAsync {
|
Task { @MainActor in
|
||||||
self?.objectWillChange.send()
|
self?.objectWillChange.send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
task?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Reset the key back to its default value.
|
Reset the key back to its default value.
|
||||||
|
@ -77,7 +97,7 @@ public struct Default<Value: Defaults.Serializable>: DynamicProperty {
|
||||||
*/
|
*/
|
||||||
public init(_ key: Defaults.Key<Value>) {
|
public init(_ key: Defaults.Key<Value>) {
|
||||||
self.key = key
|
self.key = key
|
||||||
self.observable = Defaults.Observable(key)
|
self.observable = .init(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var wrappedValue: Value {
|
public var wrappedValue: Value {
|
||||||
|
@ -178,7 +198,7 @@ extension Defaults {
|
||||||
|
|
||||||
public init(key: Defaults.Key<Bool>, @ViewBuilder label: @escaping () -> Label) {
|
public init(key: Defaults.Key<Bool>, @ViewBuilder label: @escaping () -> Label) {
|
||||||
self.label = label
|
self.label = label
|
||||||
self.observable = Defaults.Observable(key)
|
self.observable = .init(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
|
@ -194,7 +214,7 @@ extension Defaults {
|
||||||
extension Defaults.Toggle<Text> {
|
extension Defaults.Toggle<Text> {
|
||||||
public init(_ title: some StringProtocol, key: Defaults.Key<Bool>) {
|
public init(_ title: some StringProtocol, key: Defaults.Key<Bool>) {
|
||||||
self.label = { Text(title) }
|
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, *)
|
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||||
@propertyWrapper
|
@propertyWrapper
|
||||||
private struct ViewStorage<Value>: DynamicProperty {
|
private struct ViewStorage<Value>: DynamicProperty {
|
||||||
|
|
|
@ -10,7 +10,7 @@ extension UserDefaults {
|
||||||
}
|
}
|
||||||
|
|
||||||
func _set<Value: Defaults.Serializable>(_ key: String, to value: Value) {
|
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)
|
removeObject(forKey: key)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ extension Decodable {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
final class ObjectAssociation<T: Any> {
|
final class ObjectAssociation<T> {
|
||||||
subscript(index: AnyObject) -> T? {
|
subscript(index: AnyObject) -> T? {
|
||||||
get {
|
get {
|
||||||
objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T?
|
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.
|
- 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`.
|
This is useful as you cannot compare `_OptionalType` to `nil`.
|
||||||
*/
|
*/
|
||||||
var isNil: Bool { get }
|
var isNil: Bool { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Optional: _DefaultsOptionalType {
|
extension Optional: _DefaultsOptionalProtocol {
|
||||||
public var isNil: Bool { self == nil }
|
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 {
|
extension Sequence {
|
||||||
/**
|
/**
|
||||||
Returns an array containing the non-nil elements.
|
Returns an array containing the non-nil elements.
|
||||||
|
|
|
@ -2,7 +2,7 @@ import Foundation
|
||||||
import Defaults
|
import Defaults
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
private enum FixtureCodableEnum: String, Defaults.Serializable & Codable & Hashable {
|
private enum FixtureCodableEnum: String, Hashable, Codable, Defaults.Serializable {
|
||||||
case tenMinutes = "10 Minutes"
|
case tenMinutes = "10 Minutes"
|
||||||
case halfHour = "30 Minutes"
|
case halfHour = "30 Minutes"
|
||||||
case oneHour = "1 Hour"
|
case oneHour = "1 Hour"
|
||||||
|
|
|
@ -349,7 +349,12 @@ final class DefaultsNSSecureCodingTests: XCTestCase {
|
||||||
.collect(expectedArray.count)
|
.collect(expectedArray.count)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
print("Result array: \(result)")
|
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 {
|
inputArray.forEach {
|
||||||
|
@ -378,7 +383,12 @@ final class DefaultsNSSecureCodingTests: XCTestCase {
|
||||||
.collect(expectedArray.count)
|
.collect(expectedArray.count)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
print("Result array: \(result)")
|
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 {
|
inputArray.forEach {
|
||||||
|
|
|
@ -499,7 +499,12 @@ final class DefaultsTests: XCTestCase {
|
||||||
.collect(expectedArray.count)
|
.collect(expectedArray.count)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
print("Result array: \(result)")
|
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 {
|
inputArray.forEach {
|
||||||
|
@ -527,7 +532,12 @@ final class DefaultsTests: XCTestCase {
|
||||||
.collect(expectedArray.count)
|
.collect(expectedArray.count)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
print("Result array: \(result)")
|
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 {
|
inputArray.forEach {
|
||||||
|
|
Loading…
Reference in New Issue