Values of structs changing when appending to array with protocol type - swift

I have a protocol, and some structs that conform to it, basically in the format shown below. I'm facing an issue where if I append different structs to an array of type [Protocol], the values of the structs are changing in a weird way. However, if I change the type of the array to [Struct1] or [Struct2], and only append the appropriate types, there's no problem.
protocol Protocol {
var id: String { get set }
var name: String { get set }
}
struct Struct1: Protocol {
var id: String = "1"
var name: String = "Struct1"
var uniqueProperty1: String = "uniqueProperty1"
}
struct Struct2: Protocol {
var id: String = "2"
var name: String = "Struct2"
var uniqueProperty2: String = "uniqueProperty2"
}
var structs: [Protocol] = []
let struct1 = Struct1()
let struct2 = Struct2()
structs.append(struct1)
structs.append(struct2)
And I should add, the above code works as expected. It's my project that has a protocol and some structs however that are behaving strangely. What could be causing this issue?

I discovered that if you look at the value of an element within an array of type [Protocol] in the Variables View within the Debug Area, it's possible that it won't reflect that element's actual values.
Here's an example:
You can see that itemsList in cards[2] is nil, but when I print out the same value in the Debugger Output of the Console, it's not nil (has a length of 4):
(lldb) po (cards[2] as? RBListCard)?.itemsList?.count
▿ Optional<Int>
- some : 4
I guess the moral of the story is don't trust the values that show up within the Variables View.

Related

Is it possible to have default member initialization with a class in Swift (like with a struct)

In the following example code, I create a struct and a class with similar members. With the struct I can initialize an instance by any number of the members into its constructor, and the rest will default. With a class, I have to specify every version of init I want to use. Seems like I must be missing some way to do it with a class though -- is there any way to do this? It looks like in 2016 there was not, but I know Swift has changed a ton since then. I'm hoping there is a way now.
import Foundation
struct FooStruct {
var id: UUID = UUID()
var title = ""
}
// these statements both work fine
let a = FooStruct(id: UUID())
let a2 = FooStruct(title: "bar")
class FooClass {
var id: UUID = UUID()
var title = ""
}
// these statements both give the same error:
// Argument passed to call that takes no arguments
let b = FooClass(id: UUID())
let b2 = FooClass(title: "bar")
What you are seeing with Structure types is what is called a memberwise initializer. Swift does not provide one of these to Class types because of the more complex way Classes are initialized, due to their inheritance model.
Swift provides a default initializer—different than a memberwise initializer—for any structure or class that provides default values for all of its properties and doesn’t provide at least one initializer itself. The default initializer simply creates a new instance with all of its properties set to their default values.
you could just use this:
class FooClass {
var id: UUID = UUID()
var title = ""
init(id: UUID = UUID(), title: String = ""){
self.id = id
self.title = title
}
}
and this will work:
let b = FooClass(id: UUID())
let b2 = FooClass(title: "bar")

Changing a struct with one type to another type

I have two structs with the same fields. What is the best way to merge them.
struct Type1{
var variable1:String?
var variable2:Double?
var variable3:String?
var notImporant:String?
}
struct Type2{
var variable1A:String?
var variable2A:String?
var variable3A:String!
}
What is the best way to convert type2 to type1? I am getting a return from an API and parsing it using codable but there are two different structs and I need to get one struct. The data is the same, it is just mapped differently in terms of types. Some of the structs have more info and others have less.
Just make a copy constructor in both structs like so:
struct Type2 {
var variable1A:String?
var variable2A:String?
var variable3A:String!
init(_ otherType: Type1) {
variable1A = otherType.variable1
variable2A = otherType.variable2
variable3A = otherType.variable3
}
}
You cannot cast two unrelated structs. What you can do is define a common Protocol for the two of them, and use them in places where you don't care which underlying object it is.
protocol CommonProtocol {
var variable1: String? { get }
var variable3: String? { get }
}
struct Type1: CommonProtocol {
var variable1:String?
var variable2:Double?
var variable3:String?
var notImporant:String?
}
struct Type2: CommonProtocol {
var variable1A:String?
var variable2A:String?
var variable3A:String!
}
Then, in whichever place you're currently stuck with a type1 instead of a type2, have that function just accept a CommonProtocol instead, and you can use either.
Note that, while both of your types have a variable2, one of them is a Double? while the other is a String?. There are a few different ways to approach that, which I leave to you. I just left it out of the protocol.
On another note, it's Swift standard to capitalize the names of structs (Type1, Type2). In certain instances, you can run into problems if you don't, so I suggest you do.

