Cast between types using only protocols? - swift

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.

Related

Why does return <expr> work when let var = <expr>; return var give an error that it can't convert the type

I have the following code, which compiles & works fine:
import RealmSwift
struct Bucket: Codable, Identifiable {
var id: UUID
var title: String
init() {
self.id = UUID()
self.title = "new bucket"
}
init(title: String) {
self.id = UUID()
self.title = title
}
init(id: UUID, title: String) {
self.id = id
self.title = title
}
}
class RealmBucket : Object {
#Persisted var id : UUID
#Persisted var title : String
convenience init(_ id: UUID, _ title: String) {
self.init()
self.id = id
self.title = title
}
}
func loadBuckets() -> [Bucket] {
let realm = try! Realm()
let realmBuckets = realm.objects(RealmBucket.self)
return realmBuckets.map { Bucket(id: $0.id, title: $0.title) }
}
but if I change the loadBuckets() function to:
func loadBuckets() -> [Bucket] {
let realm = try! Realm()
let realmBuckets = realm.objects(RealmBucket.self)
let result = realmBuckets.map { Bucket(id: $0.id, title: $0.title) }
return result
}
(just the last line changed)
I get the error:
Cannot convert return expression of type 'LazyMapSequence<Results<RealmBucket>, Bucket>' to return type '[Bucket]'
If I change the let line to be:
let result : [Bucket] = realmBuckets.map { Bucket(id: $0.id, title: $0.title) }
then it works again.
I can think of a couple possible explanations for the behavior, but they all seem to point to a compiler bug, or language deficiency, and I'm guessing that perhaps there is some language feature I'm unaware of.
Does anyone know why the compiler is able to automatically convert the LazyMapSequence in the case of the return value being a variable, when it clearly knows the type of the variable given the error it is giving me. I'm relatively new to Swift, and hoping to learn something from this.
Based on the current answers, my assumption is that it is just a slightly different case in the compiler code to convert the variable versus a method call, so it's really just a compiler deficiency, and likely to not exist down the road. In any case, it's easy enough to work around, it just struck me as odd.
You have to explicitly define the return type of map function, when you use shorthand closure syntax, probably that is the reason.
A “return” statement knows the type it has to return, so if applies that knowledge when the right hand side is evaluated, and can often convert that result to the correct type.
A let statement with no type means the compiler doesn’t know which type is needed. So the right hand side can be evaluated differently.

Is there any way to make the method return a mutable value?

