import Foundation extension Decodable { init?(jsonData: Data) { guard let value = try? JSONDecoder().decode(Self.self, from: jsonData) else { return nil } self = value } init?(jsonString: String) { guard let data = jsonString.data(using: .utf8) else { return nil } self.init(jsonData: data) } } final class ObjectAssociation { subscript(index: AnyObject) -> T? { get { objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T? } set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } /** Causes a given target object to live at least as long as a given owner object. */ final class LifetimeAssociation { private class ObjectLifetimeTracker { var object: AnyObject? var deinitHandler: () -> Void init(for weaklyHeldObject: AnyObject, deinitHandler: @escaping () -> Void) { self.object = weaklyHeldObject self.deinitHandler = deinitHandler } deinit { deinitHandler() } } private static let associatedObjects = ObjectAssociation<[ObjectLifetimeTracker]>() private weak var wrappedObject: ObjectLifetimeTracker? private weak var owner: AnyObject? /** Causes the given target object to live at least as long as either the given owner object or the resulting `LifetimeAssociation`, whichever is deallocated first. When either the owner or the new `LifetimeAssociation` is destroyed, the given deinit handler, if any, is called. ``` class Ghost { var association: LifetimeAssociation? func haunt(_ host: Furniture) { association = LifetimeAssociation(of: self, with: host) { [weak self] in // Host has been deinitialized self?.haunt(seekHost()) } } } let piano = Piano() Ghost().haunt(piano) // The Ghost will remain alive as long as `piano` remains alive. ``` - Parameter target: The object whose lifetime will be extended. - Parameter owner: The object whose lifetime extends the target object's lifetime. - Parameter deinitHandler: An optional closure to call when either `owner` or the resulting `LifetimeAssociation` is deallocated. */ init(of target: AnyObject, with owner: AnyObject, deinitHandler: @escaping () -> Void = {}) { let wrappedObject = ObjectLifetimeTracker(for: target, deinitHandler: deinitHandler) let associatedObjects = LifetimeAssociation.associatedObjects[owner] ?? [] LifetimeAssociation.associatedObjects[owner] = associatedObjects + [wrappedObject] self.wrappedObject = wrappedObject self.owner = owner } /** Invalidates the association, unlinking the target object's lifetime from that of the owner object. The provided deinit handler is not called. */ func cancel() { wrappedObject?.deinitHandler = {} invalidate() } deinit { invalidate() } private func invalidate() { guard let owner = owner, let wrappedObject = wrappedObject, var associatedObjects = LifetimeAssociation.associatedObjects[owner], let wrappedObjectAssociationIndex = associatedObjects.firstIndex(where: { $0 === wrappedObject }) else { return } associatedObjects.remove(at: wrappedObjectAssociationIndex) LifetimeAssociation.associatedObjects[owner] = associatedObjects self.owner = nil } } /// 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. public protocol _DefaultsOptionalType: ExpressibleByNilLiteral { /// This is useful as you can't compare `_OptionalType` to `nil`. var isNil: Bool { get } } extension Optional: _DefaultsOptionalType { public var isNil: Bool { self == nil } } func isOptionalType(_ type: T.Type) -> Bool { type is _DefaultsOptionalType.Type } 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) } } }