Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@codelynx
Created September 4, 2021 13:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save codelynx/f8c4d8f394ee64483cd05e60332253e2 to your computer and use it in GitHub Desktop.
Save codelynx/f8c4d8f394ee64483cd05e60332253e2 to your computer and use it in GitHub Desktop.
This code demonstrate archive and unarchive objects derived from a certain class and its descendants using Codable and Runtime Utility
/*
This sample code shows the problem of archive and unarchive abstruct objects. Please see `TODO` keyword
and make this Contents class to save and load shape objects.
CASE: All target objects are based on NSObject with NSCoding to archive and unarchive Shape desendant class objects
file: archive-unarchive-1.swift
https://gist.github.com/codelynx/428b27b3cfd58b8c7382346f1a4bc415
*/
import Foundation
import CoreGraphics
// Utility class to inspect runtime classes
public class Runtime {
public static func allClasses() -> [AnyClass] {
let numberOfClasses = Int(objc_getClassList(nil, 0))
if numberOfClasses > 0 {
let classesPtr = UnsafeMutablePointer<AnyClass>.allocate(capacity: numberOfClasses)
let autoreleasingClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(classesPtr)
let count = objc_getClassList(autoreleasingClasses, Int32(numberOfClasses))
assert(numberOfClasses == count)
defer { classesPtr.deallocate() }
let classes = (0 ..< numberOfClasses).map { classesPtr[$0] }
return classes
}
return []
}
public static func subclasses(of `class`: AnyClass) -> [AnyClass] {
return self.allClasses().filter {
var ancestor: AnyClass? = $0
while let type = ancestor {
if ObjectIdentifier(type) == ObjectIdentifier(`class`) { return true }
ancestor = class_getSuperclass(type)
}
return false
}
}
public static func classes(conformToProtocol `protocol`: Protocol) -> [AnyClass] {
let classes = self.allClasses().filter { `class` in
var subject: AnyClass? = `class`
while let `class` = subject {
if class_conformsToProtocol(`class`, `protocol`) { print(String(describing: `class`)); return true }
subject = class_getSuperclass(`class`)
}
return false
}
return classes
}
public static func classes<T>(conformTo: T.Type) -> [AnyClass] {
return self.allClasses().filter { $0 is T }
}
}
// Main code
protocol Shape: Codable, CustomStringConvertible {
init(from decoder: Decoder) throws
func encode(to encoder: Encoder) throws
}
class Circle: Shape {
private enum CodingKeys: String, CodingKey { case center, radius }
var center: CGPoint
var radius: CGFloat
init(center: CGPoint, radius: CGFloat) {
self.center = center
self.radius = radius
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.center = try container.decode(CGPoint.self, forKey: .center)
self.radius = try container.decode(CGFloat.self, forKey: .radius)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.center, forKey: .center)
try container.encode(self.radius, forKey: .radius)
}
var description: String { "{Circle: center=\(self.center), radius=\(self.radius)}" }
}
class Rectangle: Shape {
private enum CodingKeys: String, CodingKey { case origin, size }
var origin: CGPoint
var size: CGSize
init(origin: CGPoint, size: CGSize) {
self.origin = origin
self.size = size
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.origin = try container.decode(CGPoint.self, forKey: .origin)
self.size = try container.decode(CGSize.self, forKey: .size)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.origin, forKey: .origin)
try container.encode(self.size, forKey: .size)
}
var description: String { "{Rectangle: origin=\(self.origin), size=\(self.size)}" }
}
class RoundedRectangle: Rectangle {
private enum CodingKeys: String, CodingKey { case cornerRadius }
var cornerRadius: CGFloat
init(origin: CGPoint, size: CGSize, cornerRadius: CGFloat) {
self.cornerRadius = cornerRadius
super.init(origin: origin, size: size)
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.cornerRadius = try container.decode(CGFloat.self, forKey: .cornerRadius)
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.cornerRadius, forKey: .cornerRadius)
try super.encode(to: encoder)
}
override var description: String { "{RoundedRectangle: \(super.description), cornerRadius=\(self.cornerRadius)}" }
}
class Contents: Codable {
private enum CodingKeys: String, CodingKey { case shapes, type, shape }
var shapes: [Shape]
init(shapes: [Shape]) {
self.shapes = shapes
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var subcontainer = try container.nestedUnkeyedContainer(forKey: .shapes)
let classes = Runtime.classes(conformTo: Shape.Type.self)
var shapes = [Shape]()
while (!subcontainer.isAtEnd) {
let type = try subcontainer.decode(String.self)
if let shapeType = classes.filter({ "\($0.self)" == type }).first as? Shape.Type {
let shape = try shapeType.init(from: subcontainer.superDecoder())
shapes.append(shape)
}
}
self.shapes = shapes
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
var subcontainer = container.nestedUnkeyedContainer(forKey: .shapes)
for shape in self.shapes {
let typeString: String = "\(type(of: shape))"
try subcontainer.encode(typeString)
try shape.encode(to: subcontainer.superEncoder())
}
}
}
func test() throws {
let a = Circle(center: CGPoint(x: 10, y: 20), radius: 30)
let b = Rectangle(origin: CGPoint(x: 40, y: 50), size: CGSize(width: 60, height: 70))
let c = RoundedRectangle(origin: CGPoint(x: 100, y: 110), size: CGSize(width: 120, height: 130), cornerRadius: 5)
let contents = Contents(shapes: [a, b, c])
let encoder = JSONEncoder()
let data: Data = try encoder.encode(contents)
print("encoded:", data as NSData)
let decoder = JSONDecoder()
let object = try decoder.decode(Contents.self, from: data)
print(object.shapes.map { $0.description }.joined(separator: "\r"))
}
// CAUTION: This code may not work on Playground.
do { try test() }
catch { print("\(error)") }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment