Skip to content

Instantly share code, notes, and snippets.

@hooman
Last active December 29, 2022 09:06
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hooman/e103cac5ea929dbeda0b0f6ad8e83b5d to your computer and use it in GitHub Desktop.
Save hooman/e103cac5ea929dbeda0b0f6ad8e83b5d to your computer and use it in GitHub Desktop.
A set of utilities for working with weak references.
//: 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)
@timqzm
Copy link

timqzm commented Jul 5, 2018

Great job! Thanks a lot!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment