Swift Property Observer in Protocol Extension

Swift property observer in protocol extension?

No, this is explicitly disallowed. See Extension: Computed Properties:

Extensions can add new computed properties, but they cannot add stored properties, or add property observers to existing properties.

Keep in mind that if this were legal, it would add some non-trivial confusion about order of execution. Imagine there were several extensions that added didSet, and the actual implementation also had a didSet. What order should they run in? This doesn't mean it's impossible to implement, but it could be somewhat surprising if we had it.

Swift - Protocol extensions - Property default values

It seems you want to add a stored property to a type via protocol extension. However this is not possible because with extensions you cannot add a stored property.

I can show you a couple of alternatives.

Subclassing (Object Oriented Programming)

The easiest way (as probably you already imagine) is using classes instead of structs.

class IdentifiableBase {
var id = 0
var name = "default"
}

class A: IdentifiableBase { }

let a = A()
a.name = "test"
print(a.name) // test

Cons: In this case your A class needs to inherit from IdentifiableBase and since in Swift theres is not multiple inheritance this will be the only class A will be able to inherit from.

Components (Protocol Oriented Programming)

This technique is pretty popular in game development

struct IdentifiableComponent {
var id = 0
var name = "default"
}

protocol HasIdentifiableComponent {
var identifiableComponent: IdentifiableComponent { get set }
}

protocol Identifiable: HasIdentifiableComponent { }

extension Identifiable {
var id: Int {
get { return identifiableComponent.id }
set { identifiableComponent.id = newValue }
}
var name: String {
get { return identifiableComponent.name }
set { identifiableComponent.name = newValue }
}
}

Now you can make your type conform to Identifiable simply writing

struct A: Identifiable {
var identifiableComponent = IdentifiableComponent()
}

Test

var a = A()
a.identifiableComponent.name = "test"
print(a.identifiableComponent.name) // test

Adding property observer to Decodable protocol in Swift 4

Below I will enumerate a few ways of handling this sort of scenario, but the right answer is that you simply should not be retrieving images during the parsing of the JSON. And you definitely shouldn't be doing this synchronously (which is what Data(contentsOf:) does).

Instead, you should should only retrieve the images as they're needed by the UI. And you want to retrieve images into something that can be purged in low memory scenarios, responding to the .UIApplicationDidReceiveMemoryWarning system notification. Bottom line, images can take a surprising amount of memory if you're not careful and you almost always want to decouple the images from the model objects themselves.


But, if you're absolutely determined to incorporate the image into the Book object (which, again, I'd advise against), you can either:

  1. You can make the bookArtwork a lazy variable:

    struct Book: Decodable {
    let author: String
    lazy var bookArtwork: UIImage = {
    let data = try! Data(contentsOf: artworkURL)
    return UIImage(data: data)!
    }()
    let artworkURL: URL
    let genres: [Genre]
    let name: String
    let releaseDate: String
    }

    This is horrible pattern in this case (again, because we should never do synchronous network call), but it illustrates the idea of a lazy variable. You sometimes do this sort of pattern with computed properties, too.

  2. Just as bad (for the same reason, that synchronous network calls are evil), you can also implement a custom init method:

    struct Book: Decodable {
    let author: String
    let bookArtwork: UIImage
    let artworkURL: URL
    let genres: [Genre]
    let name: String
    let releaseDate: String

    init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    author = try values.decode(String.self, forKey: .author)
    artworkURL = try values.decode(URL.self, forKey: .artworkURL)
    genres = try values.decode([Genre].self, forKey: .genres)
    name = try values.decode(String.self, forKey: .name)
    releaseDate = try values.decode(String.self, forKey: .releaseDate)

    // special processing for artworkURL
    let data = try Data(contentsOf: artworkURL)
    bookArtwork = UIImage(data: data)!
    }

    enum CodingKeys: String, CodingKey {
    case author, artworkURL, genres, name, releaseDate
    }
    }

    In fact, this is even worse than the prior pattern, because at least with the first option, the synchronous network calls are deferred until when you reference the UIImage property.

    But for more information on this general pattern see Encode and Decode Manually in Encoding and Decoding Custom Types.

I provide these two above patterns, as examples of how you can have some property not part of the JSON, but initialized in some other manner. But because you are using Data(contentsOf:), neither of these are really advisable. But they're good patterns to be aware of in case the property in question didn't require some time consuming synchronous task, like you do here.

In this case, I think it's simplest to just provide a method to retrieve the image asynchronously when you need it:


  1. Eliminate the synchronous network call altogether and just provide an asynchronous method to retrieve the image:

    struct Book: Decodable {
    let author: String
    let artworkURL: URL
    let genres: [Genre]
    let name: String
    let releaseDate: String

    func retrieveImage(completionHandler: @escaping (UIImage?, Error?) -> Void) {
    let task = URLSession.shared.dataTask(with: artworkURL) { data, _, error in
    guard let data = data, error == nil else {
    completionHandler(nil, error)
    return
    }
    completionHandler(UIImage(data: data), nil)
    }
    task.resume()
    }
    }

    Then, when you need the image for your UI, you can retrieve it lazily:

    book.retrieveImage { image, error in
    DispatchQueue.main.async { image, error in
    cell.imageView.image = image
    }
    }

Those are a few approaches you can adopt.

Is it possible to set observer(KVO) for a protocol instance in Swift

The didSet approach should work fine actually, had no issue with the following code in a playground (Xcode 7 GM):

class DTNavigationController : UINavigationController {
var dataSource: DTNavigationControllerDataSource? {
didSet {
print("new data source: \(dataSource)")
}
}
}

Add didSet observer on a weak property in an extension

You can't override delegate property using extension, you need to create subclass:

class TextField: UITextField {
override weak var delegate: UITextFieldDelegate? {
didSet {
super.delegate = delegate
print("Do stuff")
}
}
}

But this seems kinda wrong. What are you trying to achieve?

Which is an appropriate protocol constraint to identify a property as being '@Published'

As far as I could replicate your code here is a solution (tested with Xcode 12.1 / iOS 14.1)

extension Diskable where T: Identifiable {
func binding(for object: T) -> Binding<T> {
let index = fetchedItems.firstIndex(where: { $0.id == object.id } )!
return Binding(
get: { self.fetchedItems[index] },
set: { self.fetchedItems[index] = $0 }
)
}
}


Related Topics



Leave a reply



Submit