简体   繁体   中英

Dynamic attributes (getters/setters) in Swift on data models

I'm trying to use Swift (which I'm very new at) to create a clean interface for my data models. These models are backed by Firebase (though this is ultimately irrelevant to the actual question). What I want to avoid is writing tons of setter/accessor boiler plate code over and over.

For example:

class MyData {
  let connection: Firebase!

  private var _name: String? // internal storage, like an iVar in ObjC

  init(connection: Firebase!) {
    self.connection = connection
    self.connection.observeEventType(.Value, withBlock: { snapshot in
      _name = snapshot["name"]
    }
  }

  var name: {
    get {
      return _name
    }
    set(name) {
      // When the client sets the name, write it to Firebase
      _name = name
      self.connection.childByAppendingPath("name").setValue(name)
    }
  }
}

I'm sure I'm making a lot of mistakes in there. The idea is that the data is first loaded from the server when the instance is instantiated. Subsequently, we could call my_data_instance.name to get that name, or my_data_instance.name = "foo" and the name would be automatically written to the server.

This requires ~10 lines of code for a single attribute (of which there will be many). Nuts! There must be a better way!


EDIT: to be clear, I want to obviate the need to write as MUCH boiler plate code as possible. Consider a library like Mantle , where merely defining a @property is sufficient to do everything you want. In my opinion, anything more than one single line of code to say I have an attribute called "name", handle it via Firebase is overly verbose.

You can use Key-Value Observing to monitor changes in your properties. More info in Adopting Cocoa Design Patterns in Swift .

import Foundation

private var KVOContext = 0

// Your class must inherit from NSObject
class MyData : NSObject {
    private let propertiesToObserve = ["name", "location"]

    dynamic var name: String
    dynamic var location: String

    init(name: String, location: String) {
        self.name = name
        self.location = location
        super.init()

        // Add the properties that you want to observe
        for property in self.propertiesToObserve {
            self.addObserver(self, forKeyPath: property, options: [.New, .Old], context: &KVOContext)
        }
    }

    // This method is called whenever an observed property is changed
    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        if let property = keyPath,
            newValue = change![NSKeyValueChangeNewKey],
            oldValue = change![NSKeyValueChangeOldKey] {
                print("\(property) changed from \(oldValue) to \(newValue)")
                // If oldValue != newValue, write back to Firebase
        }
    }

    // Remove self as observer of self
    deinit {
        for property in self.propertiesToObserve {
            self.removeObserver(self, forKeyPath: property)
        }
    }
}

let data = MyData(name: "John", location: "Chicago")
data.name = "David"         // print: name changed from John to David
data.location = "New York"  // print: location changed from Chicago to New York

Swift provides that functionality called property observer

var name: String {
   didSet {
     self.connection.childByAppendingPath("name").setValue(name)
   }
}

There is a second observer willSet which is called before the value is changed.

Note (from the documentation):

When you assign a default value to a stored property, or set its initial value within an initializer, the value of that property is set directly, without calling any property observers.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM