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:
You can make the
bookArtwork
alazy
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.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:
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
How to Compile Latest Swift Version on Older Xcode
How to Use Unsafemutablerawpointer to Fill an Array
Rxswift/Rxcocoa: Prevent Uitextfield from Having More Than ... Characters
How to Set Uisegmentedcontrol Tint Color for Individual Segment
Access Properties via Subscripting in Swift
Mkpointannotations Touch Event in Swift
Swift 4 Timer Crashes with Nsexception
Error: Unable to Spawn Process (Argument List Too Long) in Xcode Build
How to Use JSON Arrays with Alamofire Parameters
iOS Swift: Video Thumbnail Error
How to Get All Days in Current Week in Swift
What Is the "@Exported" Attribute in Swift
Timedmetadata' Deprecated. Another Method? <Updated>
Pass Optional Block or Closure to a Function in Swift
What Is Other Option Available in Swift Instead of Refactoring and Renaming Class or Attribute Name
How to Determine If a Variable Passed in Is Reference Type or Value Type