Nested enums which conform to Equatable protocol produce error - swift

I have a protocol named TableViewItem. This protocol enforces that conforming objects implement a type property, which has the protocol TableViewCellIdentifiable as its type. TableViewCellIdentifiable is used to group three nested enums together, as shown below:
internal protocol TableViewCellIdentifiable: Equatable { }
internal enum TableViewCellType {
internal enum PortfolioSelection: String, TableViewCellIdentifiable {
case portfolio = "portfolioTableViewCell"
case enterPortfolioDetails = "enterPortfolioDetailsTableViewCell"
case addPortfolio = "actionTableViewCell"
}
internal enum EditPortfolio: String, TableViewCellIdentifiable {
case editPortfolioName = "editPortfolioNameTableViewCell"
case deletePortfolio = "deletePortfolioTableViewCell"
}
internal enum Portfolio: String, TableViewCellIdentifiable {
case portfolioAsset = "portfolioAssetTableViewCell"
case addAsset = "actionTableViewCell"
}
}
Here is an example of how this is being used:
internal final class EditPortfolioNameTableViewItem: TableViewItem {
// MARK: - Internal Properties
internal let type: TableViewCellIdentifiable = TableViewCellType.EditPortfolio.editPortfolioName
internal let viewModel: TableViewCellModel
// MARK: - Initialization
internal init(viewModel: EditPortfolioNameTableViewCellModel) {
self.viewModel = viewModel
}
}
Unfortunately, on the line that I am declaring the type property, I receive the following error:
Protocol 'TableViewCellIdentifiable' can only be used as a generic constraint because it has Self or associated type requirements
I have read through other questions/answers from others who have encountered this error but I can't quite understand why this particular implementation is problematic, and what the solution would be. I know that Equatable is the source of the problem, however this is crucial to the functionality, as the enums serve two purposes:
To provide reuse identifiers for the table view cells (the raw values).
To allow types to be compared - i.e:
self.tableViewItems.contains(where: { $0.type == item.type })
Any suggestions would be much appreciated, even if it means taking an alternative approach.

