Skip to content
ApolloZhu edited this page Sep 18, 2019 · 7 revisions

In this chapter, we are going to study how to wrap your container with EFStorage.

// If you integrated using SPM, you should
import EFStorageCore
// if with CocoaPods, then
import EFStorage

IMPORTANT: remember to replace placeholder <#ExampleStorage#> with your underlying storage name!

Conform to EFUnderlyingStorage

extension <#ExampleStorage#>: EFUnderlyingStorage {
    public class func makeDefault() -> Self {
        // ... return the default storage
    }
}

In case if your class/struct may fail the initialization process, you could conform to EFFailableUnderlyingStorage instead.

extension <#ExampleStorage#>: EFFailableUnderlyingStorage {
    public class func makeDefault() -> Self? {
        // ... return the default storage or nil
    }
}

Define Protocol

public protocol <#ExampleStorage#>Storable {
    func as<#ExampleStorage#>Storable() -> <#ExampleStorage#>Storable//or ! or ?, depends on your usage
    static func from<#ExampleStorage#>(_ <#exampleStorage#>: <#ExampleStorage#>, forKey key: String) -> Self?
}

then conform type to <#ExampleStorage#>Storable to be stored.

Since you have total control of this protocol, so returning Result<StorableType, Error> for type safety (like those that comes with EFStorage by default), or even writing a protocol that looks nothing like the one above, all of them are totally acceptable as long as it fits your need.

Implement the EFStorage<#ExampleStorage#>Ref Class

Due to the original implementation of EFStorage, it has to be a singleton.

public class EFStorage<#ExampleStorage#>Ref<Content: <#ExampleStorage#>Storable>: EFSingleInstanceStorageReference {
    public required init(
        iKnowIShouldNotCallThisDirectlyAndIsResponsibleForUnexpectedBehaviorMyself: Bool,
        forKey key: String, in storage: <#ExampleStorage#>//? // if your storage may fail to initialize
    ) {
        self.key = key
        self.storage = storage
        self.content = Content.from<#ExampleStorage#>(storage, forKey: key)
    }
    
    public let key: String
    public let storage: <#ExampleStorage#>//?
    public var content: Content? {
        didSet {
            if let newValue = content {
                storage/*?*/.set(newValue.as<#ExampleStorage#>Storable(), forKey: key)
            } else {
                storage/*?*/.removeObject(forKey: key)
            }
        }
    }
}

Provide the @propertyWrapper

This is not required, but highly recommended.

@propertyWrapper
public struct EFStorage<#ExampleStorage#><Content: <#ExampleStorage#>Storable>: EFSingleInstanceStorageReferenceWrapper {
    public var _ref: EFStorage<#ExampleStorage#>Ref<Content>
    public var wrappedValue: Content {
        get {
            if let content = _ref.content { return content }
            let defaultContent = makeDefaultContent()
            if persistDefaultContent { _ref.content = defaultContent }
            return defaultContent
        }
        set { _ref.content = newValue }
    }
    
    public let persistDefaultContent: Bool
    public let makeDefaultContent: () -> Content
    public func removeContentFromUnderlyingStorage() {
        _ref.content = nil
    }
    
    public init(
        __ref: EFStorage<#ExampleStorage#>Ref<Content>,
        makeDefaultContent: @escaping () -> Content,
        persistDefaultContent: Bool
    ) {
        self._ref = __ref
        self.makeDefaultContent = makeDefaultContent
        self.persistDefaultContent = persistDefaultContent
    }
}

Support @dynamicMemberLookup

Again, this is optional, but can be useful

public extension EFUnderlyingStorageWrapper {
    subscript<T: <#ExampleStorage#>Storable>(
        dynamicMember key: String
    ) -> T? where Base == <#ExampleStorage#> {
        get {
            return EFStorage<#ExampleStorage#>Ref.forKey(key, in: base).content
        }
        set {
            EFStorage<#ExampleStorage#>Ref.forKey(key, in: base).content = newValue
        }
    }
    
    subscript<T: <#ExampleStorage#>Storable>(
        dynamicMember key: String
    ) -> T? where Base == <#ExampleStorage#>.Type {
        get {
            return EFStorage<#ExampleStorage#>Ref.forKey(key).content
        }
        set {
            EFStorage<#ExampleStorage#>Ref.forKey(key).content = newValue
        }
    }
}