Cast between types using only protocols?

I have 2 structs that are only related by protocols. I'm trying to cast between the two types since they share a protocol, but I get a runtime error.
This is what I'm trying to do:
protocol A {
var id: String { get }
var name: String { get }
var isActive: Bool { get set }
}
struct Entity: A {
let id: String
var name: String
var isActive: Bool
func someFun() {
print("\(name), active: \(isActive)")
}
}
struct ViewModel: A {
let id: String
var name: String
var isActive: Bool
}
let vm = ViewModel(id: "abc", name: "John Doe", isActive: true)
let pt = vm as A
let et = pt as! Entity
print(et.someFun()) //crash
The error I get is:
Could not cast value of type '__lldb_expr_87.ViewModel' (0x111835638) to '__lldb_expr_87.Entity' (0x111835550).
This is a real bummer because if I have millions of records, I don't want to have to loop through each one to manually convert one by one. I was hoping to implicitly cast magically like this:
let entity = vm as A as Entity
let entities = vms as [A] as [Entity]
Any performance-conscious way to pass objects between boundaries without looping through each one?
As vadian stated in his comment, you can't typecast like this. To perform what you are trying to do by typecasting, you would need to do something like this:
let entity = Entity(id: vm.id, name: vm.name, isActive: vm.isActive)
let entities = vms.map {
return Entity(id: $0.id, name: $0.name, isActive: $0.isActive
}
The reason why you are crashing is because typecasting doesn't change the properties and methods that an object actually has, only the methods and properties they have access to. If you performed the following, you would lose access to your someFun() method:
let entityAsA = Entity(id: "id", name: "name", isActive: true) as A
However, the following will compile and run without crashing:
(entityAsA as! Entity).someFun()
So, when you attempt to cast from ViewModel to A to Entity, it doesn't change the way the value was created in memory (without access to a function called someFun() ) and you'll crash every time.

pointer to string in Array

During my program execution, I have to load a large array of MyStruct
// Struct definition
struct MyStruct
{
var someString: String = ""
}
// Array definition
var ar = Array<MyStruct>()
The issue is, having all these someString take a large amount of memory and could easily be shaved down because they all have common (large) prefixes. These prefix do vary.
So, I would like to have
struct MyStruct
{
var someString: String = ""
var someString: pointer to a shared string prefix
}
My questions are:
How do I tell swift not to assign and copy the string, but rather a pointer to the string.
How do I obtain the pointer to said strings. Currently, I obtain the prefix using stringByReplacingOccurrencesOfString.
Also, For the strings to be retained somewhere, I plan to put all the prefixes in an array.
Thanks for your help
You can use a static variable to achieve that.
struct MyStruct
{
static var prefix: String = "prefix"
var someString: String = ""
}

Change what print(Object) displays in Swift 2.0

I am trying to make my class Digit display the num variable whenever print is called on that object, in Swift 2.0. I thought this might be done with a description variable, but no luck.
class Digit {
var num: Int
var x: Int
var y: Int
var box: Int
var hintList: [Int] = []
var guess: Bool = false
var description: String {
let string = String(num)
return string
}
}
It isn't enough to just add a description variable. You need to also state that your class conforms to CustomStringConvertible (formerly known as Printable in earlier Swift versions).
If you command click the print function, you find the following description.
Writes the textual representation of value, and an optional newline,
into the standard output.
The textual representation is obtained from the value using its protocol
conformances, in the following order of preference: Streamable,
CustomStringConvertible, CustomDebugStringConvertible. If none of
these conformances are found, a default text representation is constructed
in an implementation-defined way, based on the type kind and structure.
The part of which that matters here being that objects passed to print are not checked for whether or not they have a description method, but instead checked for things like whether or not the conform to protocols like CustomStringConvertible which offer data to be printed.
That being said, all you need to do in this case is specify that your class conforms to CustomStringConvertible since you've already added a description variable. If you hadn't already added this, the compiler would complain because this protocol requires that the description variable be implemented.
class Digit: CustomStringConvertible {
var num: Int
var x: Int
var y: Int
var box: Int
var hintList: [Int] = []
var guess: Bool = false
var description: String {
let string = String(num)
return string
}
}