In your head, should the following code compile?
var x : Equatable
It shouldn't. Why?
Because if you had:
var x : Equatable
var y : Equatable
Then the compiler can't ensure that x & y are of the same type. x can be "John", because "John"/Strings are Equatable...all while y can be 10, because 10/integers are equatable.
and the compiler would suspect that a few lines below you might want to do
if x == y { print ("equal" }
which it can't process. So it just stops you from ever doing it in the beginning.
The following line of your code will trigger the same error because of the reason above.
internal let type: TableViewCellIdentifiable = TableViewCellType.EditPortfolio.editPortfolioName

As explained by Honey's answer, TableViewCellIdentifiable doesn't provide enough type information for the compiler to work with. You could potentially adopt a different approach which changes the structure a bit (and is potentially overkill), but provides the functionality you're looking for:
internal protocol ValueAssociated { }
internal extension ValueAssociated {
fileprivate var association: (label: String, value: Any?)? {
get {
let mirror = Mirror(reflecting: self)
if let association = mirror.children.first, let label = association.label {
return (label, association.value)
}
return nil
}
}
}
internal protocol CellIdentifiable {
var rawValue: String { get }
}
internal enum CellType: Equatable, ValueAssociated {
case portfolio(PortfolioIdentifier)
case portfolioSelection(PortfolioSelectionIdentifier)
case editPortfolio(EditPortfolioIdentifier)
internal var identifier: String? {
return (self.association?.value as? CellIdentifiable)?.rawValue
}
internal enum PortfolioIdentifier: String, Equatable, CellIdentifiable {
case portfolioAsset = "portfolioAssetTableViewCell"
case addAsset = "actionTableViewCell"
}
internal enum PortfolioSelectionIdentifier: String, Equatable, CellIdentifiable {
case portfolio = "portfolioTableViewCell"
case enterPortfolioDetails = "enterPortfolioDetailsTableViewCell"
case addPortfolio = "actionTableViewCell"
}
internal enum EditPortfolioIdentifier: String, Equatable, CellIdentifiable {
case editPortfolioName = "editPortfolioNameTableViewCell"
case deletePortfolio = "deletePortfolioTableViewCell"
}
}
This can be used as follows:
internal let cellType: CellType = .portfolio(.portfolioAsset)
print(cellType.identifier!) // Prints "portfolioAssetTableViewCell"
Hope this helps.

Related

Swift: Same-Type requirement makes generic parameters equivalent?

I'm using swift 5 and try to compile the following code:
protocol BasicProtocol {
associatedtype T
var str: T {get set}
}
struct AItem<U>: BasicProtocol {
typealias T = U
var str: T
init<G: StringProtocol>(str: G) where G == T {
self.str = str
}
}
I got compilation error:
error: Test.playground:10:45: error: same-type requirement makes generic parameters 'G' and 'U' equivalent
init<G: StringProtocol>(str: G) where G == T {
^
How to make them equivalent? or I can't?
Thanks.
Update 1:
This is the problem I encountered: I want to declare struct "AItem", hoping it has a generic type "T". And this generic type will have some restrictions, such as: "T: StringProtocol". Then for some reason, I need to use an array to load these structs, and ensure that the generics of each structure can be set at will.
I learned that there is "type-erase" might can solve this. So I tried this way, but it seemed unsuccessful. The problems mentioned above have occurred.
Update 2:
struct AItem<T: StringProtocol> {
var aStr: T
}
var array: [AItem<Any>] = [AItem(aStr: "asdfasdf")]
Look,If you compile this code, you will get a compilation error:
error: Test.playground:5:13: error: type 'Any' does not conform to protocol 'StringProtocol'
var array: [AItem<Any>] = [AItem(aStr: "asdfasdf")]
^
If I use "var array: [AItem<String>]", I will not be able to put any other non-"String" but implemented "StringProtocol" instance in the array.
This is why I said I want "ensure that the generics of each structure can be set at will".
Update 3:
very thanks for #jweightman, now I update my question again.
protocol ConstraintProtocol {}
extension String: ConstraintProtocol{}
extension Data: ConstraintProtocol{}
extension Int: ConstraintProtocol{}
.......
struct AItem<T = which class has Implemented "ConstraintProtocol"> {
var aPara: T
init(aPara:T) {
self.aPara = aPara
}
}
// make a array to contain them
var anArray: [AItem<Any class which Implemented "ConstraintProtocol">] = [AItem(aPara: "String"), AItem(aPara: 1234), AItem(aPara: Data("a path")), …]
// then I can use any item which in anArray. Maybe I will implement a method to judge these generics and perform the appropriate action.
for curItem in anArray {
var result = handleItem(curItem)
do something...
}
func handleItem<T: ConstraintProtocol>(item: AItem<T>) -> Any? {
if (item.T is ...) {
do someThing
return ......
} else if (item.T is ...) {
do someThing
return ...
}
return nil
}
This is my whole idea, but all of which are pseudo-code.
It seems like type erasure is the answer to your problem. The key idea to the type erasure pattern is to put your strongly typed but incompatible data (like an AItem<String> and an AItem<Data>) inside of another data structure which stores them with "less precise" types (usually Any).
A major drawback of type erasure is that you're discarding type information—if you need to recover it later on to figure out what you need to do with each element in your array, you'll need to try to cast your data to each possible type, which can be messy and brittle. For this reason, I've generally tried to avoid it where possible.
Anyways, here's an example of type erasure based on your pseudo code:
protocol ConstraintProtocol {}
extension String: ConstraintProtocol{}
extension Data: ConstraintProtocol{}
extension Int: ConstraintProtocol{}
struct AItem<T: ConstraintProtocol> {
var aPara: T
init(aPara: T) {
self.aPara = aPara
}
}
struct AnyAItem {
// By construction, this is always some kind of AItem. The loss of type
// safety here is one of the costs of the type erasure pattern.
let wrapped: Any
// Note: all the constructors always initialize `wrapped` to an `AItem`.
// Since the member variable is constant, our program is "type correct"
// even though type erasure isn't "type safe."
init<T: ConstraintProtocol>(_ wrapped: AItem<T>) {
self.wrapped = wrapped
}
init<T: ConstraintProtocol>(aPara: T) {
self.wrapped = AItem(aPara: aPara);
}
// Think about why AnyAItem cannot expose any properties of `wrapped`...
}
var anArray: [AnyAItem] = [
AnyAItem(aPara: "String"),
AnyAItem(aPara: 1234),
AnyAItem(aPara: "a path".data(using: .utf8)!)
]
for curItem in anArray {
let result = handleItem(item: curItem)
print("result = \(result)")
}
// Note that this function is no longer generic. If you want to try to "recover"
// the type information you erased, you will have to do that somewhere. It's up
// to you where you want to do this.
func handleItem(item: AnyAItem) -> String {
if (item.wrapped is AItem<String>) {
return "String"
} else if (item.wrapped is AItem<Data>) {
return "Data"
} else if (item.wrapped is AItem<Int>) {
return "Int"
}
return "unknown"
}
An alternative to type erasure you could consider, which works well if there's a small, finite set of concrete types your generic could take on, would be to use an enum with associated values to define a "sum type". This might not be a good choice if the protocol you're interested in is from a library that you can't change. In practice, the sum type might look like this:
enum AItem {
case string(String)
case data(Data)
case int(Int)
}
var anArray: [AItem] = [
.string("String"),
.int(1234),
.data("a path".data(using: .utf8)!)
]
for curItem in anArray {
let result = handleItem(item: curItem)
print("result = \(result)")
}
func handleItem(item: AItem) -> String {
// Note that no casting is required, and we don't need an unknown case
// because we know all types that might occur at compile time!
switch item {
case .string: return "String"
case .data: return "Data"
case .int: return "Int"
}
}

Swift: Generic's type protocol not being recognized

Long time listener, first time caller.
I'm getting the following error:
Cannot convert value of type MyClass<Model<A>, OtherClass> to expected argument type MyClass<Protocol, OtherClass>
Despite the fact that MyClass<T> conforms to Protocol
I've attached a snippet that can be run in Playgrounds that resembles what I am actually trying to achieve.
protocol DisplayProtocol {
var value: String { get }
}
class DataBundle<T: CustomStringConvertible>: DisplayProtocol {
var data: T
var value: String {
return data.description
}
init(data: T) {
self.data = data
}
}
class Mapper<DisplayProtocol, Data> {
// ...
}
class MapperViewModel<Data> {
let mapper: Mapper<DisplayProtocol, Data>
init(mapper: Mapper<DisplayProtocol, Data>) {
self.mapper = mapper
}
}
let dataBundle = DataBundle<Int>(data: 100)
let mapper = Mapper<DataBundle<Int>, Bool>()
let viewModel = MapperViewModel<Bool>(mapper: mapper) // <- This fails w/error
Is this the expected behavior? If it is it feels like its breaking the contract of allowing me to have the DisplayProtocol as a type in Mapper.
This is caused by the fact that Swift generics are invariant in respect to their arguments. Thus MyClass<B> is not compatible with MyClass<A> even if B is compatible with A (subclass, protocol conformance, etc). So yes, unfortunately the behaviour is the expected one.
In your particular case, if you want to keep the current architecture, you might need to use protocols with associated types and type erasers.

How to reduce Swift enum conversion code

I have a variety of enums such as below.
enum PaperOrientation : Int { case portrait, landscape }
enum MetricType : Int { case inches, metric }
I made the enums of type Int, so that the values of instances could be saved as numbers with CoreData.
When retrieving the values from CoreData to use in the program, I end up with very similar conversion routines, like those shown below.
Typically, I want some default value - such as for the case where it is a new enum for the latest version of the program, and a value for that variable may not actually have been saved in CoreData. For example, the MetricType was added for the second rev of the program. Retrieving a paper created in rev 1 will not have a metric value saved. For the nil value, I want to use a default value the paper was originally assumed to have.
class ConversionRoutine {
class func orientationFor(_ num: NSNumber?) -> PaperOrientation {
if let iVal = num?.intValue {
if let val = PaperOrientation(rawValue: iVal) {
return val
}
}
return PaperOrientation(rawValue: 0)!
}
class func metricTypeFor(_ num: NSNumber?) -> MetricType {
if let iVal = num?.intValue {
if let val = MetricType(rawValue: iVal) {
return val
}
}
return MetricType(rawValue: 0)!
}
}
Is there a way to reduce the redundancy?
I present a way below that works pretty well. But welcome more refinements or improvements.
The Swift 4 example below uses a Defaultable protocol based on RawRepresentable. The first step is creating a defaultValue that can be used when the initializer fails. Note that the Defaultable protocol is not limited to Int enums. A String enum could also use it.
protocol Defaultable : RawRepresentable {
static var defaultValue : Self { get }
}
protocol IntDefaultable : Defaultable where RawValue == Int {
}
extension IntDefaultable {
static func value(for intValue : Int) -> Self {
return Self.init(rawValue: intValue) ?? Self.defaultValue
}
static func value(for num : NSNumber?) -> Self {
if let iVal = num?.intValue {
return self.value(for: iVal)
}
return Self.defaultValue
}
}
After the Defaultable protocol is defined, I can create an IntDefaultable protocol that will be used for Int enums.
In an extension to IntDefaultable, I can create the generic code to handle the conversion. First, I create a function that takes an Int. Then I create a function that takes an NSNumber optional.
Next, look at how one of the enums is built:
enum MetricType : Int, Codable, IntDefaultable { case inches, metric
static var defaultValue: MetricType = .inches
}
I also decided to declare the enum Codable, which may be useful. When I add the IntDefaultable protocol, it becomes fairly easy to add the defaultValue line of code with code completion - go to the new line and type “def”-tab, then “ = .”, and then choose one of the values from the popup. Note that often I want to pick the first enum value, but the default value could be any one.
The last thing is calling the conversion routine for getting a value from CoreData
let units = MetricType.value(for: self.metricType) // where self.metricType is the NSManagedObject member.
You can add an initializer in enum.
enum PaperOrientation : Int {
case portrait, landscape
init(number: NSNumber) {
self = PaperOrientation(rawValue: number.intValue) ?? .portrait
}
}

How can you mirror the design of the Codable/CodableKeys protocols?

I'm trying to achieve something similar to how Swift utilizes the CodableKeys protocol set on an enumeration defined within a class that implements Codable. In my case, the class is CommandHandler and the enumeration is CommandIds and it doesn't require on code-gen from the compiler as the enum will always be explicitly specified.
Here's a simplified version of what I'm after...
protocol CommandId{}
protocol CommandHandler{
associatedtype CommandIds : CommandId, RawRepresentable
}
class HandlerA : CommandHandler{
enum CommandIds : String, CommandId{
case commandA1
case commandA2
}
}
class HandlerB : CommandHandler{
enum CommandIds : String, CommandId{
case commandB1
case commandB2
case commandB3
}
}
func processHandler<T:CommandHandler>(_ handler:T){
// Logic to iterate over CommandIds. <-- This is where I get stumped
}
let handlerA = HandlerA()
processHandler(handlerA)
I'm struggling with the code inside processHandler here because I'm not sure how to reach the enumeration's values from a handler instance.
So what am I missing? What would be the code to get the values of the associated enumeration?
Ok, I believe I have all of the pieces in place to show how you can do this in Swift. Turns out my revised question was right at the edge of being correct in how to do it.
Here's my example written in Swift 4...
First, here's how you define the protocols needed to make this work. From the design standpoint, these are synonymous with CodableKeys and Codable respectively.
protocol CommandId : EnumerableEnum, RawRepresentable {}
protocol CommandHandler{
associatedtype CommandIds : CommandId
}
Here's a protocol and its associated extension to make the 'case' values of enums enumerable. You simply make your enums adhere to the EnumerableEnum protocol and you get a 'values' array.
Since the CommandId protocol above will already be applied to the enums in question, we simplify things by making it also apply the EnumerableEnum protocol in its own definition. This way we only need to apply CommandId to our enums and we get both.
public protocol EnumerableEnum : Hashable {
static var values: [Self] { get }
}
public extension EnumerableEnum {
public static var values: [Self] {
let valuesSequence = AnySequence { () -> AnyIterator<Self> in
var caseIndex = 0
return AnyIterator {
let currentCase: Self = withUnsafePointer(to: &caseIndex){
$0.withMemoryRebound(to: self, capacity: 1){
$0.pointee
}
}
guard currentCase.hashValue == caseIndex else {
return nil
}
caseIndex += 1
return currentCase
}
}
return Array(valuesSequence)
}
}
Here are two classes that implement my CommandHandler/CommandId protocols
class HandlerA : CommandHandler{
enum CommandIds : Int, CommandId{
case commandA1
case commandA2
}
}
class HandlerB : CommandHandler{
enum CommandIds : String, CommandId{
case commandB1 = "Command B1"
case commandB2
case commandB3 = "Yet another command"
}
}
Here's a test function which accepts a CommandHandler type
func enumerateCommandIds<T:CommandHandler>(_ commandHandlerType:T.Type){
for value in commandHandlerType.CommandIds.values{
let caseName = String(describing:value)
let caseRawValue = value.rawValue
print("\(caseName) = '\(caseRawValue)'")
}
}
And finally, here's the results of running that test
enumerateCommandIds(HandlerA.self)
// Outputs
// commandA1 = '0'
// commandA2 = '1'
enumerateCommandIds(HandlerB.self)
// Outputs
// commandB1 = 'Command B1'
// commandB2 = 'commandB2'
// commandB3 = 'Yet another command'
It was a long, windy road to get here, but we did! Thanks to everyone for their help!
You can do this easily using Swift's CaseIterable protocol.
protocol CommandId: CaseIterable {
func handle()
}
protocol CommandHandler {
associatedtype CommandIds: CommandId, RawRepresentable
}
class HandlerA: CommandHandler {
enum CommandIds: String, CommandId {
case commandA1
case commandA2
func handle() {
print("\(rawValue) is handled")
}
}
}
class HandlerB: CommandHandler {
enum CommandIds: String, CommandId {
case commandB1
case commandB2
case commandB3
func handle() {
print("\(rawValue) is handled")
}
}
}
func processHandler<T: CommandHandler>(_ handler: T) {
// Logic to iterate over CommandIds. <-- This is where I get stumped
T.CommandIds.allCases.forEach({ $0.handle() })
}
let handlerA = HandlerA()
processHandler(handlerA)
Having enums in protocols is not allowed in swift. If it were possible, the protocol would not be allowed to refer to the enumerated cases. You'd have to as cast eventually breaking protocol ideals.
Maybe associated types best serve your purpose?
enum Commands:String {
case compliaceType = ""
}
protocol CommandDef {
associatedtype Commands
}
class MyClassA : CommandDef {
enum Commands : String {
case commandA1 = "hi"
case commandA2 = "Explicit A2"
}
}
class MyClassB : CommandDef {
enum Commands : String {
case commandB2 = "Explicit B2"
}
}
print(MyClassA.Commands.commandA1)
The only solution come up in my mind is using associatedtype and moving the enum outside the protocol, doing something like:
enum Commands:String {
case default_command = ""
}
protocol CommandDef {
associatedtype Commands
}
class MyClassA : CommandDef {
enum Commands : String {
case commandA1
case commandA2 = "Explicit A2"
}
}
class MyClassB : CommandDef {
enum Commands : String {
case commandB1
case commandB2 = "Explicit B2"
case commandB3
}
}
There isn’t a way with the Swift language itself to mimic Codable, because Codable’s implementation relies on the compiler generating special-case code. Specifically, there is no protocol extension that creates the default CodingKeys enum, the compiler creates that enum inside a type that conforms to Codable automatically, unless you specify it yourself.
This is similar to how the Swift compiler will automatically create an initializer for structs (the “memberwise initializer”) unless you specify your own initializer. In that case as well, there is no protocol extension or Swift language feature you can use to replicate the auto-generated struct initializer, because it is based on metaprogramming / code generation, in this case, by the compiler.
There are tools, such as Sourcery (https://github.com/krzysztofzablocki/Sourcery), which allow you to implement your own metaprogramming and code generation. With Sourcery, you could run a script in your build phase that would automatically generate the code for the Command enum you want, and add it to any type that conforms toCommandHandler.
This would essentially mimic how Codable works via the Swift compiler generating needed code. But in neither case is it accomplished via Swift language features like protocol extensions, etc. Rather, it is boilerplate source code that gets written by a script rather than having to be written by hand.
UPDATE FOR REVISED QUESTION
If simply ensuring there is a way to enumerate all the cases of the CommandIds enum is all that you need, you can always add a protocol requirement to the CommandId protocol like this:
protocol CommandId {
static var all: [Self] { get }
}
Then implementations would need to look like:
class HandlerA : CommandHandler {
enum CommandIds : String, CommandId {
case commandA1
case commandA2
static var all: [CommandIds] { return [.commandA1, .commandA2] }
}
}
And your process function could look like:
func processHandler<T:CommandHandler>(_ handler:T){
T.CommandIds.all.forEach { // Do something with each command case }
}
It's worth continuing to note though, that for Codable, Swift does not have or use any language functionality to enumerate all cases. Instead, the compiler uses knowledge of all the properties of the Codable-conforming type to generate a specific implementation of the init(from decoder: Decoder) for that type, including a line for each case, based on the known property names and types, e.g.
// This is all the code a developer had to write
struct Example: Codable {
let name: String
let number: Int
}
// This is all source code generated by the compiler using compiler reflection into the type's properties, including their names and types
extension Example {
enum CodingKeys: String, CodingKey {
case name, number
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
number = try values.decode(Int.self, forKey: .number)
}
}
Since Swift is a mostly static language with extremely limited runtime reflection (for now), there is no way to do these types of tasks at runtime using language features.
But there is nothing stopping you or any developer from using code generation the same way the Swift compiler does to accomplish similar conveniences. In fact, a well-known member of the Swift core team at Apple even encouraged me to do so when I presented him some challenges I was facing at WWDC.
It's also worth noting that features that are now part of the Swift compiler or have open pull requests to be added to the Swift compiler (like Codable, and automatic conformance to Equatable and Hashable) were first created and implemented in real-world Swift projects using Sourcery, before they were added to Swift itself.

Protocol function implementation without actually conforming to a protocol

I am a beginner Swift learner and I have a question about protocols. I have followed a tutorial that teaches you about linked lists, which is as follows:
Node:
class LinkedListNode<Key> {
let key: Key
var next: LinkedListNode?
weak var previous: LinkedListNode?
init (key: Key) {
self.key = key
}
}
And the linked list:
class LinkedList<Element>: CustomStringConvertible {
typealias Node = LinkedListNode<Element>
private var head: Node?
// irrelevant code removed here
var description: String {
var output = "["
var node = head
while node != nil {
output += "\(node!.key)"
node = node!.next
if node != nil { output += ", " }
}
return output + "]"
}
}
The var description: String implementation simply lets you to print each elements in the linked list.
So far, I understand the structure of the linked list, my problem isn't about the linked list actually. What I don't understand is the protocol CustomStringConvertible. Why would it be wrong if I have only the var description: String implementation without conforming to the protocol? I mean, this protocol just simply say "Hey, you need to implement var description: String because you are conformed to me, but why can't we just implement var description: String without conforming to the protocol?
Is it because in the background, there is a function or some sort that takes in a type CustomStringConvertible and run it through some code and voila! text appears.
Why can't we just implement var description: String without conforming to the protocol?
Compare:
class Foo {
var description: String { return "my awesome description" }
}
let foo = Foo()
print("\(foo)") // return "stackoverflow.Foo" (myBundleName.Foo)
and
class Foo: CustomStringConvertible {
var description: String { return "my awesome description" }
}
let foo = Foo()
print("\(foo)") // return "my awesome description"
When you use CustomStringConvertible, you warrant that this class have the variable description, then, you can call it, without knowing the others details of implementation.
Another example:
(someObject as? CustomStringConvertible).description
I don't know the type of someObject, but, if it subscriber the CustomStringConvertible, then, I can call description.
You must conform to CustomStringConvertible if you want string interpolation to use your description property.
You use string interpolation in Swift like this:
"Here's my linked list: \(linkedList)"
The compiler basically turns that into this:
String(stringInterpolation:
String(stringInterpolationSegment: "Here's my linked list: "),
String(stringInterpolationSegment: linkedList),
String(stringInterpolationSegment: ""))
There's a generic version of String(stringInterpolationSegment:) defined like this:
public init<T>(stringInterpolationSegment expr: T) {
self = String(describing: expr)
}
String(describing: ) is defined like this:
public init<Subject>(describing instance: Subject) {
self.init()
_print_unlocked(instance, &self)
}
_print_unlocked is defined like this:
internal func _print_unlocked<T, TargetStream : TextOutputStream>(
_ value: T, _ target: inout TargetStream
) {
// Optional has no representation suitable for display; therefore,
// values of optional type should be printed as a debug
// string. Check for Optional first, before checking protocol
// conformance below, because an Optional value is convertible to a
// protocol if its wrapped type conforms to that protocol.
if _isOptional(type(of: value)) {
let debugPrintable = value as! CustomDebugStringConvertible
debugPrintable.debugDescription.write(to: &target)
return
}
if case let streamableObject as TextOutputStreamable = value {
streamableObject.write(to: &target)
return
}
if case let printableObject as CustomStringConvertible = value {
printableObject.description.write(to: &target)
return
}
if case let debugPrintableObject as CustomDebugStringConvertible = value {
debugPrintableObject.debugDescription.write(to: &target)
return
}
let mirror = Mirror(reflecting: value)
_adHocPrint_unlocked(value, mirror, &target, isDebugPrint: false)
}
Notice that _print_unlocked only calls the object's description method if the object conforms to CustomStringConvertible.
If your object doesn't conform to CustomStringConvertible or one of the other protocols used in _print_unlocked, then _print_unlocked creates a Mirror for your object, which ends up just printing the object's type (e.g. MyProject.LinkedList) and nothing else.
CustomStringConvertible allows you to do a print(linkedListInstance) that will print to the console whatever is returned by the description setter.
You can find more information about this protocol here: https://developer.apple.com/reference/swift/customstringconvertible