Last active
December 29, 2022 09:06
-
-
Save hooman/e103cac5ea929dbeda0b0f6ad8e83b5d to your computer and use it in GitHub Desktop.
A set of utilities for working with weak references.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//: Playground - noun: a place where people can play | |
/// A protocol to provide an abstraction of types that hold a weak reference to a target object. | |
/// | |
/// It is defined because a single generic implementation cannot support existentials, as they | |
/// do not conform to themselves or `AnyObject`. Most of its API is defined by protocol extensions | |
/// to makes it easier to create existential wrapper `struct`s or `final class`es. | |
/// | |
/// Here is an example protocol and the corresponding weak reference | |
/// container: | |
/// | |
/// protocol MyDelegateProtocol: AnyObject { func didSomething() } | |
/// | |
/// struct MyDelegateRef: WeakReference { | |
/// private(set) weak var ref: MyDelegateProtocol? | |
/// init(_ reference: MyDelegateProtocol?) { self.ref = reference } | |
/// } | |
/// | |
/// That is all that is needed to get the rest of the API. | |
/// | |
/// This protocol is `ExpressibleByNilLiteral` (implementation provided) | |
/// to support creating empty or sentinel values that are useful in some | |
/// situations. | |
public protocol WeakReference: ExpressibleByNilLiteral { | |
/// The type of the reference stored by this type. | |
/// | |
/// It is not constrained to `AnyObject` to support existentials from class-constrained | |
/// non-Objective-C protocols. | |
associatedtype ReferenceType | |
/// Returns the reference stored by this type. | |
/// | |
/// Returns `nil` if the reference is already released (deallocated). | |
var ref: ReferenceType? {get} | |
/// Returns true if the object is released (deallocated). | |
/// | |
/// Default implementation provided. | |
var isGone: Bool {get} | |
/// Returns true if the object is still available. | |
/// | |
/// Default implementation provided. | |
var isAvailable: Bool {get} | |
/// Creates a new wrapper from the given optional `reference`. | |
/// | |
/// - Parameter `reference`: The reference to be weakly held. | |
init(_ reference: ReferenceType?) | |
/// Returns `true` if the wrapper contains the given object. | |
/// | |
/// Default implementation provided. | |
func contains(_ object: Any?) -> Bool | |
/// A map function that returns another weak reference to help provide a monadish behavior for this type. | |
/// | |
/// It does not return `Self` to aid in jumping between generic and concrete (existential) containers. | |
/// | |
/// Default implementation provided. | |
/// | |
/// - Parameter transform: A function that returns an object | |
/// - Returns: A weak reference to the result of applying `transform` to the reference stored in | |
/// this type. | |
func map<T,W: WeakReference>(_ transform: (ReferenceType) throws -> T?) rethrows -> W where W.ReferenceType==T | |
} | |
extension WeakReference { | |
public var isGone: Bool { return ref == nil } | |
public var isAvailable: Bool { return ref != nil } | |
public func contains(_ object: Any?) -> Bool { | |
guard let ref = ref as AnyObject?, let object = object as AnyObject? else { return false } | |
return ref === object | |
} | |
public func map<T,W:WeakReference>(_ transform:(ReferenceType)throws->T?)rethrows->W where W.ReferenceType==T { | |
return try W(ref.flatMap(transform)) | |
} | |
} | |
extension WeakReference /*ExpressibleByNilLiteral*/ { | |
public init(nilLiteral: ()) { self.init(nil) } | |
} | |
extension WeakReference /*CustomStringConvertible*/ { | |
public var description: String { | |
if let ref = ref { return "「\(ref)」" } else { return "「」" } | |
} | |
} | |
extension WeakReference /*Identity based equality*/ { | |
/// Identity-bases equality test operator. | |
public static func == (lhs: Self, rhs: Self) -> Bool { | |
if lhs.ref == nil && rhs.ref == nil { return true } | |
guard let lhs = lhs.ref, let rhs = rhs.ref else { return false } | |
return lhs as AnyObject === rhs as AnyObject | |
} | |
/// Identity-bases inequality test operator. | |
public static func != (lhs: Self, rhs: Self) -> Bool { return !(lhs == rhs) } | |
} | |
extension WeakReference where ReferenceType: Equatable { | |
/// `Equatable` conditional conformance. | |
public static func == (lhs: Self, rhs: Self) -> Bool { | |
if lhs.ref == nil && rhs.ref == nil { return true } | |
guard let lhs = lhs.ref, let rhs = rhs.ref else { return false } | |
return lhs == rhs | |
} | |
/// `Equatable` conditional conformance. | |
public static func != (lhs: Self, rhs: Self) -> Bool { return !(lhs == rhs) } | |
} | |
extension WeakReference where ReferenceType: Hashable { | |
public var hashValue: Int { | |
guard let ref = ref else { return Int.min } | |
return ref.hashValue | |
} | |
} | |
/// A generic wrapper type to keep a weak reference to an object. | |
/// | |
/// This wrapper type is used to keep a weak reference to an object in some other container such as array or dictionary. | |
/// It could also be defined as a `final class` to reduce the number of copies of the weak reference created to help | |
/// improve performance with old (pre-Swift 4.0) behavior of the weak references. `final class` can still help with | |
/// lifetime of side tables, but I don't think this is really going to matter. On the other hand, class has a higher cost | |
/// in temrs construction, per-instance memory and reference counting. | |
public struct Weak<Object: AnyObject>: WeakReference { | |
public typealias ReferenceType = Object | |
public private(set) weak var ref: ReferenceType? | |
public init(_ reference: ReferenceType?) { ref = reference } | |
} | |
extension Weak: CustomStringConvertible {} | |
#if swift(>=4.1) | |
extension Weak: Equatable where Weak.ReferenceType: Equatable {} | |
extension Weak: Hashable where Weak.ReferenceType: Hashable {} | |
#endif | |
/// A sequence of weak pointers to objects. | |
/// | |
/// Besides `Sequence` operations, this type supports basic mutating operations | |
/// `add(_:)` and `remove(_:)`. | |
/// | |
/// It is intended to be used to store a list of weak references to target objects | |
/// typically to notify them with a `for`-loop. Considering `MyDelegateProtocol` | |
/// from `WeakReference` documentation, here is an example use of `WeakSequence`: | |
/// | |
/// typealias MyDelegates = WeakSequence<MyDelegateRef> | |
/// | |
/// var myDelegates = MyDelegates() | |
/// | |
/// class SomeClass {} | |
/// | |
/// extension SomeClass: MyDelegateProtocol { func didSomething() { /* ... */ } } | |
/// | |
/// let aDelegate = SomeClass() | |
/// myDelegates.add(aDelegate) | |
/// for delegate in myDelegates { delegate.didSomething() } | |
/// | |
public struct WeakSequence<WeakHolder: WeakReference>: Sequence { | |
public struct Iterator: IteratorProtocol { | |
private var iterator: Array<WeakHolder>.Iterator | |
fileprivate init(_ weakSeq: WeakSequence) { self.iterator = weakSeq.weakElements.makeIterator() } | |
/// Returns the next available object in the sequence, or `nil` if there are no more objects. | |
/// | |
/// - Returns: The next available reference in the sequence; or `nil` is there are no more references left. | |
public mutating func next() -> Element? { | |
repeat { | |
guard let nextHolder = iterator.next() else { return nil } | |
if let nextRef = nextHolder.ref { return nextRef } | |
} while true | |
} | |
} | |
/// The `Sequence` element type. | |
public typealias Element = WeakHolder.ReferenceType | |
// Storage for weak references | |
private var weakElements: [WeakHolder] | |
/// Creates a `WeakSequence` from the given sequence of weak reference holders. | |
public init<S: Sequence>(_ elements: S) where S.Element == WeakHolder { weakElements = Array(elements) } | |
/// Creates a `WeakSequence` from the given sequence of weak reference holders. | |
public init<S: Sequence>(_ elements: S) where S.Element == WeakHolder.ReferenceType { weakElements = elements.map(WeakHolder.init) } | |
/// Creates a `WeakSequence` from the given sequence of weak reference holders. | |
public init<S: Sequence>(_ elements: S) where S.Element == WeakHolder.ReferenceType? { weakElements = elements.map(WeakHolder.init) } | |
public func makeIterator() -> Iterator { return Iterator(self) } | |
/// Removes all deallocated objects from the sequence. | |
/// | |
/// This operation does not have any visible impact on | |
/// the sequence behavior as it always returns available | |
/// references. | |
public mutating func compact() { weakElements = weakElements.filter { $0.isAvailable } } | |
/// Adds an object to the sequence in an undefined order. | |
/// | |
/// - Parameter element: The reference to be added to the sequence. | |
/// - Parameter allowDuplicates: If you pass `true`, it will allow duplicates to be added. The default | |
/// is `false`. When you are sure it will not be duplicate, passing `true` improves performance. | |
/// - Returns: `true` if the `element` was actually added; `false` otherwise. | |
@discardableResult | |
public mutating func add(_ element: Element?, allowDuplicates: Bool = false ) -> Bool { | |
guard let element = element else { return false } | |
guard allowDuplicates || !contains(element) else { return false } | |
if let index = emptyIndex() { | |
weakElements[index] = WeakHolder(element) | |
} else { | |
weakElements.append(WeakHolder(element)) | |
} | |
return true | |
} | |
/// Removes the given object from the sequence if it is there. | |
/// | |
/// - Parameter object: The reference to be removed from the sequence. | |
/// - Returns: `true` if the `element` was actually removed; `false` otherwise. | |
@discardableResult | |
public mutating func remove(_ element: Any?) -> Bool { | |
guard let element = element, let index = index(of: element) else { return false } | |
weakElements[index] = nil | |
return true | |
} | |
/// Determines if the sequence contains a given `object`. | |
/// | |
/// - Parameter object: The reference to look for. | |
/// - Returns: `true` if `object` is found; `false` otherwise. | |
public func contains(_ element: Any?) -> Bool { return index(of: element) != nil } | |
// Finds the index of the given object; if present. | |
private func index(of element: Any?) -> Int? { return weakElements.index(where: { $0.contains(element) }) } | |
// Finds the first empty index (where the reference is gone) | |
private func emptyIndex() -> Int? { | |
var index = 0 | |
let end = weakElements.endIndex | |
while index < end && weakElements[index].isAvailable { index += 1 } | |
guard index < end else { return nil } | |
return index | |
} | |
} | |
extension WeakSequence { | |
/// Creates an empty `WeakSequence`. | |
public init() { weakElements = [] } | |
} | |
extension WeakSequence: ExpressibleByArrayLiteral { | |
public init(arrayLiteral elements: Element?...) { self.init(elements.map(WeakHolder.init) ) } | |
} | |
extension WeakSequence: CustomStringConvertible { | |
public var description: String { return Array(self).description } | |
} | |
extension WeakSequence: CustomDebugStringConvertible { | |
public var debugDescription: String { return "[\(weakElements.map({ $0.description }).joined())]" } | |
} | |
/// Typealias for better readability of `WeakSequence<Weak<T>>` | |
public typealias WeakSequenceOf<T> = WeakSequence<Weak<T>> where T: AnyObject | |
/// Returns the given object ref in a weak container. | |
func weak<T: AnyObject>(_ object: T?) -> Weak<T> { return Weak(object) } | |
/// Returns the given existentiasl ref in a weak existential container. | |
func weak<W: WeakReference>(_ object: W.ReferenceType?) -> W { return W(object) } | |
/// Given a sequence of weak references, returns a `WeakSequence`. | |
func weak<S: Sequence>(_ sequence: S) -> WeakSequence<S.Element> where S.Element: WeakReference { return WeakSequence(sequence) } | |
/// Given a sequence of objects, returns a weakly held sequence of the same objects. | |
func weak<S: Sequence>(_ sequence: S) -> WeakSequenceOf<S.Element> where S.Element: AnyObject { return WeakSequenceOf<S.Element>(sequence.map(Weak.init)) } | |
/// Given a sequence of _optional_ objects, returns a weakly held sequence of the same objects. | |
func weak<S:Sequence, T:AnyObject>(_ seq: S) -> WeakSequenceOf<T> where S.Element == T? { | |
#if swift(>=4.1) | |
return WeakSequenceOf<T>(seq.compactMap(Weak.init)) | |
#else | |
return WeakSequenceOf<T>(seq.flatMap(Weak.init)) | |
#endif | |
} | |
// Usage example/basic test: | |
// Define a demo class: | |
class C: CustomStringConvertible { | |
let id: Int | |
var description: String{ return "C\(id)" } | |
init(_ id: Int) { self.id = id } | |
deinit { print("\(self) is gone.") } | |
} | |
// Make some nullable objects: | |
var c1: C? = C(1), c2: C? = C(2), c3: C? = C(3), c4: C? = C(4), c5: C? = C(5) | |
var c6: C? = C(6), c7: C? = C(7), c8: C? = C(8), c9: C? = C(9), c10: C? = C(10) | |
var c11: C? = C(11), c12: C? = C(12), c13: C? = C(13), c14: C? = C(14), c15: C? = C(15) | |
// Make a weak sequence: | |
var ws = WeakSequenceOf([c1,c2,c3,c4,c5,c6,c7,c8]) | |
// Play with the sequence: | |
print("Initial sequence") | |
debugPrint(ws) | |
print(ws) | |
var it = ws.makeIterator() | |
print("Iterator:") | |
print(it) | |
print("Discard c1, c3, c5, c7.") | |
(c1,c3,c5,c7) = (nil,nil,nil,nil) | |
debugPrint(ws) | |
print(Array(ws)) | |
print("Add c9, c10, c11, c12, c13, c14") | |
ws.add(c9!) | |
ws.add(c10!) | |
ws.add(c11!) | |
ws.add(c12!) | |
ws.add(c13!) | |
ws.add(c14!) | |
debugPrint(ws) | |
print(ws) | |
print("Remove c11, c8") | |
ws.remove(c11!) | |
ws.remove(c8!) | |
debugPrint(ws) | |
print(ws) | |
print("Discard c2, c6, c12") | |
var wc2 = Weak(c2) | |
var wc6 = Weak(c6) | |
c2 = nil | |
c6 = nil | |
c12 = nil | |
debugPrint(ws) | |
print(ws) | |
print("Add c15. Remove c14") | |
ws.add(c15!) | |
ws.remove(c14!) | |
debugPrint(ws) | |
print(ws) | |
print("Compact") | |
ws.compact() | |
debugPrint(ws) | |
print(ws) | |
print("What remains in the original iterator:") | |
print(Array(IteratorSequence(it))) | |
// Example weak reference for an existential type: | |
protocol MyObserverProtocol: AnyObject { func notify() } | |
extension C: MyObserverProtocol { func notify() { print("C(\(id)).notify!") } } | |
struct WeakMyObserver: WeakReference { | |
private(set) weak var ref: MyObserverProtocol? | |
init(_ reference: MyObserverProtocol?) { self.ref = reference } | |
} | |
typealias MyObservers = WeakSequence<WeakMyObserver> | |
var myObservers = MyObservers([c1,c2,c3,c4,c5,c6,c7,c8,c9,c10,c11,c12,c13,c14,c15]) | |
for observer in myObservers { observer.notify() } | |
// `nil` equality tests: | |
print("wc2 == nil -> ", wc2 == nil) | |
print("wc2 != nil -> ", wc2 != nil) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Great job! Thanks a lot!