Skip to content

Instantly share code, notes, and snippets.

@orchetect
Last active September 23, 2022 04:30
Show Gist options
  • Select an option

  • Save orchetect/670571acc03840d8674ea3b08302f661 to your computer and use it in GitHub Desktop.

Select an option

Save orchetect/670571acc03840d8674ea3b08302f661 to your computer and use it in GitHub Desktop.
Weak.swift
//
// Weak.swift
//
// Created by Steffan Andrews on 2019-11-16.
// Copyright © 2019 Steffan Andrews. All rights reserved.
//
// MARK: - Weak<T>
/// Wrapper that contains a weak reference to an object.
internal struct Weak<T: AnyObject> {
weak var wrapped: T?
init(_ object: T?) {
wrapped = object
}
}
// MARK: - WeakHashable<T>
/// Wrapper that contains a weak reference to a `Hashable` object.
internal struct WeakHashable<T: AnyObject>: Hashable where T: Hashable {
weak var wrapped: T?
init(_ object: T?) {
wrapped = object
}
func hash(into hasher: inout Hasher) {
hasher.combine(wrapped)
}
}
// MARK: - WeakArray
/// Transparently encapsulates each element in a `Weak` box, storing the value as a `weak var` reference.
public struct WeakArray<Element: AnyObject> {
// backing array of Weak elements
private var storage: [Weak<Element>] = []
// user-exposed unwrapped elements
public var elements: [Element?] {
get {
storage.map { $0.wrapped }
}
set {
storage = newValue.map { Weak($0) }
}
}
init() { }
init(_ elements: [Element?]) {
storage = elements.map { Weak($0) }
}
public subscript(_ index: Array<Element>.Index) -> Element? {
get {
storage[index].wrapped
}
set {
storage[index] = Weak(newValue)
}
}
/// Removes weak elements boxes that contain `nil`.
public mutating func cleanup() {
for idx in storage.indices.reversed() {
if storage[idx].wrapped == nil {
storage.remove(at: idx)
}
}
}
}
extension WeakArray: ExpressibleByArrayLiteral {
public typealias ArrayLiteralElement = Element
public init(arrayLiteral elements: Element...) {
self.init(elements)
}
}
// MARK: - WeakArrayStorage propertywrapper
/// Transparently encapsulates each element in a `Weak` box, storing the value as a `weak var` reference.
@propertyWrapper
public struct WeakArrayStorage<Element: AnyObject> {
private var value: WeakArray<Element>
public var wrappedValue: [Element?] {
get {
value.elements
}
set {
value.elements = newValue
}
}
public init(wrappedValue: [Element?]) {
value = WeakArray(wrappedValue)
}
/// Removes weak elements boxes that contain `nil`.
public mutating func cleanup() {
value.cleanup()
}
}
// MARK: - WeakSet
/// Array that holds WeakBox objects. Accessing directly will act as a set of unwrapped objects.
public struct WeakSet<Element: AnyObject> where Element: Hashable {
// backing array of Weak elements
private var storage: Set<WeakHashable<Element>> = []
// user-exposed unwrapped elements
public var elements: Set<Element?> {
get {
Set(storage.map { $0.wrapped })
}
set {
storage = Set(newValue.map { WeakHashable($0) })
}
}
init() { }
init(_ elements: [Element?]) {
storage = Set(elements.map { WeakHashable($0) })
}
init(_ set: Set<Element?>) {
storage = Set(elements.map { WeakHashable($0) })
}
/// Removes elements that contain `nil`.
public mutating func cleanup() {
for idx in storage.indices.reversed() {
if storage[idx].wrapped == nil {
storage.remove(at: idx)
}
}
}
}
extension WeakSet: ExpressibleByArrayLiteral {
public typealias ArrayLiteralElement = Element
public init(arrayLiteral elements: Element...) {
self.init(elements)
}
}
// MARK: - WeakSetStorage propertywrapper
/// Transparently encapsulates each element in a `Weak` box, storing the value as a `weak var` reference.
@propertyWrapper
public struct WeakSetStorage<Element: AnyObject> where Element: Hashable {
private var value: WeakSet<Element>
public var wrappedValue: Set<Element?> {
get {
value.elements
}
set {
value.elements = newValue
}
}
public init(wrappedValue: Set<Element?>) {
value = WeakSet(wrappedValue.map { $0 })
}
/// Removes weak elements that contain `nil`.
public mutating func cleanup() {
value.cleanup()
}
}
// MARK: - WeakDictionary
/// Array that holds WeakBox objects. Accessing directly will act as a dictionary of unwrapped objects.
public struct WeakDictionary<KeyType: Hashable, Element: AnyObject> {
public typealias DictionaryType = [KeyType: Element?]
fileprivate typealias StorageDictionaryType = [KeyType: Weak<Element>]
// backing array of Weak elements
private var storage: StorageDictionaryType = [:]
public var elements: DictionaryType {
get {
storage.mapValues { $0.wrapped }
}
set {
storage = newValue.mapValues { Weak($0) }
}
}
init() { }
init(_ dictionary: [KeyType: Element?]) {
storage = dictionary.mapValues { Weak($0) }
}
}
extension WeakDictionary {
public subscript(_ key: KeyType) -> Element? {
get {
storage[key]?.wrapped
}
set {
storage[key] =
newValue == nil
? nil
: Weak(newValue)
}
}
/// Removes weak elements that contain `nil`.
public mutating func cleanup() {
for key in storage.keys {
if storage[key]?.wrapped == nil {
storage[key] = nil
}
}
}
}
extension WeakDictionary: ExpressibleByDictionaryLiteral {
public typealias Key = KeyType
public typealias Value = Element
public init(dictionaryLiteral elements: (KeyType, Element)...) {
elements.forEach { storage[$0.0] = Weak($0.1) }
}
}
// MARK: - WeakDictionary propertywrapper
/// Transparently encapsulates each element in a `Weak` box, storing the value as a `weak var` reference.
@propertyWrapper
public struct WeakDictionaryStorage<KeyType: Hashable, Element: AnyObject> {
private var value: WeakDictionary<KeyType, Element>
public var wrappedValue: [KeyType: Element?] {
get {
value.elements
}
set {
value.elements = newValue
}
}
public init(wrappedValue: [KeyType: Element?]) {
value = WeakDictionary(wrappedValue)
}
/// Removes weak elements that contain `nil`.
public mutating func cleanup() {
value.cleanup()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment