Skip to content

Commit

Permalink
Fix retain cycle(s) in encoder implementation (#220)
Browse files Browse the repository at this point in the history
  • Loading branch information
petrpavlik authored Nov 23, 2022
1 parent bf1c279 commit 79fbcfb
Showing 1 changed file with 56 additions and 13 deletions.
69 changes: 56 additions & 13 deletions Sources/Leaf/LeafEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,9 @@ extension LeafEncoder {
}

private final class KeyedContainerImpl<Key>: KeyedEncodingContainerProtocol, LeafEncodingResolvable where Key: CodingKey {
private let encoder: EncoderImpl
private weak var encoder: EncoderImpl!
private var data: [String: LeafEncodingResolvable] = [:]
private var nestedEncoderCaptures = [AnyObject]()

/// See ``LeafEncodingResolvable/resolvedData``.
var resolvedData: LeafData? { .dictionary(self.data.compactMapValues { $0.resolvedData }) }
Expand All @@ -158,35 +159,57 @@ extension LeafEncoder {

/// See ``KeyedEncodingContainerProtocol/nestedContainer(keyedBy:forKey:)``.
func nestedContainer<NestedKey: CodingKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
guard let encoder = encoder else {
fatalError("Encoder deallocated")
}

let nestedEncoder = EncoderImpl(from: encoder, withKey: key)
nestedEncoderCaptures.append(nestedEncoder)

/// Use a subencoder to create a nested container so the coding paths are correctly maintained.
/// Save the subcontainer in our data so it can be resolved later before returning it.
.init(self.insert(
EncoderImpl(from: self.encoder, withKey: key).rawContainer(keyedBy: NestedKey.self),
return .init(self.insert(
nestedEncoder.rawContainer(keyedBy: NestedKey.self),
forKey: key,
as: KeyedContainerImpl<NestedKey>.self
))
}

/// See ``KeyedEncodingContainerProtocol/nestedUnkeyedContainer(forKey:)``.
func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
self.insert(
EncoderImpl(from: self.encoder, withKey: key).unkeyedContainer() as! UnkeyedContainerImpl,
guard let encoder = encoder else {
fatalError("Encoder deallocated")
}

let nestedEncoder = EncoderImpl(from: encoder, withKey: key)
nestedEncoderCaptures.append(nestedEncoder)

return self.insert(
nestedEncoder.unkeyedContainer() as! UnkeyedContainerImpl,
forKey: key
)
}

/// A super encoder is, in fact, just a subdecoder with delusions of grandeur and some rather haughty
/// pretensions. (It's mostly Codable's fault anyway.)
func superEncoder() -> Encoder {
self.insert(
EncoderImpl(from: self.encoder, withKey: GenericCodingKey(stringValue: "super")),
guard let encoder = encoder else {
fatalError("Encoder deallocated")
}

return self.insert(
EncoderImpl(from: encoder, withKey: GenericCodingKey(stringValue: "super")),
forKey: GenericCodingKey(stringValue: "super")
)
}

/// See ``KeyedEncodingContainerProtocol/superEncoder(forKey:)``.
func superEncoder(forKey key: Key) -> Encoder {
self.insert(EncoderImpl(from: self.encoder, withKey: key), forKey: key)
guard let encoder = encoder else {
fatalError("Encoder deallocated")
}

return self.insert(EncoderImpl(from: encoder, withKey: key), forKey: key)
}

/// Helper for the encoding methods.
Expand All @@ -197,9 +220,11 @@ extension LeafEncoder {
}

private final class UnkeyedContainerImpl: UnkeyedEncodingContainer, LeafEncodingResolvable {
private let encoder: EncoderImpl
private weak var encoder: EncoderImpl!
private var data: [LeafEncodingResolvable] = []

private var nestedEncoderCaptures = [AnyObject]()

/// See ``LeafEncodingResolvable/resolvedData``.
var resolvedData: LeafData? { .array(data.compactMap(\.resolvedData)) }

Expand All @@ -224,20 +249,38 @@ extension LeafEncoder {

/// See ``UnkeyedEncodingContainer/nestedContainer(keyedBy:)``.
func nestedContainer<NestedKey: CodingKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> {
.init(self.add(
EncoderImpl(from: self.encoder, withKey: self.nextCodingKey).rawContainer(keyedBy: NestedKey.self),
guard let encoder = encoder else {
fatalError("Encoder deallocated")
}

let nestedEncoder = EncoderImpl(from: encoder, withKey: self.nextCodingKey)
nestedEncoderCaptures.append(nestedEncoder)

return .init(self.add(
nestedEncoder.rawContainer(keyedBy: NestedKey.self),
as: KeyedContainerImpl<NestedKey>.self
))
}

/// See ``UnkeyedEncodingContainer/nestedUnkeyedContainer()``.
func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
self.add(EncoderImpl(from: self.encoder, withKey: self.nextCodingKey).unkeyedContainer() as! UnkeyedContainerImpl)
guard let encoder = encoder else {
fatalError("Encoder deallocated")
}

let nestedEncoder = EncoderImpl(from: encoder, withKey: self.nextCodingKey)
nestedEncoderCaptures.append(nestedEncoder)

return self.add(nestedEncoder.unkeyedContainer() as! UnkeyedContainerImpl)
}

/// See ``UnkeyedEncodingContainer/superEncoder()``.
func superEncoder() -> Encoder {
self.add(EncoderImpl(from: self.encoder, withKey: self.nextCodingKey))
guard let encoder = encoder else {
fatalError("Encoder deallocated")
}

return self.add(EncoderImpl(from: encoder, withKey: self.nextCodingKey))
}

/// A `CodingKey` corresponding to the index that will be given to the next value added to the array.
Expand Down

0 comments on commit 79fbcfb

Please sign in to comment.