Search code examples
swiftdowncast

Downcasting Polymorphism and assignments


I have created a toy problem to understand why I am unable to set the value of a property after downcasting. The issue is in view controller #3: I am wanting to assign a value to property artist.

import Foundation

protocol MediaType {
    var year: String? { get set }
}

struct Song: MediaType {
    var artist: String?
    var musicians: [String]?

    // Implement Protocol `MediaType`
    var year: String?
}

class MediaStateController {
  var media: MediaType?
}

//
// view controller 1: controller does not know the media type just yet.
//
let stateController = MediaStateController() // media remains nil for now
print(stateController.media is Song) // false

//
// view controller 2: knows the media type so it can assign the media type.
//
stateController.media = Song()

print(stateController.media is Song) // true
print(stateController.media as? Song) // Optional(Song(artist: nil, musicians: nil, year: nil))
print(stateController.media) // Optional(Song(artist: nil, musicians: nil, year: nil))
print(type(of: stateController.media)) // Optional<MediaType>

// view controller 3: knows only the artist name but not the musicians. 
if var song = stateController.media as? Song {
    song.artist = "SomeArtist" // ISSUE: this does not set the value (see print-out below)
}

print(stateController.media as? Song) // Optional(Song(artist: nil, musicians: nil, year: nil))


if var song = stateController.media as? Song {
    // makes no sense to not use song, but let's do it anyway.
    // (stateController.media as? Song)?.artist = "SomeArtist" // error: cannot assign to immutable expression of type 'String?'
}
print(stateController.media as? Song) // Optional(=Song(artist: nil, musicians: nil, year: nil))

if var song = stateController.media as? Song {
      // THIS DOES SET THE VALUE, but why?
      song.artist = "SomeArtist" 
      stateController.media = song
    }
print(stateControllaer.media as? Song) // Optional(Song(artist: Optional("SomeArtist"), musicians: nil, year: nil))

Solution

  • Song is a struct, which is a value type in Swift. In this example:

    // view controller 3: knows only the artist name but not the musicians. 
    if var song = stateController.media as? Song {
        song.artist = "SomeArtist" // ISSUE: this does not set the value (see print-out below)
    }
    

    You're copying the original Song instance into a new var, then updating that copy's artist property. That copy is then thrown away once you exit the scope of your if var.