as shown in the code below:
struct Person {
var name: String
}
struct Group {
var person: Person
func callAsFunction() -> Person {
// Person is immutable value
person
}
}
var james = Person(name: "James")
var group = Group(person: james)
group().name = "Wong" //ERROR: Cannot assign to property: function call returns immutable value
group() return an immutable value, that can't be changed! So Is there any way to make the callAsFunction() method return a mutable value?
Thanks ;)
Updated:
My idea is to transfer all the calls and visits of the Group to the Person object in the Group, just like using Person directly.
I can't use dynamicMemberLookup because I don't know what method or property there will be in Person. For example, there may be 100 methods and properties in Person (not only one name property as demonstrated), and it is impossible for me to write 100 subscript methods with dynamicMemberLookup.
My needs are a bit like proxy objects in the Ruby language. Accessing an object (Group) actually accesses another object (Person) inside it, as if the Group does not exist.
ruby proxy patterns:
https://refactoring.guru/design-patterns/proxy/ruby/example
CallAsFunction is the closest implementation so far, but requires that Person cannot be a Struct, otherwise it cannot be assigned to its properties.
Maybe it's not possible to implement this feature in Swift yet?
You're using the wrong dynamic method. What you want is dynamicMemberLookup. Watch closely. First, the preparation:
struct Person {
var name: String
}
#dynamicMemberLookup
struct Group {
var person: Person
subscript(dynamicMember kp:WritableKeyPath<Person,String>) -> String {
get { self.person[keyPath:kp] }
set { self.person[keyPath:kp] = newValue }
}
}
Now look at what that allows you to say:
var group = Group(person: Person(name: "James"))
group.name = "Wong"
print(group.person) // Person(name: "Wong")
Do you see? We set the name of the Group even though it has no name property, and the result was that we set the name of the Group's person which does have a name property.
The callAsFunction simply returns (a copy of the) Person, which is a value type. You cannot then mutate the property of it like that. It is equivalent to the following:
struct Person {
var name: String
}
Person(name: "Foo").name = "Bar"
That returns the same error:
If Person was a reference type, it would have worked, but not for a value type. And even if you took your value type, and first assigned it to a variable before mutating it, you would only be mutating your copy, not the original.
If you want the behavior you want, you would use a #dynamicMemberLookup as suggested by matt (+1) and outlined in SE-0195.
You said:
I can't use dynamicMemberLookup because I don't know what method or property there will be in Person. For example, there may be 100 methods and properties in Person (not only one name property as demonstrated), and it is impossible for me to write 100 subscript methods with dynamicMemberLookup.
You do not need “100 subscript methods.” It is the motivating idea behind #dynamicMemberLookup, namely that the properties will be determined dynamically. E.g., here is Person with two properties, but Group only has the one #dynamicMemberLookup.
struct Person {
var name: String
var city: String
}
#dynamicMemberLookup
struct Group {
var person: Person
subscript(dynamicMember keyPath: WritableKeyPath<Person, String>) -> String {
get { person[keyPath: keyPath] }
set { person[keyPath: keyPath] = newValue }
}
}
var group = Group(person: Person(name: "James", city: "New York"))
group.name = "Wong"
group.city = "Los Angeles"
print(group.person) // Person(name: "Wong", city: "Los Angeles")
If you want to handle different types, make it generic:
struct Person {
var name: String
var city: String
var age: Int
}
#dynamicMemberLookup
struct Group {
var person: Person
subscript<T>(dynamicMember keyPath: WritableKeyPath<Person, T>) -> T {
get { person[keyPath: keyPath] }
set { person[keyPath: keyPath] = newValue }
}
}
And
var group = Group(person: Person(name: "James", city: "New York", age: 41))
group.name = "Wong"
group.city = "Los Angeles"
group.age = 42
print(group.person) // Person(name: "Wong", city: "Los Angeles", age: 42)

How can I implement a class structure that allows to an instance of a subclass to change to an instance of another subclass of the same class

