add
This commit is contained in:
@@ -1,19 +0,0 @@
|
||||
/// A case-preserving, case-insensitive identifier
|
||||
/// that matches the ASCII version of sqlite3_stricmp
|
||||
struct CaseInsensitiveIdentifier: Hashable {
|
||||
private let lowercased: String
|
||||
let rawValue: String
|
||||
|
||||
init(rawValue: String) {
|
||||
self.lowercased = rawValue.lowercased()
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
static func == (lhs: CaseInsensitiveIdentifier, rhs: CaseInsensitiveIdentifier) -> Bool {
|
||||
lhs.lowercased == rhs.lowercased
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(lowercased)
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
// Copyright (C) 2015-2023 Gwendal Roué
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// =============================================================================
|
||||
//
|
||||
// Copyright (c) 2005-2019 David Heinemeier Hansson
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
extension Inflections {
|
||||
/// The default inflections
|
||||
public static var `default`: Inflections = {
|
||||
// Defines the standard inflection rules. These are the starting point
|
||||
// for new projects and are not considered complete. The current set of
|
||||
// inflection rules is frozen. This means, we do not change them to
|
||||
// become more complete. This is a safety measure to keep existing
|
||||
// applications from breaking.
|
||||
//
|
||||
// https://github.com/rails/rails/blob/b2eb1d1c55a59fee1e6c4cba7030d8ceb524267c/activesupport/lib/active_support/inflections.rb
|
||||
var inflections = Inflections()
|
||||
|
||||
inflections.plural("$", "s")
|
||||
inflections.plural("s$", "s")
|
||||
inflections.plural("^(ax|test)is$", "$1es")
|
||||
inflections.plural("(octop|vir)us$", "$1i")
|
||||
inflections.plural("(octop|vir)i$", "$1i")
|
||||
inflections.plural("(alias|status)$", "$1es")
|
||||
inflections.plural("(bu)s$", "$1ses")
|
||||
inflections.plural("(buffal|tomat|her)o$", "$1oes")
|
||||
inflections.plural("([ti])um$", "$1a")
|
||||
inflections.plural("([ti])a$", "$1a")
|
||||
inflections.plural("sis$", "ses")
|
||||
inflections.plural("(?:([^f])fe|([lr])f)$", "$1$2ves")
|
||||
inflections.plural("(hive)$", "$1s")
|
||||
inflections.plural("([^aeiouy]|qu)y$", "$1ies")
|
||||
inflections.plural("(x|ch|ss|sh)$", "$1es")
|
||||
inflections.plural("(matr|vert|ind)(?:ix|ex)$", "$1ices")
|
||||
inflections.plural("^(m|l)ouse$", "$1ice")
|
||||
inflections.plural("^(m|l)ice$", "$1ice")
|
||||
inflections.plural("^(ox)$", "$1en")
|
||||
inflections.plural("^(oxen)$", "$1")
|
||||
inflections.plural("(quiz)$", "$1zes")
|
||||
inflections.plural("(canva)s$", "$1ses")
|
||||
|
||||
inflections.singular("s$", "")
|
||||
inflections.singular("(ss)$", "$1")
|
||||
inflections.singular("(n)ews$", "$1ews")
|
||||
inflections.singular("([ti])a$", "$1um")
|
||||
inflections.singular("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$", "$1sis")
|
||||
inflections.singular("(^analy)(sis|ses)$", "$1sis")
|
||||
inflections.singular("([^f])ves$", "$1fe")
|
||||
inflections.singular("(hive)s$", "$1")
|
||||
inflections.singular("(tive)s$", "$1")
|
||||
inflections.singular("([lr])ves$", "$1f")
|
||||
inflections.singular("([^aeiouy]|qu)ies$", "$1y")
|
||||
inflections.singular("(s)eries$", "$1eries")
|
||||
inflections.singular("(m)ovies$", "$1ovie")
|
||||
inflections.singular("(x|ch|ss|sh)es$", "$1")
|
||||
inflections.singular("^(m|l)ice$", "$1ouse")
|
||||
inflections.singular("(bus)(es)?$", "$1")
|
||||
inflections.singular("(o)es$", "$1")
|
||||
inflections.singular("(shoe)s$", "$1")
|
||||
inflections.singular("(cris|test)(is|es)$", "$1is")
|
||||
inflections.singular("^(a)x[ie]s$", "$1xis")
|
||||
inflections.singular("(octop|vir)(us|i)$", "$1us")
|
||||
inflections.singular("(alias|status)(es)?$", "$1")
|
||||
inflections.singular("^(ox)en$", "$1")
|
||||
inflections.singular("(vert|ind)ices$", "$1ex")
|
||||
inflections.singular("(matr)ices$", "$1ix")
|
||||
inflections.singular("(quiz)zes$", "$1")
|
||||
inflections.singular("(database)s$", "$1")
|
||||
inflections.singular("(canvas)(es)?$", "$1")
|
||||
|
||||
inflections.uncountableWords([
|
||||
"advice",
|
||||
"corps",
|
||||
"dice",
|
||||
"equipment",
|
||||
"fish",
|
||||
"information",
|
||||
"jeans",
|
||||
"kudos",
|
||||
"money",
|
||||
"offspring",
|
||||
"police",
|
||||
"rice",
|
||||
"sheep",
|
||||
"species",
|
||||
])
|
||||
|
||||
inflections.irregularSuffix("child", "children")
|
||||
inflections.irregularSuffix("foot", "feet")
|
||||
inflections.irregularSuffix("leaf", "leaves")
|
||||
inflections.irregularSuffix("man", "men")
|
||||
inflections.irregularSuffix("move", "moves")
|
||||
inflections.irregularSuffix("person", "people")
|
||||
inflections.irregularSuffix("sex", "sexes")
|
||||
inflections.irregularSuffix("specimen", "specimens")
|
||||
inflections.irregularSuffix("zombie", "zombies")
|
||||
|
||||
return inflections
|
||||
}()
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
/// "player" -> "Player"
|
||||
var uppercasingFirstCharacter: String {
|
||||
guard let first else {
|
||||
return self
|
||||
}
|
||||
return String(first).uppercased() + dropFirst()
|
||||
}
|
||||
|
||||
/// "player" -> "players"
|
||||
/// "players" -> "players"
|
||||
var pluralized: String {
|
||||
Inflections.default.pluralize(self)
|
||||
}
|
||||
|
||||
/// "player" -> "player"
|
||||
/// "players" -> "player"
|
||||
var singularized: String {
|
||||
Inflections.default.singularize(self)
|
||||
}
|
||||
|
||||
/// "bar" -> "bar"
|
||||
/// "foo12" -> "foo"
|
||||
var digitlessRadical: String {
|
||||
String(prefix(upTo: Inflections.endIndexOfDigitlessRadical(self)))
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that controls GRDB string inflections.
|
||||
///
|
||||
/// - note: [**🔥 EXPERIMENTAL**](https://github.com/groue/GRDB.swift/blob/master/README.md#what-are-experimental-features)
|
||||
public struct Inflections: Sendable {
|
||||
private var pluralizeRules: [(NSRegularExpression, String)] = []
|
||||
private var singularizeRules: [(NSRegularExpression, String)] = []
|
||||
private var uncountablesRegularExpressions: [String: NSRegularExpression] = [:]
|
||||
|
||||
// For testability
|
||||
var uncountables: Set<String> {
|
||||
Set(uncountablesRegularExpressions.keys)
|
||||
}
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
// MARK: - Configuration
|
||||
|
||||
/// Appends a pluralization rule.
|
||||
///
|
||||
/// var inflections = Inflections()
|
||||
/// inflections.plural("$", "s")
|
||||
/// inflections.pluralize("player") // "players"
|
||||
///
|
||||
/// - parameters:
|
||||
/// - pattern: A regular expression pattern.
|
||||
/// - options: Regular expression options (defaults to
|
||||
/// `[.caseInsensitive]`).
|
||||
/// - template: A replacement template string.
|
||||
public mutating func plural(
|
||||
_ pattern: String,
|
||||
options: NSRegularExpression.Options = [.caseInsensitive],
|
||||
_ template: String)
|
||||
{
|
||||
let reg = try! NSRegularExpression(pattern: pattern, options: options)
|
||||
pluralizeRules.append((reg, template))
|
||||
}
|
||||
|
||||
/// Appends a singularization rule.
|
||||
///
|
||||
/// var inflections = Inflections()
|
||||
/// inflections.singular("s$", "")
|
||||
/// inflections.singularize("players") // "player"
|
||||
///
|
||||
/// - parameters:
|
||||
/// - pattern: A regular expression pattern.
|
||||
/// - options: Regular expression options (defaults to
|
||||
/// `[.caseInsensitive]`).
|
||||
/// - template: A replacement template string.
|
||||
public mutating func singular(
|
||||
_ pattern: String,
|
||||
options: NSRegularExpression.Options = [.caseInsensitive],
|
||||
_ template: String)
|
||||
{
|
||||
let reg = try! NSRegularExpression(pattern: pattern, options: options)
|
||||
singularizeRules.append((reg, template))
|
||||
}
|
||||
|
||||
/// Appends uncountable words.
|
||||
///
|
||||
/// var inflections = Inflections()
|
||||
/// inflections.plural("$", "s")
|
||||
/// inflections.uncountableWords(["foo"])
|
||||
/// inflections.pluralize("foo") // "foo"
|
||||
/// inflections.pluralize("bar") // "bars"
|
||||
public mutating func uncountableWords(_ words: [String]) {
|
||||
for word in words {
|
||||
uncountableWord(word)
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends an irregular singular/plural pair.
|
||||
///
|
||||
/// var inflections = Inflections()
|
||||
/// inflections.plural("$", "s")
|
||||
/// inflections.irregularSuffix("man", "men")
|
||||
/// inflections.pluralize("man") // "men"
|
||||
/// inflections.singularizes("women") // "woman"
|
||||
///
|
||||
/// - parameters:
|
||||
/// - singular: The singular form.
|
||||
/// - plural: The plural form.
|
||||
public mutating func irregularSuffix(_ singular: String, _ plural: String) {
|
||||
let s0 = singular.first!
|
||||
let srest = singular.dropFirst()
|
||||
|
||||
let p0 = plural.first!
|
||||
let prest = plural.dropFirst()
|
||||
|
||||
if s0.uppercased() == p0.uppercased() {
|
||||
self.plural("(\(s0))\(srest)$", options: [.caseInsensitive], "$1\(prest)")
|
||||
self.plural("(\(p0))\(prest)$", options: [.caseInsensitive], "$1\(prest)")
|
||||
|
||||
self.singular("(\(s0))\(srest)$", options: [.caseInsensitive], "$1\(srest)")
|
||||
self.singular("(\(p0))\(prest)$", options: [.caseInsensitive], "$1\(srest)")
|
||||
} else {
|
||||
self.plural("\(s0.uppercased())(?i)\(srest)$", options: [], p0.uppercased() + prest)
|
||||
self.plural("\(s0.lowercased())(?i)\(srest)$", options: [], p0.lowercased() + prest)
|
||||
self.plural("\(p0.uppercased())(?i)\(prest)$", options: [], p0.uppercased() + prest)
|
||||
self.plural("\(p0.lowercased())(?i)\(prest)$", options: [], p0.lowercased() + prest)
|
||||
|
||||
self.singular("\(s0.uppercased())(?i)\(srest)$", options: [], s0.uppercased() + srest)
|
||||
self.singular("\(s0.lowercased())(?i)\(srest)$", options: [], s0.lowercased() + srest)
|
||||
self.singular("\(p0.uppercased())(?i)\(prest)$", options: [], s0.uppercased() + srest)
|
||||
self.singular("\(p0.lowercased())(?i)\(prest)$", options: [], s0.lowercased() + srest)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Inflections
|
||||
|
||||
/// Returns a pluralized string.
|
||||
///
|
||||
/// Inflections.default.pluralize("player") // "players"
|
||||
public func pluralize(_ string: String) -> String {
|
||||
inflectString(string, with: pluralizeRules)
|
||||
}
|
||||
|
||||
/// Returns a singularized string.
|
||||
public func singularize(_ string: String) -> String {
|
||||
inflectString(string, with: singularizeRules)
|
||||
}
|
||||
|
||||
// MARK: - Utils
|
||||
|
||||
/// Appends an uncountable word.
|
||||
///
|
||||
/// var inflections = Inflections()
|
||||
/// inflections.plural("$", "s")
|
||||
/// inflections.uncountableWord("foo")
|
||||
/// inflections.pluralize("foo") // "foo"
|
||||
/// inflections.pluralize("bar") // "bars"
|
||||
private mutating func uncountableWord(_ word: String) {
|
||||
let escWord = NSRegularExpression.escapedPattern(for: word)
|
||||
uncountablesRegularExpressions[word] = try! NSRegularExpression(
|
||||
pattern: "\\b\(escWord)\\Z",
|
||||
options: [.caseInsensitive])
|
||||
}
|
||||
|
||||
private func isUncountable(_ string: String) -> Bool {
|
||||
let range = NSRange(location: 0, length: string.utf16.count)
|
||||
for (_, reg) in uncountablesRegularExpressions {
|
||||
if reg.firstMatch(in: string, options: [], range: range) != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func inflectString(_ string: String, with rules: [(NSRegularExpression, String)]) -> String {
|
||||
let indexOfLastWord = Inflections.startIndexOfLastWord(string)
|
||||
let endIndexOfDigitlessRadical = Inflections.endIndexOfDigitlessRadical(string)
|
||||
let lastWord = String(string[indexOfLastWord..<endIndexOfDigitlessRadical])
|
||||
if isUncountable(lastWord) {
|
||||
return string
|
||||
}
|
||||
return """
|
||||
\(string.prefix(upTo: indexOfLastWord))\
|
||||
\(inflectWord(lastWord, with: rules))\
|
||||
\(string.suffix(from: endIndexOfDigitlessRadical))
|
||||
"""
|
||||
}
|
||||
|
||||
private func inflectWord(_ string: String, with rules: [(NSRegularExpression, String)]) -> String {
|
||||
if string.isEmpty {
|
||||
return string
|
||||
}
|
||||
let range = NSRange(string.startIndex..<string.endIndex, in: string)
|
||||
for (reg, template) in rules.reversed() {
|
||||
let result = NSMutableString(string: string)
|
||||
let matchCount = reg.replaceMatches(in: result, options: [], range: range, withTemplate: template)
|
||||
if matchCount > 0 {
|
||||
return String(result)
|
||||
}
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
/// startIndexOfLastWord("foo") -> "foo"
|
||||
/// startIndexOfLastWord("foo bar") -> "bar"
|
||||
/// startIndexOfLastWord("foo_bar") -> "bar"
|
||||
/// startIndexOfLastWord("fooBar") -> "Bar"
|
||||
static func startIndexOfLastWord(_ string: String) -> String.Index {
|
||||
let range = NSRange(string.startIndex..<string.endIndex, in: string)
|
||||
|
||||
let index1: String.Index? = wordBoundaryReg.firstMatch(in: string, options: [], range: range).flatMap {
|
||||
if $0.range.location == NSNotFound { return nil }
|
||||
return Range($0.range, in: string)?.lowerBound
|
||||
}
|
||||
let index2: String.Index? = underscoreBoundaryReg.firstMatch(in: string, options: [], range: range).flatMap {
|
||||
if $0.range.location == NSNotFound { return nil }
|
||||
return Range($0.range, in: string).map { string.index(after: $0.lowerBound) }
|
||||
}
|
||||
let index3: String.Index? = caseBoundaryReg.firstMatch(in: string, options: [], range: range).flatMap {
|
||||
if $0.range.location == NSNotFound { return nil }
|
||||
return Range($0.range, in: string).map { string.index(after: $0.lowerBound) }
|
||||
}
|
||||
|
||||
return [index1, index2, index3].compactMap { $0 }.max() ?? string.startIndex
|
||||
}
|
||||
|
||||
/// "bar" -> "bar"
|
||||
/// "foo12" -> "foo"
|
||||
static func endIndexOfDigitlessRadical(_ string: String) -> String.Index {
|
||||
let digits: ClosedRange<Character> = "0"..."9"
|
||||
return string // "foo12"
|
||||
.reversed() // "21oof"
|
||||
.prefix(while: { digits.contains($0) }) // "21"
|
||||
.endIndex // reversed(foo^12)
|
||||
.base // foo^12
|
||||
}
|
||||
|
||||
private static let wordBoundaryReg = try! NSRegularExpression(pattern: "\\b\\w+$", options: [])
|
||||
private static let underscoreBoundaryReg = try! NSRegularExpression(pattern: "_[^_]+$", options: [])
|
||||
private static let caseBoundaryReg = try! NSRegularExpression(pattern: "[^A-Z][A-Z]+[a-z1-9]+$", options: [])
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// A LockedBox protects a value with an NSLock.
|
||||
@propertyWrapper
|
||||
final class LockedBox<T> {
|
||||
private var _wrappedValue: T
|
||||
private var lock = NSLock()
|
||||
|
||||
var wrappedValue: T {
|
||||
get { read { $0 } }
|
||||
set { update { $0 = newValue } }
|
||||
}
|
||||
|
||||
var projectedValue: LockedBox<T> { self }
|
||||
|
||||
init(wrappedValue: T) {
|
||||
_wrappedValue = wrappedValue
|
||||
}
|
||||
|
||||
/// Runs the provided closure while holding a lock on the value.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// // Prints "0"
|
||||
/// @LockedBox var count = 0
|
||||
/// $count.read { print($0) }
|
||||
///
|
||||
/// - parameter block: A closure that accepts the value.
|
||||
@inline(__always)
|
||||
@usableFromInline
|
||||
func read<U>(_ block: (T) throws -> U) rethrows -> U {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return try block(_wrappedValue)
|
||||
}
|
||||
|
||||
/// Runs the provided closure while holding a lock on the value.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// // Prints "1"
|
||||
/// @LockedBox var count = 0
|
||||
/// $count.update { $0 += 1 }
|
||||
/// print(count)
|
||||
///
|
||||
/// - parameter block: A closure that can modify the value.
|
||||
func update<U>(_ block: (inout T) throws -> U) rethrows -> U {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return try block(&_wrappedValue)
|
||||
}
|
||||
}
|
||||
|
||||
extension LockedBox where T: Numeric {
|
||||
@discardableResult
|
||||
func increment() -> T {
|
||||
update { n in
|
||||
n += 1
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func decrement() -> T {
|
||||
update { n in
|
||||
n -= 1
|
||||
return n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension LockedBox: @unchecked Sendable where T: Sendable { }
|
||||
@@ -1,116 +0,0 @@
|
||||
#if canImport(Combine)
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
/// A publisher that eventually produces one value and then finishes or fails.
|
||||
///
|
||||
/// Like a Combine.Future wrapped in Combine.Deferred, OnDemandFuture starts
|
||||
/// producing its value on demand.
|
||||
///
|
||||
/// Unlike Combine.Future wrapped in Combine.Deferred, OnDemandFuture guarantees
|
||||
/// that it starts producing its value on demand, **synchronously**, and that
|
||||
/// it produces its value on promise completion, **synchronously**.
|
||||
///
|
||||
/// Both two extra scheduling guarantees are used by GRDB in order to be
|
||||
/// able to spawn concurrent database reads right from the database writer
|
||||
/// queue, and fulfill GRDB preconditions.
|
||||
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
|
||||
struct OnDemandFuture<Output, Failure: Error>: Publisher {
|
||||
typealias Promise = (Result<Output, Failure>) -> Void
|
||||
typealias Output = Output
|
||||
typealias Failure = Failure
|
||||
fileprivate let attemptToFulfill: (@escaping Promise) -> Void
|
||||
|
||||
init(_ attemptToFulfill: @escaping (@escaping Promise) -> Void) {
|
||||
self.attemptToFulfill = attemptToFulfill
|
||||
}
|
||||
|
||||
func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input {
|
||||
let subscription = OnDemandFutureSubscription(
|
||||
attemptToFulfill: attemptToFulfill,
|
||||
downstream: subscriber)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
|
||||
private class OnDemandFutureSubscription<Downstream: Subscriber>: Subscription {
|
||||
typealias Promise = (Result<Downstream.Input, Downstream.Failure>) -> Void
|
||||
|
||||
private enum State {
|
||||
case waitingForDemand(downstream: Downstream, attemptToFulfill: (@escaping Promise) -> Void)
|
||||
case waitingForFulfillment(downstream: Downstream)
|
||||
case finished
|
||||
}
|
||||
|
||||
private var state: State
|
||||
private let lock = NSRecursiveLock() // Allow re-entrancy
|
||||
|
||||
init(
|
||||
attemptToFulfill: @escaping (@escaping Promise) -> Void,
|
||||
downstream: Downstream)
|
||||
{
|
||||
self.state = .waitingForDemand(downstream: downstream, attemptToFulfill: attemptToFulfill)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.synchronized {
|
||||
switch state {
|
||||
case let .waitingForDemand(downstream: downstream, attemptToFulfill: attemptToFulfill):
|
||||
guard demand > 0 else {
|
||||
return
|
||||
}
|
||||
state = .waitingForFulfillment(downstream: downstream)
|
||||
attemptToFulfill { result in
|
||||
switch result {
|
||||
case let .success(value):
|
||||
self.receive(value)
|
||||
case let .failure(error):
|
||||
self.receive(completion: .failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
case .waitingForFulfillment, .finished:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.synchronized {
|
||||
state = .finished
|
||||
}
|
||||
}
|
||||
|
||||
private func receive(_ value: Downstream.Input) {
|
||||
lock.synchronized { sideEffect in
|
||||
switch state {
|
||||
case let .waitingForFulfillment(downstream: downstream):
|
||||
state = .finished
|
||||
sideEffect = {
|
||||
_ = downstream.receive(value)
|
||||
downstream.receive(completion: .finished)
|
||||
}
|
||||
|
||||
case .waitingForDemand, .finished:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func receive(completion: Subscribers.Completion<Downstream.Failure>) {
|
||||
lock.synchronized { sideEffect in
|
||||
switch state {
|
||||
case let .waitingForFulfillment(downstream: downstream):
|
||||
state = .finished
|
||||
sideEffect = {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
case .waitingForDemand, .finished:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,214 +0,0 @@
|
||||
/// A dictionary with guaranteed keys ordering.
|
||||
///
|
||||
/// var dict = OrderedDictionary<String, Int>()
|
||||
/// dict.append(1, forKey: "foo")
|
||||
/// dict.append(2, forKey: "bar")
|
||||
///
|
||||
/// dict["foo"] // 1
|
||||
/// dict["bar"] // 2
|
||||
/// dict["qux"] // nil
|
||||
/// dict.map { $0.key } // ["foo", "bar"], in this order.
|
||||
struct OrderedDictionary<Key: Hashable, Value> {
|
||||
private(set) var keys: [Key]
|
||||
private(set) var dictionary: [Key: Value]
|
||||
|
||||
var values: [Value] { keys.map { dictionary[$0]! } }
|
||||
|
||||
private init(keys: [Key], dictionary: [Key: Value]) {
|
||||
assert(Set(keys) == Set(dictionary.keys))
|
||||
self.keys = keys
|
||||
self.dictionary = dictionary
|
||||
}
|
||||
|
||||
/// Creates an empty ordered dictionary.
|
||||
init() {
|
||||
keys = []
|
||||
dictionary = [:]
|
||||
}
|
||||
|
||||
/// Creates an empty ordered dictionary.
|
||||
init(minimumCapacity: Int) {
|
||||
keys = []
|
||||
keys.reserveCapacity(minimumCapacity)
|
||||
dictionary = Dictionary(minimumCapacity: minimumCapacity)
|
||||
}
|
||||
|
||||
/// Returns the value associated with key, or nil.
|
||||
subscript(_ key: Key) -> Value? {
|
||||
get { dictionary[key] }
|
||||
set {
|
||||
if let value = newValue {
|
||||
updateValue(value, forKey: key)
|
||||
} else {
|
||||
removeValue(forKey: key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value associated with key, or the default value.
|
||||
subscript(_ key: Key, default defaultValue: Value) -> Value {
|
||||
get { dictionary[key] ?? defaultValue }
|
||||
set { self[key] = newValue }
|
||||
}
|
||||
|
||||
/// Appends the given value for the given key.
|
||||
///
|
||||
/// - precondition: There is no value associated with key yet.
|
||||
mutating func appendValue(_ value: Value, forKey key: Key) {
|
||||
guard updateValue(value, forKey: key) == nil else {
|
||||
fatalError("key is already defined")
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the value stored in the dictionary for the given key, or
|
||||
/// appnds a new key-value pair if the key does not exist.
|
||||
///
|
||||
/// Use this method instead of key-based subscripting when you need to know
|
||||
/// whether the new value supplants the value of an existing key. If the
|
||||
/// value of an existing key is updated, updateValue(_:forKey:) returns the
|
||||
/// original value. If the given key is not present in the dictionary, this
|
||||
/// method appends the key-value pair and returns nil.
|
||||
@discardableResult
|
||||
mutating func updateValue(_ value: Value, forKey key: Key) -> Value? {
|
||||
if let oldValue = dictionary.updateValue(value, forKey: key) {
|
||||
return oldValue
|
||||
}
|
||||
keys.append(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Removes the value associated with key.
|
||||
@discardableResult
|
||||
mutating func removeValue(forKey key: Key) -> Value? {
|
||||
guard let value = dictionary.removeValue(forKey: key) else {
|
||||
return nil
|
||||
}
|
||||
let index = keys.firstIndex { $0 == key }!
|
||||
keys.remove(at: index)
|
||||
return value
|
||||
}
|
||||
|
||||
/// Returns a new ordered dictionary containing the keys of this dictionary
|
||||
/// with the values transformed by the given closure.
|
||||
func mapValues<T>(_ transform: (Value) throws -> T) rethrows -> OrderedDictionary<Key, T> {
|
||||
try reduce(into: .init()) { dict, pair in
|
||||
let value = try transform(pair.value)
|
||||
dict.appendValue(value, forKey: pair.key)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new ordered dictionary containing only the key-value pairs
|
||||
/// that have non-nil values as the result of transformation by the
|
||||
/// given closure.
|
||||
func compactMapValues<T>(_ transform: (Value) throws -> T?) rethrows -> OrderedDictionary<Key, T> {
|
||||
try reduce(into: .init()) { dict, pair in
|
||||
if let value = try transform(pair.value) {
|
||||
dict.appendValue(value, forKey: pair.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func filter(_ isIncluded: ((key: Key, value: Value)) throws -> Bool) rethrows -> OrderedDictionary<Key, Value> {
|
||||
let dictionary = try self.dictionary.filter(isIncluded)
|
||||
let keys = self.keys.filter(dictionary.keys.contains)
|
||||
return OrderedDictionary(keys: keys, dictionary: dictionary)
|
||||
}
|
||||
|
||||
mutating func merge<S>(
|
||||
_ other: S,
|
||||
uniquingKeysWith combine: (Value, Value) throws -> Value)
|
||||
rethrows
|
||||
where S: Sequence, S.Element == (Key, Value)
|
||||
{
|
||||
for (key, value) in other {
|
||||
if let current = self[key] {
|
||||
self[key] = try combine(current, value)
|
||||
} else {
|
||||
self[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutating func merge<S>(
|
||||
_ other: S,
|
||||
uniquingKeysWith combine: (Value, Value) throws -> Value)
|
||||
rethrows
|
||||
where S: Sequence, S.Element == (key: Key, value: Value)
|
||||
{
|
||||
for (key, value) in other {
|
||||
if let current = self[key] {
|
||||
self[key] = try combine(current, value)
|
||||
} else {
|
||||
self[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func merging<S>(
|
||||
_ other: S,
|
||||
uniquingKeysWith combine: (Value, Value) throws -> Value)
|
||||
rethrows -> OrderedDictionary<Key, Value>
|
||||
where S: Sequence, S.Element == (Key, Value)
|
||||
{
|
||||
var result = self
|
||||
try result.merge(other, uniquingKeysWith: combine)
|
||||
return result
|
||||
}
|
||||
|
||||
func merging<S>(
|
||||
_ other: S,
|
||||
uniquingKeysWith combine: (Value, Value) throws -> Value)
|
||||
rethrows -> OrderedDictionary<Key, Value>
|
||||
where S: Sequence, S.Element == (key: Key, value: Value)
|
||||
{
|
||||
var result = self
|
||||
try result.merge(other, uniquingKeysWith: combine)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
extension OrderedDictionary: Collection {
|
||||
typealias Index = Int
|
||||
|
||||
var startIndex: Int { 0 }
|
||||
var endIndex: Int { keys.count }
|
||||
|
||||
func index(after i: Int) -> Int { i + 1 }
|
||||
|
||||
subscript(position: Int) -> (key: Key, value: Value) {
|
||||
let key = keys[position]
|
||||
return (key: key, value: dictionary[key]!)
|
||||
}
|
||||
}
|
||||
|
||||
extension OrderedDictionary: ExpressibleByDictionaryLiteral {
|
||||
init(dictionaryLiteral elements: (Key, Value)...) {
|
||||
self.keys = elements.map { $0.0 }
|
||||
self.dictionary = Dictionary(uniqueKeysWithValues: elements)
|
||||
}
|
||||
}
|
||||
|
||||
extension OrderedDictionary: Equatable where Value: Equatable {
|
||||
static func == (lhs: OrderedDictionary, rhs: OrderedDictionary) -> Bool {
|
||||
(lhs.keys == rhs.keys) && (lhs.dictionary == rhs.dictionary)
|
||||
}
|
||||
}
|
||||
|
||||
extension OrderedDictionary: CustomStringConvertible {
|
||||
var description: String {
|
||||
let chunks = map { (key, value) in
|
||||
"\(String(reflecting: key)): \(String(reflecting: value))"
|
||||
}
|
||||
if chunks.isEmpty {
|
||||
return "[:]"
|
||||
} else {
|
||||
return "[\(chunks.joined(separator: ", "))]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Dictionary {
|
||||
init(_ orderedDictionary: OrderedDictionary<Key, Value>) {
|
||||
self = orderedDictionary.dictionary
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
import Dispatch
|
||||
|
||||
/// A Pool maintains a set of elements that are built them on demand. A pool has
|
||||
/// a maximum number of elements.
|
||||
///
|
||||
/// // A pool of 3 integers
|
||||
/// var number = 0
|
||||
/// let pool = Pool<Int>(maximumCount: 3, makeElement: {
|
||||
/// number = number + 1
|
||||
/// return number
|
||||
/// })
|
||||
///
|
||||
/// The function get() dequeues an available element and gives this element to
|
||||
/// the block argument. During the block execution, the element is not
|
||||
/// available. When the block is ended, the element is available again.
|
||||
///
|
||||
/// // got 1
|
||||
/// pool.get { n in
|
||||
/// print("got \(n)")
|
||||
/// }
|
||||
///
|
||||
/// If there is no available element, the pool builds a new element, unless the
|
||||
/// maximum number of elements is reached. In this case, the get() method
|
||||
/// blocks the current thread, until an element eventually turns available again.
|
||||
///
|
||||
/// DispatchQueue.concurrentPerform(iterations: 6) { _ in
|
||||
/// pool.get { n in
|
||||
/// print("got \(n)")
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// got 1
|
||||
/// got 2
|
||||
/// got 3
|
||||
/// got 2
|
||||
/// got 1
|
||||
/// got 3
|
||||
final class Pool<T> {
|
||||
private class Item {
|
||||
let element: T
|
||||
var isAvailable: Bool
|
||||
|
||||
init(element: T, isAvailable: Bool) {
|
||||
self.element = element
|
||||
self.isAvailable = isAvailable
|
||||
}
|
||||
}
|
||||
|
||||
private let makeElement: () throws -> T
|
||||
@ReadWriteBox private var items: [Item] = []
|
||||
private let itemsSemaphore: DispatchSemaphore // limits the number of elements
|
||||
private let itemsGroup: DispatchGroup // knows when no element is used
|
||||
private let barrierQueue: DispatchQueue
|
||||
private let semaphoreWaitingQueue: DispatchQueue // Inspired by https://khanlou.com/2016/04/the-GCD-handbook/
|
||||
|
||||
/// Creates a Pool.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - maximumCount: The maximum number of elements.
|
||||
/// - qos: The quality of service of asynchronous accesses.
|
||||
/// - makeElement: A function that creates an element. It is called
|
||||
/// on demand.
|
||||
init(
|
||||
maximumCount: Int,
|
||||
qos: DispatchQoS = .unspecified,
|
||||
makeElement: @escaping () throws -> T)
|
||||
{
|
||||
GRDBPrecondition(maximumCount > 0, "Pool size must be at least 1")
|
||||
self.makeElement = makeElement
|
||||
self.itemsSemaphore = DispatchSemaphore(value: maximumCount)
|
||||
self.itemsGroup = DispatchGroup()
|
||||
self.barrierQueue = DispatchQueue(label: "GRDB.Pool.barrier", qos: qos, attributes: [.concurrent])
|
||||
self.semaphoreWaitingQueue = DispatchQueue(label: "GRDB.Pool.wait", qos: qos)
|
||||
}
|
||||
|
||||
/// Returns a tuple (element, release)
|
||||
/// Client must call release(), only once, after the element has been used.
|
||||
func get() throws -> (element: T, release: (PoolCompletion) -> Void) {
|
||||
try barrierQueue.sync {
|
||||
itemsSemaphore.wait()
|
||||
itemsGroup.enter()
|
||||
do {
|
||||
let item = try $items.update { items -> Item in
|
||||
if let item = items.first(where: \.isAvailable) {
|
||||
item.isAvailable = false
|
||||
return item
|
||||
} else {
|
||||
let element = try makeElement()
|
||||
let item = Item(element: element, isAvailable: false)
|
||||
items.append(item)
|
||||
return item
|
||||
}
|
||||
}
|
||||
return (element: item.element, release: { self.release(item, completion: $0) })
|
||||
} catch {
|
||||
itemsSemaphore.signal()
|
||||
itemsGroup.leave()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Eventually produces a tuple (element, release), where element is
|
||||
/// intended to be used asynchronously.
|
||||
///
|
||||
/// Client must call release(), only once, after the element has been used.
|
||||
///
|
||||
/// - important: The `execute` argument is executed in a serial dispatch
|
||||
/// queue, so make sure you use the element asynchronously.
|
||||
func asyncGet(_ execute: @escaping (Result<(element: T, release: (PoolCompletion) -> Void), Error>) -> Void) {
|
||||
// Inspired by https://khanlou.com/2016/04/the-GCD-handbook/
|
||||
// > We wait on the semaphore in the serial queue, which means that
|
||||
// > we’ll have at most one blocked thread when we reach maximum
|
||||
// > executing blocks on the concurrent queue. Any other tasks the user
|
||||
// > enqueues will sit inertly on the serial queue waiting to be
|
||||
// > executed, and won’t cause new threads to be started.
|
||||
semaphoreWaitingQueue.async {
|
||||
execute(Result { try self.get() })
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs a synchronous block with an element. The element turns
|
||||
/// available after the block has executed.
|
||||
func get<U>(block: (T) throws -> U) throws -> U {
|
||||
let (element, completion) = try get()
|
||||
defer { completion(.reuse) }
|
||||
return try block(element)
|
||||
}
|
||||
|
||||
private func release(_ item: Item, completion: PoolCompletion) {
|
||||
$items.update { items in
|
||||
switch completion {
|
||||
case .reuse:
|
||||
// This is why Item is a class, not a struct: so that we can
|
||||
// release it without having to find in it the items array.
|
||||
item.isAvailable = true
|
||||
case .discard:
|
||||
// Discard should be rare: perform lookup.
|
||||
if let index = items.firstIndex(where: { $0 === item }) {
|
||||
items.remove(at: index)
|
||||
}
|
||||
}
|
||||
}
|
||||
itemsSemaphore.signal()
|
||||
itemsGroup.leave()
|
||||
}
|
||||
|
||||
/// Performs a block on each pool element, available or not.
|
||||
/// The block is run is some arbitrary dispatch queue.
|
||||
func forEach(_ body: (T) throws -> Void) rethrows {
|
||||
try $items.read { items in
|
||||
for item in items {
|
||||
try body(item.element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes all elements from the pool.
|
||||
/// Currently used elements won't be reused.
|
||||
func removeAll() {
|
||||
items = []
|
||||
}
|
||||
|
||||
/// Blocks until no element is used, and runs the `barrier` function before
|
||||
/// any other element is dequeued.
|
||||
func barrier<R>(execute barrier: () throws -> R) rethrows -> R {
|
||||
try barrierQueue.sync(flags: [.barrier]) {
|
||||
itemsGroup.wait()
|
||||
return try barrier()
|
||||
}
|
||||
}
|
||||
|
||||
/// Asynchronously runs the `barrier` function when no element is used, and
|
||||
/// before any other element is dequeued.
|
||||
func asyncBarrier(execute barrier: @escaping () -> Void) {
|
||||
barrierQueue.async(flags: [.barrier]) {
|
||||
self.itemsGroup.wait()
|
||||
barrier()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PoolCompletion {
|
||||
// Reuse the element
|
||||
case reuse
|
||||
// Discard the element
|
||||
case discard
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import Dispatch
|
||||
|
||||
/// A ReadWriteBox grants multiple readers and single-writer guarantees on a
|
||||
/// value. It is backed by a concurrent DispatchQueue.
|
||||
@propertyWrapper
|
||||
final class ReadWriteBox<T> {
|
||||
private var _wrappedValue: T
|
||||
private var queue: DispatchQueue
|
||||
|
||||
var wrappedValue: T {
|
||||
get { read { $0 } }
|
||||
set { update { $0 = newValue } }
|
||||
}
|
||||
|
||||
var projectedValue: ReadWriteBox<T> { self }
|
||||
|
||||
init(wrappedValue: T) {
|
||||
_wrappedValue = wrappedValue
|
||||
queue = DispatchQueue(label: "GRDB.ReadWriteBox", attributes: [.concurrent])
|
||||
}
|
||||
|
||||
func read<U>(_ block: (T) throws -> U) rethrows -> U {
|
||||
try queue.sync {
|
||||
try block(_wrappedValue)
|
||||
}
|
||||
}
|
||||
|
||||
func update<U>(_ block: (inout T) throws -> U) rethrows -> U {
|
||||
try queue.sync(flags: [.barrier]) {
|
||||
try block(&_wrappedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ReadWriteBox where T: Numeric {
|
||||
@discardableResult
|
||||
func increment() -> T {
|
||||
update { n in
|
||||
n += 1
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func decrement() -> T {
|
||||
update { n in
|
||||
n -= 1
|
||||
return n
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
#if canImport(Combine)
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
/// A publisher that delivers values to its downstream subscriber on a
|
||||
/// specific scheduler.
|
||||
///
|
||||
/// Unlike Combine's Publishers.ReceiveOn, ReceiveValuesOn only re-schedule
|
||||
/// values and completion. It does not re-schedule subscription.
|
||||
///
|
||||
/// This scheduling guarantee is used by GRDB in order to be able
|
||||
/// to make promises on the scheduling of database values without surprising
|
||||
/// the users as in <https://forums.swift.org/t/28631>.
|
||||
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
|
||||
struct ReceiveValuesOn<Upstream: Publisher, Context: Scheduler>: Publisher {
|
||||
typealias Output = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
fileprivate let upstream: Upstream
|
||||
fileprivate let context: Context
|
||||
fileprivate let options: Context.SchedulerOptions?
|
||||
|
||||
func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input {
|
||||
let subscription = ReceiveValuesOnSubscription(
|
||||
upstream: upstream,
|
||||
context: context,
|
||||
options: options,
|
||||
downstream: subscriber)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
|
||||
private class ReceiveValuesOnSubscription<Upstream, Context, Downstream>: Subscription, Subscriber
|
||||
where
|
||||
Upstream: Publisher,
|
||||
Context: Scheduler,
|
||||
Downstream: Subscriber,
|
||||
Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
private struct Target {
|
||||
let context: Context
|
||||
let options: Context.SchedulerOptions?
|
||||
let downstream: Downstream
|
||||
}
|
||||
|
||||
private enum State {
|
||||
case waitingForRequest(Upstream, Target)
|
||||
case waitingForSubscription(Target, Subscribers.Demand)
|
||||
case subscribed(Target, Subscription)
|
||||
case finished
|
||||
}
|
||||
|
||||
private var state: State
|
||||
private let lock = NSRecursiveLock()
|
||||
|
||||
init(
|
||||
upstream: Upstream,
|
||||
context: Context,
|
||||
options: Context.SchedulerOptions?,
|
||||
downstream: Downstream)
|
||||
{
|
||||
let target = Target(context: context, options: options, downstream: downstream)
|
||||
self.state = .waitingForRequest(upstream, target)
|
||||
}
|
||||
|
||||
// MARK: Subscription
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
lock.synchronized { sideEffect in
|
||||
switch state {
|
||||
case let .waitingForRequest(upstream, target):
|
||||
state = .waitingForSubscription(target, demand)
|
||||
sideEffect = {
|
||||
upstream.receive(subscriber: self)
|
||||
}
|
||||
|
||||
case let .waitingForSubscription(target, currentDemand):
|
||||
state = .waitingForSubscription(target, demand + currentDemand)
|
||||
|
||||
case let .subscribed(_, subcription):
|
||||
sideEffect = {
|
||||
subcription.request(demand)
|
||||
}
|
||||
|
||||
case .finished:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
lock.synchronized { sideEffect in
|
||||
switch state {
|
||||
case .waitingForRequest, .waitingForSubscription:
|
||||
state = .finished
|
||||
|
||||
case let .subscribed(_, subcription):
|
||||
state = .finished
|
||||
sideEffect = {
|
||||
subcription.cancel()
|
||||
}
|
||||
|
||||
case .finished:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Subscriber
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
lock.synchronized { sideEffect in
|
||||
switch state {
|
||||
case let .waitingForSubscription(target, currentDemand):
|
||||
state = .subscribed(target, subscription)
|
||||
sideEffect = {
|
||||
subscription.request(currentDemand)
|
||||
}
|
||||
|
||||
case .waitingForRequest, .subscribed:
|
||||
preconditionFailure()
|
||||
|
||||
case .finished:
|
||||
// We receive the upstream subscription requested by
|
||||
// `upstream.receive(subscriber: self)` above.
|
||||
//
|
||||
// But self has been cancelled since, so let's cancel this
|
||||
// upstream subscription that has turned purposeless.
|
||||
//
|
||||
// This cancellation avoids the bug described in
|
||||
// https://github.com/groue/GRDB.swift/pull/932
|
||||
// TODO: write a regression test.
|
||||
sideEffect = {
|
||||
subscription.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
lock.synchronized { sideEffect in
|
||||
switch state {
|
||||
case let .subscribed(target, _):
|
||||
sideEffect = {
|
||||
target.context.schedule(options: target.options) {
|
||||
self._receive(input)
|
||||
}
|
||||
}
|
||||
case .waitingForRequest, .waitingForSubscription, .finished:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: what problem are we creating by returning .unlimited and
|
||||
// ignoring downstream's result?
|
||||
//
|
||||
// `Publisher.receive(on:options:)` does not document its behavior
|
||||
// regarding backpressure.
|
||||
return .unlimited
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
lock.synchronized { sideEffect in
|
||||
switch state {
|
||||
case .waitingForRequest, .waitingForSubscription:
|
||||
break
|
||||
case let .subscribed(target, _):
|
||||
sideEffect = {
|
||||
target.context.schedule(options: target.options) {
|
||||
self._receive(completion: completion)
|
||||
}
|
||||
}
|
||||
case .finished:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func _receive(_ input: Upstream.Output) {
|
||||
lock.synchronized { sideEffect in
|
||||
switch state {
|
||||
case .waitingForRequest, .waitingForSubscription:
|
||||
break
|
||||
case let .subscribed(target, _):
|
||||
// TODO: don't ignore demand
|
||||
sideEffect = {
|
||||
_ = target.downstream.receive(input)
|
||||
}
|
||||
case .finished:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func _receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
lock.synchronized { sideEffect in
|
||||
switch state {
|
||||
case .waitingForRequest, .waitingForSubscription:
|
||||
break
|
||||
case let .subscribed(target, _):
|
||||
state = .finished
|
||||
sideEffect = {
|
||||
target.downstream.receive(completion: completion)
|
||||
}
|
||||
case .finished:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
|
||||
extension Publisher {
|
||||
/// Specifies the scheduler on which to receive values from the publisher
|
||||
///
|
||||
/// The difference with the stock `receive(on:options:)` Combine method is
|
||||
/// that only values and completion are re-scheduled. Subscriptions are not.
|
||||
func receiveValues<S: Scheduler>(on scheduler: S, options: S.SchedulerOptions? = nil) -> ReceiveValuesOn<Self, S> {
|
||||
ReceiveValuesOn(upstream: self, context: scheduler, options: options)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,36 +0,0 @@
|
||||
/// A marker protocol for refinable types.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// struct Player {
|
||||
/// var name: String
|
||||
/// var score: Int
|
||||
/// }
|
||||
///
|
||||
/// extension Player: Refinable { }
|
||||
///
|
||||
/// let player = Player(name: "Arthur", score: 1000)
|
||||
///
|
||||
/// // Player(name: "Arthur", score: 100)
|
||||
/// let newPlayer = player.with {
|
||||
/// $0.score = 100
|
||||
/// }
|
||||
protocol Refinable { }
|
||||
|
||||
extension Refinable {
|
||||
/// Returns self modified with the *update* function.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// let player = Player(name: "Arthur", score: 1000)
|
||||
/// let newPlayer = player.with {
|
||||
/// $0.score = 100
|
||||
/// }
|
||||
/// newPlayer.name // "Arthur"
|
||||
/// newPlayer.score // 100
|
||||
func with(_ update: (inout Self) throws -> Void) rethrows -> Self {
|
||||
var result = self
|
||||
try update(&result)
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
extension String {
|
||||
/// Returns the receiver, quoted for safe insertion as an identifier in an
|
||||
/// SQL query.
|
||||
///
|
||||
/// db.execute(sql: "SELECT * FROM \(tableName.quotedDatabaseIdentifier)")
|
||||
public var quotedDatabaseIdentifier: String {
|
||||
// See <https://www.sqlite.org/lang_keywords.html>
|
||||
return "\"\(self)\""
|
||||
}
|
||||
}
|
||||
|
||||
/// Return as many question marks separated with commas as the *count* argument.
|
||||
///
|
||||
/// databaseQuestionMarks(count: 3) // "?,?,?"
|
||||
public func databaseQuestionMarks(count: Int) -> String {
|
||||
repeatElement("?", count: count).joined(separator: ",")
|
||||
}
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
/// Reserved for GRDB: do not use.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
func GRDBPrecondition(
|
||||
_ condition: @autoclosure() -> Bool,
|
||||
_ message: @autoclosure() -> String = "",
|
||||
file: StaticString = #file,
|
||||
line: UInt = #line)
|
||||
{
|
||||
// Custom precondition function which aims at solving
|
||||
// <https://bugs.swift.org/browse/SR-905> and
|
||||
// <https://github.com/groue/GRDB.swift/issues/37>
|
||||
if !condition() {
|
||||
fatalError(message(), file: file, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
func fatalError<E: Error>(_ error: E) -> Never {
|
||||
try! { throw error }()
|
||||
}
|
||||
|
||||
// Workaround Swift inconvenience around factory methods of non-final classes
|
||||
func cast<T, U>(_ value: T) -> U? {
|
||||
value as? U
|
||||
}
|
||||
|
||||
extension RangeReplaceableCollection {
|
||||
/// Removes the first object that matches *predicate*.
|
||||
mutating func removeFirst(where predicate: (Element) throws -> Bool) rethrows {
|
||||
if let index = try firstIndex(where: predicate) {
|
||||
remove(at: index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Dictionary {
|
||||
/// Removes the first object that matches *predicate*.
|
||||
mutating func removeFirst(where predicate: (Element) throws -> Bool) rethrows {
|
||||
if let index = try firstIndex(where: predicate) {
|
||||
remove(at: index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DispatchQueue {
|
||||
private static var mainKey: DispatchSpecificKey<()> = {
|
||||
let key = DispatchSpecificKey<()>()
|
||||
DispatchQueue.main.setSpecific(key: key, value: ())
|
||||
return key
|
||||
}()
|
||||
|
||||
static var isMain: Bool {
|
||||
DispatchQueue.getSpecific(key: mainKey) != nil
|
||||
}
|
||||
}
|
||||
|
||||
extension Sequence {
|
||||
func countElements(where predicate: (Element) throws -> Bool) rethrows -> Int {
|
||||
var count = 0
|
||||
for e in self where try predicate(e) {
|
||||
count += 1
|
||||
}
|
||||
return count
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes sure the `finally` function is executed even if `execute` throws, and
|
||||
/// rethrows the eventual first thrown error.
|
||||
///
|
||||
/// Usage:
|
||||
///
|
||||
/// try setup()
|
||||
/// try throwingFirstError(
|
||||
/// execute: work,
|
||||
/// finally: cleanup)
|
||||
func throwingFirstError<T>(execute: () throws -> T, finally: () throws -> Void) throws -> T {
|
||||
var result: T?
|
||||
var firstError: Error?
|
||||
do {
|
||||
result = try execute()
|
||||
} catch {
|
||||
firstError = error
|
||||
}
|
||||
do {
|
||||
try finally()
|
||||
} catch {
|
||||
if firstError == nil {
|
||||
firstError = error
|
||||
}
|
||||
}
|
||||
if let firstError {
|
||||
throw firstError
|
||||
}
|
||||
return result!
|
||||
}
|
||||
|
||||
struct PrintOutputStream: TextOutputStream {
|
||||
func write(_ string: String) {
|
||||
Swift.print(string)
|
||||
}
|
||||
}
|
||||
|
||||
/// Concatenates two functions
|
||||
func concat(_ rhs: (() -> Void)?, _ lhs: (() -> Void)?) -> (() -> Void)? {
|
||||
switch (rhs, lhs) {
|
||||
case let (rhs, nil):
|
||||
return rhs
|
||||
case let (nil, lhs):
|
||||
return lhs
|
||||
case let (rhs?, lhs?):
|
||||
return {
|
||||
rhs()
|
||||
lhs()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Concatenates two functions
|
||||
func concat<T>(_ rhs: ((T) -> Void)?, _ lhs: ((T) -> Void)?) -> ((T) -> Void)? {
|
||||
switch (rhs, lhs) {
|
||||
case let (rhs, nil):
|
||||
return rhs
|
||||
case let (nil, lhs):
|
||||
return lhs
|
||||
case let (rhs?, lhs?):
|
||||
return {
|
||||
rhs($0)
|
||||
lhs($0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NSLocking {
|
||||
func synchronized<T>(
|
||||
_ message: @autoclosure () -> String = #function,
|
||||
_ block: () throws -> T)
|
||||
rethrows -> T
|
||||
{
|
||||
lock()
|
||||
defer { unlock() }
|
||||
return try block()
|
||||
}
|
||||
|
||||
// // Verbose version which helps understanding locking bugs
|
||||
// func synchronized<T>(_ message: @autoclosure () -> String = "", _ block: () throws -> T) rethrows -> T {
|
||||
// let queueName = String(validatingUTF8: __dispatch_queue_get_label(nil))
|
||||
// print("\(queueName ?? "n/d"): \(message()) acquiring \(self)")
|
||||
// lock()
|
||||
// print("\(queueName ?? "n/d"): \(message()) acquired \(self)")
|
||||
// defer {
|
||||
// print("\(queueName ?? "n/d"): \(message()) releasing \(self)")
|
||||
// unlock()
|
||||
// print("\(queueName ?? "n/d"): \(message()) released \(self)")
|
||||
// }
|
||||
// return try block()
|
||||
// }
|
||||
|
||||
/// Performs the side effect outside of the synchronized block. This allows
|
||||
/// avoiding deadlocks, when the side effect feedbacks.
|
||||
func synchronized(
|
||||
_ message: @autoclosure () -> String = #function,
|
||||
_ block: (inout (() -> Void)?) -> Void)
|
||||
{
|
||||
var sideEffect: (() -> Void)?
|
||||
synchronized(message()) { block(&sideEffect) }
|
||||
sideEffect?()
|
||||
}
|
||||
}
|
||||
|
||||
#if !canImport(ObjectiveC)
|
||||
@inlinable func autoreleasepool<Result>(invoking body: () throws -> Result) rethrows -> Result { try body() }
|
||||
#endif
|
||||
Reference in New Issue
Block a user