I need a representation of generic objects interconnected in a tree like manner. This tree and their objects should have the following characteristics:
Codable protocol
As an example at the end of this question I created a playground that fulfills all requirements but one: decoding the tree from a JSON
A tree consists of nodes, in this case TreePartNodes, which are implementing the TreePartNodeBase protocol.
The tree in this example is an array of AnyTreePartNodes that are also implementing the TreePartNodeBase protocol and wrapping an object implementing the TreePartNodeBase protocol that should be a generic TreePartNode (TreePartNode<Trunk>, TreePartNode<Branch> or TreePartNode<Apple>).
A TreePartNode has the property treePart of type AnyTreePart. AnyTreePart (similar to AnyTreePartNode) wraps an object that implements the TreePart protocol (Trunk, Branch or Apple).
All these classes are implementing Codable and Equatable.
import Foundation
///////////// TreePart -> implemented by Trunk, Branch, Apple and AnyTreePart
protocol TreePart: Codable {
var name: String { get set }
func isEqualTo( _ other: TreePart ) -> Bool
func asEquatable() -> AnyTreePart
}
extension TreePart where Self: Equatable {
func isEqualTo( _ other: TreePart ) -> Bool {
guard let otherTreePart = other as? Self else { return false }
return self == otherTreePart
}
func asEquatable() -> AnyTreePart {
return AnyTreePart( self )
}
}
///////////// AnyTreePart -> wrapper for Trunk, Branch and Apple
class AnyTreePart: TreePart, Codable {
var wrappedTreePart: TreePart
var name: String {
get {
return self.wrappedTreePart.name
}
set {
self.wrappedTreePart.name = newValue
}
}
init( _ treePart: TreePart ) {
self.wrappedTreePart = treePart
}
// MARK: Codable
enum CodingKeys: String, CodingKey {
case trunk,
branch,
apple
}
required convenience init( from decoder: Decoder ) throws {
let container = try decoder.container( keyedBy: CodingKeys.self )
var treePart: TreePart?
if let trunk = try container.decodeIfPresent( Trunk.self, forKey: .trunk ) {
treePart = trunk
}
if let branch = try container.decodeIfPresent( Branch.self, forKey: .branch ) {
treePart = branch
}
if let apple = try container.decodeIfPresent( Apple.self, forKey: .apple ) {
treePart = apple
}
guard let foundTreePart = treePart else {
let context = DecodingError.Context( codingPath: [CodingKeys.trunk, CodingKeys.branch, CodingKeys.apple], debugDescription: "Could not find the treePart key" )
throw DecodingError.keyNotFound( CodingKeys.trunk, context )
}
self.init( foundTreePart )
}
func encode( to encoder: Encoder ) throws {
var container = encoder.container( keyedBy: CodingKeys.self )
switch self.wrappedTreePart {
case let trunk as Trunk:
try container.encode( trunk, forKey: .trunk )
case let branch as Branch:
try container.encode( branch, forKey: .branch )
case let apple as Apple:
try container.encode( apple, forKey: .apple )
default:
fatalError( "Encoding error: No encoding implementation for \( type( of: self.wrappedTreePart ) )" )
}
}
}
extension AnyTreePart: Equatable {
static func ==( lhs: AnyTreePart, rhs: AnyTreePart ) -> Bool {
return lhs.wrappedTreePart.isEqualTo( rhs.wrappedTreePart )
}
}
///////////// TreePartNodeBase -> implemented by TreePartNode<T: TreePart> and AnyTreePartNode
protocol TreePartNodeBase: class {
var treePart: AnyTreePart { get set }
var uuid: UUID { get }
var parent: AnyTreePartNode? { get set }
var weakChildren: NSPointerArray { get }
func isEqualTo( _ other: TreePartNodeBase ) -> Bool
func asEquatable() -> AnyTreePartNode
}
extension TreePartNodeBase where Self: Equatable {
func isEqualTo( _ other: TreePartNodeBase ) -> Bool {
guard let otherTreePartNode = other as? Self else { return false }
return self == otherTreePartNode &&
self.treePart == other.treePart &&
self.uuid == other.uuid &&
self.parent == other.parent &&
self.children == other.children
}
func asEquatable() -> AnyTreePartNode {
return AnyTreePartNode( self )
}
}
extension TreePartNodeBase {
var children: [AnyTreePartNode] {
guard let allNodes = self.weakChildren.allObjects as? [AnyTreePartNode] else {
fatalError( "The children nodes are not of type \( type( of: AnyTreePartNode.self ) )" )
}
return allNodes
}
}
///////////// AnyTreePartNode -> wrapper of TreePartNode<T: TreePart>
class AnyTreePartNode: TreePartNodeBase, Codable {
unowned var wrappedTreePartNode: TreePartNodeBase
var treePart: AnyTreePart {
get {
return self.wrappedTreePartNode.treePart
}
set {
self.wrappedTreePartNode.treePart = newValue
}
}
var uuid: UUID {
return self.wrappedTreePartNode.uuid
}
/// The parent node
weak var parent: AnyTreePartNode? {
get {
return self.wrappedTreePartNode.parent
}
set {
self.wrappedTreePartNode.parent = newValue
}
}
/// The weak references to the children of this node
var weakChildren: NSPointerArray {
return self.wrappedTreePartNode.weakChildren
}
init( _ treePartNode: TreePartNodeBase ) {
self.wrappedTreePartNode = treePartNode
}
// MARK: Codable
enum CodingKeys: String, CodingKey {
case trunkNode,
branchNode,
appleNode
}
required convenience init( from decoder: Decoder ) throws {
let container = try decoder.container( keyedBy: CodingKeys.self )
// even if an empty Trunk is created, the decoder crashes
self.init( TreePartNode<Trunk>( Trunk() ) )
// This attempt of decoding possible nodes doesn't work
/*
if let trunkNode: TreePartNode<Trunk> = try container
.decodeIfPresent( TreePartNode<Trunk>.self, forKey: .trunkNode ) {
self.init( trunkNode )
} else if let branchNode: TreePartNode<Branch> = try container
.decodeIfPresent( TreePartNode<Branch>.self, forKey: .branchNode ) {
self.init( branchNode )
} else if let appleNode: TreePartNode<Apple> = try cont«ainer
.decodeIfPresent( TreePartNode<Apple>.self, forKey: .appleNode ) {
self.init( appleNode )
} else {
let context = DecodingError.Context( codingPath: [CodingKeys.trunkNode,
CodingKeys.branchNode,
CodingKeys.appleNode],
debugDescription: "Could not find the treePart node key" )
throw DecodingError.keyNotFound( CodingKeys.trunkNode, context )
}
*/
// TODO recreating the connections between the nodes should happen after all objects are decoded and will be done based on the UUIDs
}
func encode( to encoder: Encoder ) throws {
var container = encoder.container( keyedBy: CodingKeys.self )
switch self.wrappedTreePartNode {
case let trunkNode as TreePartNode<Trunk>:
try container.encode( trunkNode, forKey: .trunkNode )
case let branchNode as TreePartNode<Branch>:
try container.encode( branchNode, forKey: .branchNode )
case let appleNode as TreePartNode<Apple>:
try container.encode( appleNode, forKey: .appleNode )
default:
fatalError( "Encoding error: No encoding implementation for \( type( of: self.wrappedTreePartNode ) )" )
}
}
}
extension AnyTreePartNode: Equatable {
static func ==( lhs: AnyTreePartNode, rhs: AnyTreePartNode ) -> Bool {
return lhs.wrappedTreePartNode.isEqualTo( rhs.wrappedTreePartNode )
}
}
// enables printing of the wrapped tree part and its child elements
extension AnyTreePartNode: CustomStringConvertible {
var description: String {
var text = "\( type( of: self.wrappedTreePartNode.treePart.wrappedTreePart ))"
if !self.children.isEmpty {
text += " { " + self.children.map { $0.description }.joined( separator: ", " ) + " }"
}
return text
}
}
///////////// TreeParts (Trunk, Branch and Apple)
class Trunk: TreePart, Codable, Equatable {
var name: String
var color: String
init( name: String = "trunk",
color: String = "#CCC" ) {
self.name = name
self.color = color
}
static func ==(lhs: Trunk, rhs: Trunk) -> Bool {
return lhs.name == rhs.name &&
lhs.color == rhs.color
}
}
class Branch: TreePart, Codable, Equatable {
var name: String
var length: Int
init( name: String = "branch",
length: Int = 4 ) {
self.name = name
self.length = length
}
static func ==(lhs: Branch, rhs: Branch) -> Bool {
return lhs.name == rhs.name &&
lhs.length == rhs.length
}
}
class Apple: TreePart, Codable, Equatable {
var name: String
var size: Int
init( name: String = "apple",
size: Int = 2 ) {
self.name = name
self.size = size
}
static func ==(lhs: Apple, rhs: Apple) -> Bool {
return lhs.name == rhs.name &&
lhs.size == rhs.size
}
}
///////////// TreePartNode -> The node in the tree that contains the TreePart
class TreePartNode<T: TreePart>: TreePartNodeBase, Codable {
var equatableSelf: AnyTreePartNode!
var uuid: UUID
var treePart: AnyTreePart
var weakChildren = NSPointerArray.weakObjects()
private var parentUuid : UUID?
private var childrenUuids : [UUID]?
weak var parent: AnyTreePartNode? {
willSet {
if newValue == nil {
// unrelated code
// ... removes the references to this object in the parent node, if it exists
}
}
}
init( _ treePart: AnyTreePart,
uuid: UUID = UUID() ) {
self.treePart = treePart
self.uuid = uuid
self.equatableSelf = self.asEquatable()
}
convenience init( _ treePart: T,
uuid: UUID = UUID() ) {
self.init( treePart.asEquatable(),
uuid: uuid )
}
init( _ treePart: AnyTreePart,
uuid: UUID,
parentUuid: UUID?,
childrenUuids: [UUID]?) {
self.treePart = treePart
self.uuid = uuid
self.parentUuid = parentUuid
self.childrenUuids = childrenUuids
self.equatableSelf = self.asEquatable()
}
private func add( child: AnyTreePartNode ) {
child.parent = self.equatableSelf
self.weakChildren.addObject( child )
}
private func set( parent: AnyTreePartNode ) {
self.parent = parent
parent.weakChildren.addObject( self.equatableSelf )
}
// MARK: Codable
enum CodingKeys: String, CodingKey {
case treePart,
uuid,
parent,
children,
parentPort
}
required convenience init( from decoder: Decoder ) throws {
let container = try decoder.container( keyedBy: CodingKeys.self )
// non-optional values
let uuid: UUID = try container.decode( UUID.self, forKey: .uuid )
let treePart: AnyTreePart = try container.decode( AnyTreePart.self, forKey: .treePart )
// optional values
let childrenUuids: [UUID]? = try container.decodeIfPresent( [UUID].self, forKey: .children )
let parentUuid: UUID? = try container.decodeIfPresent( UUID.self, forKey: .parent )
self.init( treePart,
uuid: uuid,
parentUuid: parentUuid,
childrenUuids: childrenUuids)
}
func encode( to encoder: Encoder ) throws {
var container = encoder.container( keyedBy: CodingKeys.self )
// non-optional values
try container.encode( self.treePart, forKey: .treePart )
try container.encode( self.uuid, forKey: .uuid )
// optional values
if !self.children.isEmpty {
try container.encode( self.children.map { $0.uuid }, forKey: .children )
}
try container.encodeIfPresent( self.parent?.uuid, forKey: .parent )
}
}
extension TreePartNode: Equatable {
static func ==( lhs: TreePartNode, rhs: TreePartNode ) -> Bool {
return lhs.treePart == rhs.treePart &&
lhs.parent == rhs.parent &&
lhs.children == rhs.children
}
}
// enables printing of the wrapped tree part and its child elements
extension TreePartNode: CustomStringConvertible {
var description: String {
var text = "\( type( of: self.treePart.wrappedTreePart ))"
if !self.children.isEmpty {
text += " { " + self.children.map { $0.description }.joined( separator: ", " ) + " }"
}
return text
}
}
// MARK: functions for adding connections to other TreeParts for each specific TreePart type
extension TreePartNode where T: Trunk {
func add( child branch: TreePartNode<Branch> ) {
self.add( child: branch.equatableSelf )
}
}
extension TreePartNode where T: Branch {
func add( child apple: TreePartNode<Apple> ) {
self.add( child: apple.equatableSelf )
}
func add( child branch: TreePartNode<Branch> ) {
self.add( child: branch.equatableSelf )
}
func set( parent branch: TreePartNode<Branch> ) {
self.set( parent: branch.equatableSelf )
}
func set( parent trunk: TreePartNode<Trunk> ) {
self.set( parent: trunk.equatableSelf )
}
}
extension TreePartNode where T: Apple {
func set( parent branch: TreePartNode<Branch> ) {
self.set( parent: branch.equatableSelf )
}
}
////////////// Helper
extension NSPointerArray {
func addObject( _ object: AnyObject? ) {
guard let strongObject = object else { return }
let pointer = Unmanaged.passUnretained( strongObject ).toOpaque()
self.addPointer( pointer )
}
}
////////////// Test (The actual usage of the implementation above)
let trunk = Trunk()
let branch1 = Branch()
let branch2 = Branch()
let branch3 = Branch()
let apple1 = Apple()
let apple2 = Apple()
let trunkNode = TreePartNode<Trunk>( trunk )
let branchNode1 = TreePartNode<Branch>( branch1 )
let branchNode2 = TreePartNode<Branch>( branch2 )
let branchNode3 = TreePartNode<Branch>( branch3 )
let appleNode1 = TreePartNode<Apple>( apple1 )
let appleNode2 = TreePartNode<Apple>( apple2 )
trunkNode.add( child: branchNode1 )
trunkNode.add( child: branchNode2 )
branchNode2.add( child: branchNode3 )
branchNode1.add( child: appleNode1 )
branchNode3.add( child: appleNode2 )
let tree = [trunkNode.equatableSelf,
branchNode1.equatableSelf,
branchNode2.equatableSelf,
branchNode3.equatableSelf,
appleNode1.equatableSelf,
appleNode2.equatableSelf]
print( "expected result when printing the decoded trunk node: \(trunkNode)" )
let encoder = JSONEncoder()
let decoder = JSONDecoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
// This is how the encoded tree looks like
let jsonTree = """
[
{
"trunkNode" : {
"children" : [
"399B35A7-3307-4EF6-8B4C-1B83A8F734CD",
"60582654-13B9-40D0-8275-3C6649614069"
],
"treePart" : {
"trunk" : {
"color" : "#CCC",
"name" : "trunk"
}
},
"uuid" : "55748AEB-271E-4560-9EE8-F00C670C8896"
}
},
{
"branchNode" : {
"children" : [
"0349C0DF-FE58-4D8E-AA72-7466749EB1D6"
],
"parent" : "55748AEB-271E-4560-9EE8-F00C670C8896",
"treePart" : {
"branch" : {
"length" : 4,
"name" : "branch"
}
},
"uuid" : "399B35A7-3307-4EF6-8B4C-1B83A8F734CD"
}
},
{
"branchNode" : {
"children" : [
"6DB14BD5-3E4A-40C4-8EBF-FBD3CC6050C7"
],
"parent" : "55748AEB-271E-4560-9EE8-F00C670C8896",
"treePart" : {
"branch" : {
"length" : 4,
"name" : "branch"
}
},
"uuid" : "60582654-13B9-40D0-8275-3C6649614069"
}
},
{
"branchNode" : {
"children" : [
"9FCCDBF6-27A7-4E21-8681-5F3E63330504"
],
"parent" : "60582654-13B9-40D0-8275-3C6649614069",
"treePart" : {
"branch" : {
"length" : 4,
"name" : "branch"
}
},
"uuid" : "6DB14BD5-3E4A-40C4-8EBF-FBD3CC6050C7"
}
},
{
"appleNode" : {
"parent" : "399B35A7-3307-4EF6-8B4C-1B83A8F734CD",
"treePart" : {
"apple" : {
"name" : "apple",
"size" : 2
}
},
"uuid" : "0349C0DF-FE58-4D8E-AA72-7466749EB1D6"
}
},
{
"appleNode" : {
"parent" : "6DB14BD5-3E4A-40C4-8EBF-FBD3CC6050C7",
"treePart" : {
"apple" : {
"name" : "apple",
"size" : 2
}
},
"uuid" : "9FCCDBF6-27A7-4E21-8681-5F3E63330504"
}
}
]
""".data(using: .utf8)!
do {
print( "begin decoding" )
/* This currently produces an error: Playground execution aborted: error: Execution was interrupted, reason: signal SIGABRT. The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.
let decodedTree = try decoder.decode( [AnyTreePartNode].self, from: jsonTree )
print( decodedTree.first( where: { $0.wrappedTreePartNode.treePart.wrappedTreePart is Trunk } )! )
*/
} catch let error {
print( error )
}
How the decode function in AnyTreePartNode should decode the JSON? What am I missing?
i have change just remove unowned from unowned var wrappedTreePartNode: TreePartNodeBase line
and compiled same code.
RESULT:
expected result when printing the decoded trunk node: Trunk { Branch { Apple }, Branch { Branch { Apple } } } begin decoding [Trunk, Branch, Branch, Branch, Apple, Apple]
Code:
//
// ViewController.swift
// TestDrive
//
// Created by Mahipal on 25/04/18.
// Copyright © 2018 Vandana. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
////////////// Test (The actual usage of the implementation above)
let trunk = Trunk()
let branch1 = Branch()
let branch2 = Branch()
let branch3 = Branch()
let apple1 = Apple()
let apple2 = Apple()
let trunkNode = TreePartNode<Trunk>( trunk )
let branchNode1 = TreePartNode<Branch>( branch1 )
let branchNode2 = TreePartNode<Branch>( branch2 )
let branchNode3 = TreePartNode<Branch>( branch3 )
let appleNode1 = TreePartNode<Apple>( apple1 )
let appleNode2 = TreePartNode<Apple>( apple2 )
trunkNode.add( child: branchNode1 )
trunkNode.add( child: branchNode2 )
branchNode2.add( child: branchNode3 )
branchNode1.add( child: appleNode1 )
branchNode3.add( child: appleNode2 )
let tree = [trunkNode.equatableSelf,
branchNode1.equatableSelf,
branchNode2.equatableSelf,
branchNode3.equatableSelf,
appleNode1.equatableSelf,
appleNode2.equatableSelf]
print( "expected result when printing the decoded trunk node: \(trunkNode)" )
let encoder = JSONEncoder()
let decoder = JSONDecoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
// This is how the encoded tree looks like
let jsonTree = """
[
{
"trunkNode" : {
"children" : [
"399B35A7-3307-4EF6-8B4C-1B83A8F734CD",
"60582654-13B9-40D0-8275-3C6649614069"
],
"treePart" : {
"trunk" : {
"color" : "#CCC",
"name" : "trunk"
}
},
"uuid" : "55748AEB-271E-4560-9EE8-F00C670C8896"
}
},
{
"branchNode" : {
"children" : [
"0349C0DF-FE58-4D8E-AA72-7466749EB1D6"
],
"parent" : "55748AEB-271E-4560-9EE8-F00C670C8896",
"treePart" : {
"branch" : {
"length" : 4,
"name" : "branch"
}
},
"uuid" : "399B35A7-3307-4EF6-8B4C-1B83A8F734CD"
}
},
{
"branchNode" : {
"children" : [
"6DB14BD5-3E4A-40C4-8EBF-FBD3CC6050C7"
],
"parent" : "55748AEB-271E-4560-9EE8-F00C670C8896",
"treePart" : {
"branch" : {
"length" : 4,
"name" : "branch"
}
},
"uuid" : "60582654-13B9-40D0-8275-3C6649614069"
}
},
{
"branchNode" : {
"children" : [
"9FCCDBF6-27A7-4E21-8681-5F3E63330504"
],
"parent" : "60582654-13B9-40D0-8275-3C6649614069",
"treePart" : {
"branch" : {
"length" : 4,
"name" : "branch"
}
},
"uuid" : "6DB14BD5-3E4A-40C4-8EBF-FBD3CC6050C7"
}
},
{
"appleNode" : {
"parent" : "399B35A7-3307-4EF6-8B4C-1B83A8F734CD",
"treePart" : {
"apple" : {
"name" : "apple",
"size" : 2
}
},
"uuid" : "0349C0DF-FE58-4D8E-AA72-7466749EB1D6"
}
},
{
"appleNode" : {
"parent" : "6DB14BD5-3E4A-40C4-8EBF-FBD3CC6050C7",
"treePart" : {
"apple" : {
"name" : "apple",
"size" : 2
}
},
"uuid" : "9FCCDBF6-27A7-4E21-8681-5F3E63330504"
}
}
]
""".data(using: .utf8)!
do {
print( "begin decoding" )
// This currently produces an error: Playground execution aborted: error: Execution was interrupted, reason: signal SIGABRT. The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.
let decodedTree = try decoder.decode( [AnyTreePartNode].self, from: jsonTree )
print( decodedTree )
} catch let error {
print( error )
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
import Foundation
///////////// TreePart -> implemented by Trunk, Branch, Apple and AnyTreePart
protocol TreePart: Codable {
var name: String { get set }
func isEqualTo( _ other: TreePart ) -> Bool
func asEquatable() -> AnyTreePart
}
extension TreePart where Self: Equatable {
func isEqualTo( _ other: TreePart ) -> Bool {
guard let otherTreePart = other as? Self else { return false }
return self == otherTreePart
}
func asEquatable() -> AnyTreePart {
return AnyTreePart( self )
}
}
///////////// AnyTreePart -> wrapper for Trunk, Branch and Apple
class AnyTreePart: TreePart, Codable {
var wrappedTreePart: TreePart
var name: String {
get {
return self.wrappedTreePart.name
}
set {
self.wrappedTreePart.name = newValue
}
}
init( _ treePart: TreePart ) {
self.wrappedTreePart = treePart
}
// MARK: Codable
enum CodingKeys: String, CodingKey {
case trunk,
branch,
apple
}
required convenience init( from decoder: Decoder ) throws {
let container = try decoder.container( keyedBy: CodingKeys.self )
var treePart: TreePart?
if let trunk = try container.decodeIfPresent( Trunk.self, forKey: .trunk ) {
treePart = trunk
}
if let branch = try container.decodeIfPresent( Branch.self, forKey: .branch ) {
treePart = branch
}
if let apple = try container.decodeIfPresent( Apple.self, forKey: .apple ) {
treePart = apple
}
guard let foundTreePart = treePart else {
let context = DecodingError.Context( codingPath: [CodingKeys.trunk, CodingKeys.branch, CodingKeys.apple], debugDescription: "Could not find the treePart key" )
throw DecodingError.keyNotFound( CodingKeys.trunk, context )
}
self.init( foundTreePart )
}
func encode( to encoder: Encoder ) throws {
var container = encoder.container( keyedBy: CodingKeys.self )
switch self.wrappedTreePart {
case let trunk as Trunk:
try container.encode( trunk, forKey: .trunk )
case let branch as Branch:
try container.encode( branch, forKey: .branch )
case let apple as Apple:
try container.encode( apple, forKey: .apple )
default:
fatalError( "Encoding error: No encoding implementation for \( type( of: self.wrappedTreePart ) )" )
}
}
}
extension AnyTreePart: Equatable {
static func ==( lhs: AnyTreePart, rhs: AnyTreePart ) -> Bool {
return lhs.wrappedTreePart.isEqualTo( rhs.wrappedTreePart )
}
}
///////////// TreePartNodeBase -> implemented by TreePartNode<T: TreePart> and AnyTreePartNode
protocol TreePartNodeBase: class {
var treePart: AnyTreePart { get set }
var uuid: UUID { get }
var parent: AnyTreePartNode? { get set }
var weakChildren: NSPointerArray { get }
func isEqualTo( _ other: TreePartNodeBase ) -> Bool
func asEquatable() -> AnyTreePartNode
}
extension TreePartNodeBase where Self: Equatable {
func isEqualTo( _ other: TreePartNodeBase ) -> Bool {
guard let otherTreePartNode = other as? Self else { return false }
return self == otherTreePartNode &&
self.treePart == other.treePart &&
self.uuid == other.uuid &&
self.parent == other.parent &&
self.children == other.children
}
func asEquatable() -> AnyTreePartNode {
return AnyTreePartNode( self )
}
}
extension TreePartNodeBase {
var children: [AnyTreePartNode] {
guard let allNodes = self.weakChildren.allObjects as? [AnyTreePartNode] else {
fatalError( "The children nodes are not of type \( type( of: AnyTreePartNode.self ) )" )
}
return allNodes
}
}
///////////// AnyTreePartNode -> wrapper of TreePartNode<T: TreePart>
class AnyTreePartNode: TreePartNodeBase, Codable {
var wrappedTreePartNode: TreePartNodeBase
var treePart: AnyTreePart {
get {
return self.wrappedTreePartNode.treePart
}
set {
self.wrappedTreePartNode.treePart = newValue
}
}
var uuid: UUID {
return self.wrappedTreePartNode.uuid
}
/// The parent node
weak var parent: AnyTreePartNode? {
get {
return self.wrappedTreePartNode.parent
}
set {
self.wrappedTreePartNode.parent = newValue
}
}
/// The weak references to the children of this node
var weakChildren: NSPointerArray {
return self.wrappedTreePartNode.weakChildren
}
init( _ treePartNode: TreePartNodeBase ) {
self.wrappedTreePartNode = treePartNode
}
// MARK: Codable
enum CodingKeys: String, CodingKey {
case trunkNode,
branchNode,
appleNode
}
required convenience init( from decoder: Decoder ) throws {
let container = try decoder.container( keyedBy: CodingKeys.self)
// This attempt of decoding possible nodes doesn't work
if let trunkNode: TreePartNode<Trunk> = try container.decodeIfPresent( TreePartNode<Trunk>.self, forKey: .trunkNode ) {
self.init( trunkNode )
} else if let branchNode: TreePartNode<Branch> = try container
.decodeIfPresent( TreePartNode<Branch>.self, forKey: .branchNode ) {
self.init( branchNode )
} else if let appleNode: TreePartNode<Apple> = try container
.decodeIfPresent( TreePartNode<Apple>.self, forKey: .appleNode ) {
self.init( appleNode )
} else {
let context = DecodingError.Context( codingPath: [CodingKeys.trunkNode,
CodingKeys.branchNode,
CodingKeys.appleNode],
debugDescription: "Could not find the treePart node key" )
throw DecodingError.keyNotFound( CodingKeys.trunkNode, context )
}
// TODO recreating the connections between the nodes should happen after all objects are decoded and will be done based on the UUIDs
}
func encode( to encoder: Encoder ) throws {
var container = encoder.container( keyedBy: CodingKeys.self )
switch self.wrappedTreePartNode {
case let trunkNode as TreePartNode<Trunk>:
try container.encode( trunkNode, forKey: .trunkNode )
case let branchNode as TreePartNode<Branch>:
try container.encode( branchNode, forKey: .branchNode )
case let appleNode as TreePartNode<Apple>:
try container.encode( appleNode, forKey: .appleNode )
default:
fatalError( "Encoding error: No encoding implementation for \( type( of: self.wrappedTreePartNode ) )" )
}
}
}
extension AnyTreePartNode: Equatable {
static func ==( lhs: AnyTreePartNode, rhs: AnyTreePartNode ) -> Bool {
return lhs.wrappedTreePartNode.isEqualTo( rhs.wrappedTreePartNode )
}
}
// enables printing of the wrapped tree part and its child elements
extension AnyTreePartNode: CustomStringConvertible {
var description: String {
var text = "\( type( of: self.wrappedTreePartNode.treePart.wrappedTreePart ))"
if !self.children.isEmpty {
text += " { " + self.children.map { $0.description }.joined( separator: ", " ) + " }"
}
return text
}
}
///////////// TreeParts (Trunk, Branch and Apple)
class Trunk: TreePart, Codable, Equatable {
var name: String
var color: String
init( name: String = "trunk",
color: String = "#CCC" ) {
self.name = name
self.color = color
}
static func ==(lhs: Trunk, rhs: Trunk) -> Bool {
return lhs.name == rhs.name &&
lhs.color == rhs.color
}
}
class Branch: TreePart, Codable, Equatable {
var name: String
var length: Int
init( name: String = "branch",
length: Int = 4 ) {
self.name = name
self.length = length
}
static func ==(lhs: Branch, rhs: Branch) -> Bool {
return lhs.name == rhs.name &&
lhs.length == rhs.length
}
}
class Apple: TreePart, Codable, Equatable {
var name: String
var size: Int
init( name: String = "apple",
size: Int = 2 ) {
self.name = name
self.size = size
}
static func ==(lhs: Apple, rhs: Apple) -> Bool {
return lhs.name == rhs.name &&
lhs.size == rhs.size
}
}
///////////// TreePartNode -> The node in the tree that contains the TreePart
class TreePartNode<T: TreePart>: TreePartNodeBase, Codable {
var equatableSelf: AnyTreePartNode!
var uuid: UUID
var treePart: AnyTreePart
var weakChildren = NSPointerArray.weakObjects()
private var parentUuid : UUID?
private var childrenUuids : [UUID]?
weak var parent: AnyTreePartNode? {
willSet {
if newValue == nil {
// unrelated code
// ... removes the references to this object in the parent node, if it exists
}
}
}
init( _ treePart: AnyTreePart,
uuid: UUID = UUID() ) {
self.treePart = treePart
self.uuid = uuid
self.equatableSelf = self.asEquatable()
}
convenience init( _ treePart: T,
uuid: UUID = UUID() ) {
self.init( treePart.asEquatable(),
uuid: uuid )
}
init( _ treePart: AnyTreePart,
uuid: UUID,
parentUuid: UUID?,
childrenUuids: [UUID]?) {
self.treePart = treePart
self.uuid = uuid
self.parentUuid = parentUuid
self.childrenUuids = childrenUuids
self.equatableSelf = self.asEquatable()
}
private func add( child: AnyTreePartNode ) {
child.parent = self.equatableSelf
self.weakChildren.addObject( child )
}
private func set( parent: AnyTreePartNode ) {
self.parent = parent
parent.weakChildren.addObject( self.equatableSelf )
}
// MARK: Codable
enum CodingKeys: String, CodingKey {
case treePart,
uuid,
parent,
children,
parentPort
}
required convenience init( from decoder: Decoder ) throws {
let container = try decoder.container( keyedBy: CodingKeys.self )
// non-optional values
let uuid: UUID = try container.decode( UUID.self, forKey: .uuid )
let treePart: AnyTreePart = try container.decode( AnyTreePart.self, forKey: .treePart )
// optional values
let childrenUuids: [UUID]? = try container.decodeIfPresent( [UUID].self, forKey: .children )
let parentUuid: UUID? = try container.decodeIfPresent( UUID.self, forKey: .parent )
self.init( treePart,
uuid: uuid,
parentUuid: parentUuid,
childrenUuids: childrenUuids)
}
func encode( to encoder: Encoder ) throws {
var container = encoder.container( keyedBy: CodingKeys.self )
// non-optional values
try container.encode( self.treePart, forKey: .treePart )
try container.encode( self.uuid, forKey: .uuid )
// optional values
if !self.children.isEmpty {
try container.encode( self.children.map { $0.uuid }, forKey: .children )
}
try container.encodeIfPresent( self.parent?.uuid, forKey: .parent )
}
}
extension TreePartNode: Equatable {
static func ==( lhs: TreePartNode, rhs: TreePartNode ) -> Bool {
return lhs.treePart == rhs.treePart &&
lhs.parent == rhs.parent &&
lhs.children == rhs.children
}
}
// enables printing of the wrapped tree part and its child elements
extension TreePartNode: CustomStringConvertible {
var description: String {
var text = "\( type( of: self.treePart.wrappedTreePart ))"
if !self.children.isEmpty {
text += " { " + self.children.map { $0.description }.joined( separator: ", " ) + " }"
}
return text
}
}
// MARK: functions for adding connections to other TreeParts for each specific TreePart type
extension TreePartNode where T: Trunk {
func add( child branch: TreePartNode<Branch> ) {
self.add( child: branch.equatableSelf )
}
}
extension TreePartNode where T: Branch {
func add( child apple: TreePartNode<Apple> ) {
self.add( child: apple.equatableSelf )
}
func add( child branch: TreePartNode<Branch> ) {
self.add( child: branch.equatableSelf )
}
func set( parent branch: TreePartNode<Branch> ) {
self.set( parent: branch.equatableSelf )
}
func set( parent trunk: TreePartNode<Trunk> ) {
self.set( parent: trunk.equatableSelf )
}
}
extension TreePartNode where T: Apple {
func set( parent branch: TreePartNode<Branch> ) {
self.set( parent: branch.equatableSelf )
}
}
////////////// Helper
extension NSPointerArray {
func addObject( _ object: AnyObject? ) {
guard let strongObject = object else { return }
let pointer = Unmanaged.passUnretained( strongObject ).toOpaque()
self.addPointer( pointer )
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With