I'm trying an item object that can be a note (just a string of text as property) or a task, so having a status, priority,a due date, etc. etc. as additional properties.
It would be possible transforming at runtime a note item in a task item and vice-versa.
I'm thinking how to implement this, I started defining a class structure having "item" as principal class and two subclasses (note and task) but I can't understand how managing the transition between the two subclasses
Can you help me?
This is not possible in Swift, at least not in any way that you'd use as a general tool. (It is possible in ObjC via isa-swizzling, and that can be bridged into Swift, but even in ObjC you'd never want to use it for this kind of problem. It's a very tricky tool, not a general purpose solution to types.)
Instead of complex class substitution, you want to redesign this. There are several approaches. You can just convert types (see the init(copying:) methods):
protocol Item {
var id: UUID { get }
var name: String { get set }
var contents: String { get set }
}
struct Note: Item {
let id: UUID
var name: String
var contents: String
init(copying item: Item) {
self.id = item.id
self.name = item.name
self.contents = item.contents
}
}
struct Task: Item {
let id: UUID
var name: String
var contents: String
var complete: Bool = false
var dueDate: DateComponents?
init(copying item: Item) {
self.id = item.id
self.name = item.name
self.contents = item.contents
}
}
Or, you can separate out additional properties and swap them (this approach is ideal if Item is a class, but I'd highly recommend trying to make Item a struct).
protocol ItemProperties{}
struct Item {
let id: UUID
var name: String
var contents: String
var properties: ItemProperties
}
// No additional properties for notes
struct Note: ItemProperties {}
struct Task: ItemProperties {
var complete: Bool = false
var dueDate: DateComponents?
}
var item = Item(id: UUID(), name: "", contents: "", properties: Note())
// convert to task
item.properties = Task(complete: false, dueDate: nil)
// Convert back to note
item.properties = Note()
That said, if you only have Tasks and Notes, I'd avoid overthinking this until you have a clear sense of what you'd add in the future. I'd consider just a simple struct with a type enum:
enum ItemType {
case note
case task(complete: Bool, due: DateComponents)
}
struct Item {
let id: UUID
var name: String
var contents: String
var type: ItemType
}
You can access these values using switch or if case let:
switch testItem2.type {
case let .task(complete: complete, due: due):
// Handle complete and due
break
case .note:
break
}
if case let .task(complete: complete, due: due) = testItem2.type {
// Handle complete or due
}
Sometimes it's convenient to create optional methods:
extension Item {
var complete: Bool? {
if case let .task(complete: complete, due: _) = type {
return complete
}
return nil
}
}
if let complete = testItem2.complete, complete {
// If this is a task and complete
}
The syntax around enums is sometimes a little clunky. If you have more than a couple of properties for the associated value, I highly recommend wrapping them up in a struct:
enum ItemType {
case note
case task(TaskProperties)
}
struct TaskProperties {
var complete: Bool
...
}
Barring a few tricks used in Objective C, a Swift instance can’t change it’s type at runtime. For one, different instances have different sizes, so there’s no good way to really make that work.
What you’re reaching for is something similar to SmallTalk’s become: message, which you can send to an object in order to get all references to it, program-wide, to be replaced with a reference to some other object instead. It’s cool, but leads to some confusing designs, and has some pretty brutal performance implications. You don’t really want this.
The exact best solution would depend on your specific use case. You haven’t provided much detail. The obvious/simple option is for each subclass to have an initializer that lets you create e.g. a new note from a task.
However, this won’t automatically update all references to the old note to point to the new task. If that’s something you need, you would need to add a layer of indirection yourself, where all item references point to items (which Item no longer has any subclasses), which themselves contain ItemDetails (or something like that) which are subclassed by NoteDetails and TaskDetails. Within the Item, the details can be switched back and forth between being a note or being a task, while all outstanding references to the wrapping item remain valid.
An alternative approach is by using a mixed design, of both structs and enums:
enum Item {
case note(Note)
case task(Task)
}
struct Note {
var text: String
}
struct Task {
var title: String
var status: Status
var priority: Priority
var dueDate: Date?
// ... other props
}
Switching between Note and Task would then be a simple matter of replacing self within the enum:
extension Task {
init(note: Note) {
self.init(title: note.text, status: .pending, priority: .low, dueDate: nil, ...)
}
}
extension Item {
mutating func convertToTask() {
guard case let .note(note) = self else {
// already a task, do nothing
// alternatively, you can throw an error
return
}
self = .task(Task(note: note))
}
}
// look, I have a `Note`
var item: Item = .note(Note(text: "something to do"))
// but now I have a `Task` :)
item.convertToTask()

Extending a constrained protocol for an array argument is not possible

I'm going to explain it by an example. We have a protocol for force having firstName and lastName like:
protocol ProfileRepresentable {
var firstName: String { get }
var lastName: String { get }
}
the type we are going to use have these two, but in an optional form:
struct Profile {
var firstName: String?
var lastName: String?
}
so after conforming to the ProfileRepresentable, we will extend the ProfileRepresentable and try to return the value and a default one for nil state:
extension Profile: ProfileRepresentable { }
extension ProfileRepresentable where Self == Profile {
var firstName: String { self.firstName ?? "NoFirstName" }
var lastName: String { self.lastName ?? "NoLastName" }
}
So far so good
Now there is a similar flow for a list of Profiles.
protocol ProfilerRepresentable {
var profiles: [ProfileRepresentable] { get }
}
struct Profiler {
var profiles: [Profile]
}
First issue
conforming to ProfilerRepresentable does NOT automatically done the implementation as expected (since Profile already conforms to ProfileRepresentable)
extension Profiler: ProfilerRepresentable { }
Second Issue
Following the previous pattern, extending ProfilerRepresentable is not working as expected and it raises a warning:
⚠️ All paths through this function will call itself
extension ProfilerRepresentable where Self == Profiler {
var profiles: [ProfileRepresentable] { self.profiles }
}
How can I achieve the goal for arrays by the way ?
Here is possible solution. Tested with Xcode 12 / swift 5.3
protocol ProfilerRepresentable {
associatedtype T:ProfileRepresentable
var profiles: [T] { get }
}
extension Profiler: ProfilerRepresentable { }
struct Profiler {
var profiles: [Profile]
}
[Profile] is not a subtype of [ProfileRepresentable]. (See Swift Generics & Upcasting for a related but distinct version of this question.) It can be converted through a compiler-provided copying step when passed as a parameter or assigned to a variable, but this is provided as a special-case for those very common uses. It doesn't apply generally.
How you should address this depends on what precisely you want to do with this type.
If you have an algorithm that relies on ProfilerRepresentable, then Asperi's solution is ideal and what I recommend. But going that way won't allow you to create a variable of type ProfileRepresentable or put ProfileRepresentable in an Array.
If you need variables or arrays of ProfilerRepresentable, then you should ask yourself what these protocols are really doing. What algorithms rely on these protocols, and what other reasonable implementations of ProfileRepresentable really make sense? In many cases, ProfileRepresentable should just be replaced with a simple Profile struct, and then have different init methods for creating it in different contexts. (This is what I recommend if your real problem looks a lot like your example, and Asperi's answer doesn't work for you.)
Ultimately you can create type erasers (AnyProfile), but I suggest exploring all other options (particularly redesigning how you do composition) first. Type erasers are perfect if your goal is to erase a complicated or private type (AnyPublisher), but that generally isn't what people mean when they reach for them.
But designing this requires knowing a more concrete goal. There is no general answer that universally applies.
Looking at your comments, there no problem with having multiple types for the same entity if they represent different things. Structs are values. It's fine to have both Double and Float types, even though every Float can also be represented as a Double. So in your case it looks like you just want Profile and PartialProfile structs, and an init that lets you convert one to the other.
struct Profile {
var firstName: String
var lastName: String
}
struct PartialProfile {
var firstName: String?
var lastName: String?
}
extension Profile {
init(_ partial: PartialProfile) {
self.firstName = partial.firstName ?? "NoFirstName"
self.lastName = partial.lastName ?? "NoLastName"
}
}
extension PartialProfile {
init(_ profile: Profile) {
self.firstName = profile.firstName
self.lastName = profile.lastName
}
}
It's possible that you have a lot of these, so this could get a bit tedious. There are many ways to deal with that depending on exactly the problem you're solving. (I recommend starting by writing concrete code, even if it causes a lot of duplication, and then seeing how to remove that duplication.)
One tool that could be useful would be Partial<Wrapped> (inspired by TypeScript) that would create an "optional" version of any non-optional struct:
#dynamicMemberLookup
struct Partial<Wrapped> {
private var storage: [PartialKeyPath<Wrapped>: Any] = [:]
subscript<T>(dynamicMember member: KeyPath<Wrapped, T>) -> T? {
get { storage[member] as! T? }
set { storage[member] = newValue }
}
}
struct Profile {
var firstName: String
var lastName: String
var age: Int
}
var p = Partial<Profile>()
p.firstName = "Bob"
p.firstName // "Bob"
p.age // nil
And a similar converter:
extension Profile {
init(_ partial: Partial<Profile>) {
self.firstName = partial.firstName ?? "NoFirstName"
self.lastName = partial.lastName ?? "NoLastName"
self.age = partial.age ?? 0
}
}
Now moving on to your Array problem, switching between these is just a map.
var partials: [Partial<Profile>] = ...
let profiles = partials.map(Profile.init)
(Of course you could create an Array extension to make this a method like .asWrapped() if it were convenient.)
The other direction is slightly tedious in the simplest approach:
extension Partial where Wrapped == Profile {
init(_ profile: Profile) {
self.init()
self.firstName = profile.firstName
self.lastName = profile.lastName
self.age = profile.age
}
}
If there were a lot of types, it might be worth it to make Partial a little more complicated so you could avoid this. Here's one approach that allows Partial to still be mutable (which I expect would be valuable) while also allowing it to be trivially mapped from the wrapped instances.
#dynamicMemberLookup
struct Partial<Wrapped> {
private var storage: [PartialKeyPath<Wrapped>: Any] = [:]
private var wrapped: Wrapped?
subscript<T>(dynamicMember member: KeyPath<Wrapped, T>) -> T? {
get { storage[member] as! T? ?? wrapped?[keyPath: member] }
set { storage[member] = newValue }
}
}
extension Partial {
init(_ wrapped: Wrapped) {
self.init()
self.wrapped = wrapped
}
}
I don't love this solution; it has a weird quirk where partial.key = nil doesn't work to clear a value. But I don't have a nice fix until we get KeyPathIterable. But there are some other routes you could take depending on your precise problem. And of course things can be simpler if Partial isn't mutable.
The point is that there's no need for protocols here. Just values and structs, and convert between them when you need to. Dig into #dynamicMemberLookup. If your problems are very dynamic, then you may just want more dynamic types.

Values of structs changing when appending to array with protocol type

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.