mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
656 lines
23 KiB
Swift
656 lines
23 KiB
Swift
// This file is derived from swift/stdlib/public/SDK/Foundation/JSONEncoder.swift
|
|
// and swift/stdlib/public/SDK/Foundation/PlistEncoder.swift
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import FirebaseFirestore
|
|
import Foundation
|
|
|
|
extension Firestore {
|
|
public struct Encoder {
|
|
public init() {}
|
|
/// Returns encoded data that Firestore API recognizes.
|
|
///
|
|
/// If possible, all types will be converted to compatible types Firestore
|
|
/// can handle. This means certain Firestore specific types will be encoded
|
|
/// as pass-through: this encoder will only pass those types along since that
|
|
/// is what Firestore can handle. The same types will be encoded differently
|
|
/// with other encoders (for example: JSONEncoder).
|
|
///
|
|
/// The Firestore pass-through types are:
|
|
/// - GeoPoint
|
|
/// - Timestamp
|
|
/// - DocumentReference
|
|
///
|
|
/// - Parameter value: The Encodable object to convert to encoded data.
|
|
/// - Returns: A Map keyed by String representing a document Firestore
|
|
/// API can work with.
|
|
public func encode<T: Encodable>(_ value: T) throws -> [String: Any] {
|
|
// SelfDocumentID, DocumentReference and FieldValue cannot be
|
|
// encoded directly.
|
|
guard T.self != DocumentReference.self,
|
|
T.self != FieldValue.self else {
|
|
throw EncodingError
|
|
.invalidValue(value,
|
|
EncodingError
|
|
.Context(codingPath: [],
|
|
debugDescription: "Top-level \(T.self) is not allowed."))
|
|
}
|
|
guard let topLevel = try _FirestoreEncoder().box_(value) else {
|
|
throw EncodingError.invalidValue(value,
|
|
EncodingError.Context(codingPath: [],
|
|
debugDescription: "Top-level \(T.self) did not encode any values."))
|
|
}
|
|
|
|
// This is O(n) check. Consider refactoring box_ to return [String: Any].
|
|
guard let dict = topLevel as? [String: Any] else {
|
|
throw EncodingError.invalidValue(value,
|
|
EncodingError.Context(codingPath: [],
|
|
debugDescription: "Top-level \(T.self) encoded not as dictionary."))
|
|
}
|
|
return dict
|
|
}
|
|
}
|
|
}
|
|
|
|
private class _FirestoreEncoder: Encoder {
|
|
/// A stack of data containers storing encoded results. When a new object is being encoded,
|
|
/// a corresponding storage is pushed to the stack; and when the field and all of its children objects
|
|
/// are encoded, the stoage should have the entire encoded result for the sub-tree, it is then poped
|
|
/// out and written to the proper place of the new stack top container by referencing to the top of `codingPath`.
|
|
fileprivate var storage: _FirestoreEncodingStorage
|
|
/// An array used as a stack to keep track of where in the encoded data tree the encoder is trying to process
|
|
/// at the moment.
|
|
public fileprivate(set) var codingPath: [CodingKey]
|
|
public var userInfo: [CodingUserInfoKey: Any] = [:]
|
|
|
|
init() {
|
|
storage = _FirestoreEncodingStorage()
|
|
codingPath = []
|
|
}
|
|
|
|
/// Returns whether we should allocate new container to store encoded result for the current
|
|
/// codingPath.
|
|
///
|
|
/// `true` if no container has been allocated for this coding path; `false` otherwise.
|
|
fileprivate var shouldAllocateNewContainer: Bool {
|
|
// The encoder starts with storage.count == codingPath.count == 0, which means it is encoding the root
|
|
// object without any container allocated, so when a container is requested, it must be allocated
|
|
// first. When a container is requested again with the same codingPath however, for another field from the root object possibly,
|
|
// because now storage.count == codingPath + 1, we should not allocate new containers because there are
|
|
// already encoded results in the container. This relation between the two stacks is maintained
|
|
// throughout the encoding process.
|
|
return storage.count == codingPath.count
|
|
}
|
|
|
|
// MARK: - Encoder Methods
|
|
|
|
public func container<Key>(keyedBy _: Key.Type) -> KeyedEncodingContainer<Key> {
|
|
// If an existing keyed container was already requested, return that one.
|
|
let topContainer: NSMutableDictionary
|
|
if shouldAllocateNewContainer {
|
|
// We haven't yet pushed a container at this level; do so here.
|
|
topContainer = storage.pushKeyedContainer()
|
|
} else {
|
|
guard let container = storage.containers.last as? NSMutableDictionary else {
|
|
preconditionFailure(
|
|
"Attempt to push new keyed encoding container when already previously encoded at this path."
|
|
)
|
|
}
|
|
|
|
topContainer = container
|
|
}
|
|
|
|
let container = _FirestoreKeyedEncodingContainer<Key>(referencing: self, codingPath: codingPath,
|
|
wrapping: topContainer)
|
|
return KeyedEncodingContainer(container)
|
|
}
|
|
|
|
public func unkeyedContainer() -> UnkeyedEncodingContainer {
|
|
// If an existing unkeyed container was already requested, return that one.
|
|
let topContainer: NSMutableArray
|
|
if shouldAllocateNewContainer {
|
|
// We haven't yet pushed a container at this level; do so here.
|
|
topContainer = storage.pushUnkeyedContainer()
|
|
} else {
|
|
guard let container = storage.containers.last as? NSMutableArray else {
|
|
preconditionFailure(
|
|
"Attempt to push new unkeyed encoding container when already previously encoded at this path."
|
|
)
|
|
}
|
|
|
|
topContainer = container
|
|
}
|
|
|
|
return _FirestoreUnkeyedEncodingContainer(referencing: self, codingPath: codingPath,
|
|
wrapping: topContainer)
|
|
}
|
|
|
|
public func singleValueContainer() -> SingleValueEncodingContainer {
|
|
return self
|
|
}
|
|
}
|
|
|
|
private struct _FirestoreEncodingStorage {
|
|
// MARK: Properties
|
|
|
|
/// The container stack.
|
|
/// Elements may be any one of the plist types (NSNumber, NSString, NSDate, NSArray, NSDictionary).
|
|
fileprivate private(set) var containers: [NSObject] = []
|
|
|
|
// MARK: - Initialization
|
|
|
|
/// Initializes `self` with no containers.
|
|
fileprivate init() {}
|
|
|
|
// MARK: - Modifying the Stack
|
|
|
|
fileprivate var count: Int {
|
|
return containers.count
|
|
}
|
|
|
|
fileprivate mutating func pushKeyedContainer() -> NSMutableDictionary {
|
|
let dictionary = NSMutableDictionary()
|
|
containers.append(dictionary)
|
|
return dictionary
|
|
}
|
|
|
|
fileprivate mutating func pushUnkeyedContainer() -> NSMutableArray {
|
|
let array = NSMutableArray()
|
|
containers.append(array)
|
|
return array
|
|
}
|
|
|
|
fileprivate mutating func push(container: NSObject) {
|
|
containers.append(container)
|
|
}
|
|
|
|
fileprivate mutating func popContainer() -> NSObject {
|
|
precondition(containers.count > 0, "Empty container stack.")
|
|
let ret = containers.popLast()!
|
|
return ret
|
|
}
|
|
}
|
|
|
|
private struct _FirestoreKeyedEncodingContainer<K: CodingKey>: KeyedEncodingContainerProtocol {
|
|
typealias Key = K
|
|
|
|
// MARK: Properties
|
|
|
|
/// A reference to the encoder we're writing to.
|
|
private let encoder: _FirestoreEncoder
|
|
|
|
/// A reference to the container we're writing to.
|
|
private let container: NSMutableDictionary
|
|
|
|
/// The path of coding keys taken to get to this point in encoding.
|
|
public private(set) var codingPath: [CodingKey]
|
|
|
|
// MARK: - Initialization
|
|
|
|
/// Initializes `self` with the given references.
|
|
fileprivate init(referencing encoder: _FirestoreEncoder,
|
|
codingPath: [CodingKey],
|
|
wrapping container: NSMutableDictionary) {
|
|
self.encoder = encoder
|
|
self.codingPath = codingPath
|
|
self.container = container
|
|
}
|
|
|
|
// MARK: - KeyedEncodingContainerProtocol Methods
|
|
|
|
public mutating func encodeNil(forKey key: Key) throws { container[key.stringValue] = NSNull() }
|
|
public mutating func encode(_ value: Bool, forKey key: Key) throws {
|
|
container[key.stringValue] = encoder.box(value)
|
|
}
|
|
|
|
public mutating func encode(_ value: Int, forKey key: Key) throws {
|
|
container[key.stringValue] = encoder.box(value)
|
|
}
|
|
|
|
public mutating func encode(_ value: Int8, forKey key: Key) throws {
|
|
container[key.stringValue] = encoder.box(value)
|
|
}
|
|
|
|
public mutating func encode(_ value: Int16, forKey key: Key) throws {
|
|
container[key.stringValue] = encoder.box(value)
|
|
}
|
|
|
|
public mutating func encode(_ value: Int32, forKey key: Key) throws {
|
|
container[key.stringValue] = encoder.box(value)
|
|
}
|
|
|
|
public mutating func encode(_ value: Int64, forKey key: Key) throws {
|
|
container[key.stringValue] = encoder.box(value)
|
|
}
|
|
|
|
public mutating func encode(_ value: UInt, forKey key: Key) throws {
|
|
container[key.stringValue] = encoder.box(value)
|
|
}
|
|
|
|
public mutating func encode(_ value: UInt8, forKey key: Key) throws {
|
|
container[key.stringValue] = encoder.box(value)
|
|
}
|
|
|
|
public mutating func encode(_ value: UInt16, forKey key: Key) throws {
|
|
container[key.stringValue] = encoder.box(value)
|
|
}
|
|
|
|
public mutating func encode(_ value: UInt32, forKey key: Key) throws {
|
|
container[key.stringValue] = encoder.box(value)
|
|
}
|
|
|
|
public mutating func encode(_ value: UInt64, forKey key: Key) throws {
|
|
container[key.stringValue] = encoder.box(value)
|
|
}
|
|
|
|
public mutating func encode(_ value: String, forKey key: Key) throws {
|
|
container[key.stringValue] = encoder.box(value)
|
|
}
|
|
|
|
public mutating func encode(_ value: Float, forKey key: Key) throws {
|
|
container[key.stringValue] = encoder.box(value)
|
|
}
|
|
|
|
public mutating func encode(_ value: Double, forKey key: Key) throws {
|
|
container[key.stringValue] = encoder.box(value)
|
|
}
|
|
|
|
public mutating func encode<T: Encodable>(_ value: T, forKey key: Key) throws {
|
|
#if compiler(>=5.1)
|
|
// `DocumentID`-annotated fields are ignored during encoding.
|
|
if T.self is DocumentIDProtocol.Type {
|
|
return
|
|
}
|
|
#endif // compiler(>=5.1)
|
|
|
|
encoder.codingPath.append(key)
|
|
defer {
|
|
encoder.codingPath.removeLast()
|
|
}
|
|
container[key.stringValue] = try encoder.box(value)
|
|
}
|
|
|
|
public mutating func nestedContainer<NestedKey>(keyedBy _: NestedKey.Type,
|
|
forKey key: Key)
|
|
-> KeyedEncodingContainer<NestedKey> {
|
|
let dictionary = NSMutableDictionary()
|
|
self.container[key.stringValue] = dictionary
|
|
|
|
codingPath.append(key)
|
|
defer {
|
|
codingPath.removeLast()
|
|
}
|
|
|
|
let container = _FirestoreKeyedEncodingContainer<NestedKey>(referencing: encoder,
|
|
codingPath: codingPath,
|
|
wrapping: dictionary)
|
|
return KeyedEncodingContainer(container)
|
|
}
|
|
|
|
public mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
|
|
let array = NSMutableArray()
|
|
container[key.stringValue] = array
|
|
|
|
codingPath.append(key)
|
|
defer {
|
|
codingPath.removeLast()
|
|
}
|
|
return _FirestoreUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath,
|
|
wrapping: array)
|
|
}
|
|
|
|
public mutating func superEncoder() -> Encoder {
|
|
return _FirestoreReferencingEncoder(referencing: encoder, at: _FirestoreKey.super,
|
|
wrapping: container)
|
|
}
|
|
|
|
public mutating func superEncoder(forKey key: Key) -> Encoder {
|
|
return _FirestoreReferencingEncoder(referencing: encoder, at: key, wrapping: container)
|
|
}
|
|
}
|
|
|
|
private struct _FirestoreUnkeyedEncodingContainer: UnkeyedEncodingContainer {
|
|
// MARK: Properties
|
|
|
|
/// A reference to the encoder we're writing to.
|
|
private let encoder: _FirestoreEncoder
|
|
|
|
/// A reference to the container we're writing to.
|
|
private let container: NSMutableArray
|
|
|
|
/// The path of coding keys taken to get to this point in encoding.
|
|
public private(set) var codingPath: [CodingKey]
|
|
|
|
/// The number of elements encoded into the container.
|
|
public var count: Int {
|
|
return container.count
|
|
}
|
|
|
|
// MARK: - Initialization
|
|
|
|
/// Initializes `self` with the given references.
|
|
fileprivate init(referencing encoder: _FirestoreEncoder,
|
|
codingPath: [CodingKey],
|
|
wrapping container: NSMutableArray) {
|
|
self.encoder = encoder
|
|
self.codingPath = codingPath
|
|
self.container = container
|
|
}
|
|
|
|
// MARK: - UnkeyedEncodingContainer Methods
|
|
|
|
public mutating func encodeNil() throws { container.add(NSNull()) }
|
|
public mutating func encode(_ value: Bool) throws { container.add(encoder.box(value)) }
|
|
public mutating func encode(_ value: Int) throws { container.add(encoder.box(value)) }
|
|
public mutating func encode(_ value: Int8) throws { container.add(encoder.box(value)) }
|
|
public mutating func encode(_ value: Int16) throws { container.add(encoder.box(value)) }
|
|
public mutating func encode(_ value: Int32) throws { container.add(encoder.box(value)) }
|
|
public mutating func encode(_ value: Int64) throws { container.add(encoder.box(value)) }
|
|
public mutating func encode(_ value: UInt) throws { container.add(encoder.box(value)) }
|
|
public mutating func encode(_ value: UInt8) throws { container.add(encoder.box(value)) }
|
|
public mutating func encode(_ value: UInt16) throws { container.add(encoder.box(value)) }
|
|
public mutating func encode(_ value: UInt32) throws { container.add(encoder.box(value)) }
|
|
public mutating func encode(_ value: UInt64) throws { container.add(encoder.box(value)) }
|
|
public mutating func encode(_ value: Float) throws { container.add(encoder.box(value)) }
|
|
public mutating func encode(_ value: Double) throws { container.add(encoder.box(value)) }
|
|
public mutating func encode(_ value: String) throws { container.add(encoder.box(value)) }
|
|
|
|
public mutating func encode<T: Encodable>(_ value: T) throws {
|
|
encoder.codingPath.append(_FirestoreKey(index: count))
|
|
defer { encoder.codingPath.removeLast()
|
|
}
|
|
container.add(try encoder.box(value))
|
|
}
|
|
|
|
public mutating func nestedContainer<NestedKey>(keyedBy _: NestedKey
|
|
.Type) -> KeyedEncodingContainer<NestedKey> {
|
|
codingPath.append(_FirestoreKey(index: count))
|
|
defer {
|
|
self.codingPath.removeLast()
|
|
}
|
|
|
|
let dictionary = NSMutableDictionary()
|
|
self.container.add(dictionary)
|
|
|
|
let container = _FirestoreKeyedEncodingContainer<NestedKey>(referencing: encoder,
|
|
codingPath: codingPath,
|
|
wrapping: dictionary)
|
|
return KeyedEncodingContainer(container)
|
|
}
|
|
|
|
public mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
|
|
codingPath.append(_FirestoreKey(index: count))
|
|
defer {
|
|
self.codingPath.removeLast()
|
|
}
|
|
|
|
let array = NSMutableArray()
|
|
container.add(array)
|
|
return _FirestoreUnkeyedEncodingContainer(referencing: encoder, codingPath: codingPath,
|
|
wrapping: array)
|
|
}
|
|
|
|
public mutating func superEncoder() -> Encoder {
|
|
return _FirestoreReferencingEncoder(referencing: encoder, at: container.count,
|
|
wrapping: container)
|
|
}
|
|
}
|
|
|
|
struct _FirestoreKey: CodingKey {
|
|
public var stringValue: String
|
|
public var intValue: Int?
|
|
|
|
public init?(stringValue: String) {
|
|
self.stringValue = stringValue
|
|
intValue = nil
|
|
}
|
|
|
|
public init?(intValue: Int) {
|
|
stringValue = "\(intValue)"
|
|
self.intValue = intValue
|
|
}
|
|
|
|
init(index: Int) {
|
|
stringValue = "Index \(index)"
|
|
intValue = index
|
|
}
|
|
|
|
static let `super` = _FirestoreKey(stringValue: "super")!
|
|
}
|
|
|
|
extension _FirestoreEncoder {
|
|
/// Returns the given value boxed in a container appropriate for pushing onto the container stack.
|
|
fileprivate func box(_ value: Bool) -> NSObject { return NSNumber(value: value) }
|
|
fileprivate func box(_ value: Int) -> NSObject { return NSNumber(value: value) }
|
|
fileprivate func box(_ value: Int8) -> NSObject { return NSNumber(value: value) }
|
|
fileprivate func box(_ value: Int16) -> NSObject { return NSNumber(value: value) }
|
|
fileprivate func box(_ value: Int32) -> NSObject { return NSNumber(value: value) }
|
|
fileprivate func box(_ value: Int64) -> NSObject { return NSNumber(value: value) }
|
|
fileprivate func box(_ value: UInt) -> NSObject { return NSNumber(value: value) }
|
|
fileprivate func box(_ value: UInt8) -> NSObject { return NSNumber(value: value) }
|
|
fileprivate func box(_ value: UInt16) -> NSObject { return NSNumber(value: value) }
|
|
fileprivate func box(_ value: UInt32) -> NSObject { return NSNumber(value: value) }
|
|
fileprivate func box(_ value: UInt64) -> NSObject { return NSNumber(value: value) }
|
|
fileprivate func box(_ value: Float) -> NSObject { return NSNumber(value: value) }
|
|
fileprivate func box(_ value: Double) -> NSObject { return NSNumber(value: value) }
|
|
fileprivate func box(_ value: String) -> NSObject { return NSString(string: value) }
|
|
|
|
fileprivate func box<T: Encodable>(_ value: T) throws -> NSObject {
|
|
return try box_(value) ?? NSDictionary()
|
|
}
|
|
|
|
func box_<T: Encodable>(_ value: T) throws -> NSObject? {
|
|
if T.self == Date.self || T.self == NSDate.self {
|
|
return (value as! NSDate)
|
|
} else if T.self == Data.self || T.self == NSData.self {
|
|
return (value as! NSData)
|
|
} else if T.self == URL.self || T.self == NSURL.self {
|
|
return box((value as! URL).absoluteString)
|
|
} else if T.self == Decimal.self || T.self == NSDecimalNumber.self {
|
|
return (value as! NSDecimalNumber)
|
|
} else if isFirestorePassthroughType(value) {
|
|
// This is a native Firestore types that we don't need to encode.
|
|
return (value as! NSObject)
|
|
}
|
|
|
|
// The value should request a container from the _FirestoreEncoder.
|
|
let depth = storage.count
|
|
do {
|
|
try value.encode(to: self)
|
|
} catch {
|
|
// If the value pushed a container before throwing, pop it back off to restore state.
|
|
if storage.count > depth {
|
|
_ = storage.popContainer()
|
|
}
|
|
|
|
throw error
|
|
}
|
|
|
|
// The top container should be a new container.
|
|
guard storage.count > depth else {
|
|
return nil
|
|
}
|
|
|
|
return storage.popContainer()
|
|
}
|
|
}
|
|
|
|
extension _FirestoreEncoder: SingleValueEncodingContainer {
|
|
// MARK: - SingleValueEncodingContainer Methods
|
|
|
|
private func assertCanEncodeNewValue() {
|
|
precondition(shouldAllocateNewContainer,
|
|
"Attempt to encode value through single value container when previously value already encoded.")
|
|
}
|
|
|
|
public func encodeNil() throws {
|
|
assertCanEncodeNewValue()
|
|
storage.push(container: NSNull())
|
|
}
|
|
|
|
public func encode(_ value: Bool) throws {
|
|
assertCanEncodeNewValue()
|
|
storage.push(container: box(value))
|
|
}
|
|
|
|
public func encode(_ value: Int) throws {
|
|
assertCanEncodeNewValue()
|
|
storage.push(container: box(value))
|
|
}
|
|
|
|
public func encode(_ value: Int8) throws {
|
|
assertCanEncodeNewValue()
|
|
storage.push(container: box(value))
|
|
}
|
|
|
|
public func encode(_ value: Int16) throws {
|
|
assertCanEncodeNewValue()
|
|
storage.push(container: box(value))
|
|
}
|
|
|
|
public func encode(_ value: Int32) throws {
|
|
assertCanEncodeNewValue()
|
|
storage.push(container: box(value))
|
|
}
|
|
|
|
public func encode(_ value: Int64) throws {
|
|
assertCanEncodeNewValue()
|
|
storage.push(container: box(value))
|
|
}
|
|
|
|
public func encode(_ value: UInt) throws {
|
|
assertCanEncodeNewValue()
|
|
storage.push(container: box(value))
|
|
}
|
|
|
|
public func encode(_ value: UInt8) throws {
|
|
assertCanEncodeNewValue()
|
|
storage.push(container: box(value))
|
|
}
|
|
|
|
public func encode(_ value: UInt16) throws {
|
|
assertCanEncodeNewValue()
|
|
storage.push(container: box(value))
|
|
}
|
|
|
|
public func encode(_ value: UInt32) throws {
|
|
assertCanEncodeNewValue()
|
|
storage.push(container: box(value))
|
|
}
|
|
|
|
public func encode(_ value: UInt64) throws {
|
|
assertCanEncodeNewValue()
|
|
storage.push(container: box(value))
|
|
}
|
|
|
|
public func encode(_ value: String) throws {
|
|
assertCanEncodeNewValue()
|
|
storage.push(container: box(value))
|
|
}
|
|
|
|
public func encode(_ value: Float) throws {
|
|
assertCanEncodeNewValue()
|
|
storage.push(container: box(value))
|
|
}
|
|
|
|
public func encode(_ value: Double) throws {
|
|
assertCanEncodeNewValue()
|
|
storage.push(container: box(value))
|
|
}
|
|
|
|
public func encode<T: Encodable>(_ value: T) throws {
|
|
assertCanEncodeNewValue()
|
|
try storage.push(container: box(value))
|
|
}
|
|
}
|
|
|
|
/// Special subclass of `_FirestoreEncoder` used by `superEncoder`.
|
|
/// It inherits the codingPath from the referencing `_FirestoreEncoder` but uses its own
|
|
/// storage. The encoded result will be written back to the referencing encoder's storage
|
|
/// when it is `deinit`-ed.
|
|
private class _FirestoreReferencingEncoder: _FirestoreEncoder {
|
|
// MARK: Reference types.
|
|
|
|
/// The type of container we're referencing, and where to write the encoded result to.
|
|
private enum Reference {
|
|
/// Referencing a specific index in an array container.
|
|
case array(NSMutableArray, Int)
|
|
|
|
/// Referencing a specific key in a dictionary container.
|
|
case dictionary(NSMutableDictionary, String)
|
|
}
|
|
|
|
// MARK: - Properties
|
|
|
|
/// The encoder we're referencing.
|
|
private let encoder: _FirestoreEncoder
|
|
|
|
/// The container reference itself.
|
|
private let reference: Reference
|
|
|
|
// MARK: - Initialization
|
|
|
|
/// Initializes `self` by referencing the given array container in the given encoder.
|
|
fileprivate init(referencing encoder: _FirestoreEncoder,
|
|
at index: Int,
|
|
wrapping array: NSMutableArray) {
|
|
self.encoder = encoder
|
|
reference = .array(array, index)
|
|
super.init()
|
|
codingPath = encoder.codingPath
|
|
|
|
codingPath.append(_FirestoreKey(index: index))
|
|
}
|
|
|
|
/// Initializes `self` by referencing the given dictionary container in the given encoder.
|
|
fileprivate init(referencing encoder: _FirestoreEncoder,
|
|
at key: CodingKey,
|
|
wrapping dictionary: NSMutableDictionary) {
|
|
self.encoder = encoder
|
|
reference = .dictionary(dictionary, key.stringValue)
|
|
super.init()
|
|
codingPath = encoder.codingPath
|
|
codingPath.append(key)
|
|
}
|
|
|
|
// MARK: - Coding Path Operations
|
|
|
|
override fileprivate var shouldAllocateNewContainer: Bool {
|
|
// With a regular encoder, the storage and coding path grow together.
|
|
// A referencing encoder, however, inherits its parents coding path, as well as the key it was created for.
|
|
// We have to take this into account.
|
|
return storage.count == codingPath.count - encoder.codingPath.count - 1
|
|
}
|
|
|
|
// MARK: - Deinitialization
|
|
|
|
// Finalizes `self` by writing the contents of our storage to the referenced encoder's storage.
|
|
deinit {
|
|
let value: Any
|
|
switch storage.count {
|
|
case 0: value = NSDictionary()
|
|
case 1: value = self.storage.popContainer()
|
|
default: fatalError("Referencing encoder deallocated with multiple containers on stack.")
|
|
}
|
|
|
|
switch self.reference {
|
|
case let .array(array, index):
|
|
array.insert(value, at: index)
|
|
|
|
case let .dictionary(dictionary, key):
|
|
dictionary[NSString(string: key)] = value
|
|
}
|
|
}
|
|
}
|