I'm using a computed property to get the last book in my books array. However, it seems I can't use this property to directly set a book's position property, as my attempt below shows:
struct Book {
var position: CGPoint?
}
class ViewController: UIViewController {
var books = [Book]()
var currentBook: Book {
return books[books.count - 1]
}
func setup() {
// Compiler Error: Cannot assign to property: 'currentBook' is a get-only property
currentBook.position = CGPoint.zero
}
}
The following works but I'd like it to be more readable and a single line.
books[books.count - 1].position = CGPoint.zero
I could use a function to return the current book but using a property would be cleaner. Is there another way?
The error occurs because you did not tell the compiler what to do if the value of currentBook is mutated. The compiler assumes it is immutable.
Just add a setter so that the compiler knows what to do when you set the value:
var currentBook: Book {
get { return books[books.count - 1] }
set { books[books.count - 1] = newValue }
}
Or, consider using books.last!:
books.last!.position = CGPoint.zero
Related
I'm trying to implement getters/setters for properties. In the computed property, I'm returning the _locationManager, but also initializing the first time only:
var _locationManager: CLLocationManager? { get set }
var locationManager: CLLocationManager {
if let manager = _locationManager {
return manager
}
_locationManager = CLLocationManager()
return _locationManager!
}
Is there a more Swift-like version of the above statement? For example, in C# I can do something like this:
var locationManager: CLLocationManager {
return _locationManager ?? (_locationManager = CLLocationManager())
}
This is saying return _locationManager, or initialize plus return it. Does Swift have some kind of shorthand or another way for this scenario?
Update:
Below is another version. I'm trying to stick with a computed property so it can be used in protocol extensions and/or able to wire up a delegate in the process etc:
var locationManager: CLLocationManager {
return _locationManager ?? {
self._locationManager = CLLocationManager()
return self._locationManager!
}()
}
you could use a lazy property for this:
lazy var locationManager: CLLocationManager = CLLocationManager()
a lazy property gets initialized only when it is first used.
If you're really desperate to, you can do something like you're asking, but it's not terribly pretty. However, in the example you quoted, you should use lazy instantiation as Palle answered.
class Test {
}
var _test: Test?
var test: Test {
get {
return _test ?? { _test = Test() ; return _test! }()
}
}
Declaring a closure like this gets you the 'cute' one line getter, but I would leave it as you've got it because it's clearer.
With the help of Reflection API I'm getting the properties list for my types.
func inspectedProperties(ignored: [String] = []) -> [Property] {
var properties = [String]()
for child in self.children() {
guard let label = child.label else {
continue
}
properties += [label]
}
return properties.filter { !ignored.contains($0) }
}
This function returns me the names for all properties.
Now I want to mutate a certain property just by knowing its name.
class Fruit {
private dynamic var name = "Apple"
}
If I call Fruit().inspectedProperties() I'll get the following array ["name"].
But is it possible to mutate the variable named "name"?
OK, I found a very simple solution but it is not flexible. Actually, you can use KVO to mutate your data types. For this purpose your models should be subclasses of NSObject to enable KVO features and variables marked as dynamic.
P.S.
typealias Property = String
class Fruit: NSObject {
private dynamic var name = "Apple"
}
Then there is the mutating function.
func mutateProperty<T>(property: Property) -> T -> () {
return { value in
let filtered = self.children().filter { label, value in
if let label = label where label == property {
return true
}
return false
}
guard let child = filtered.first else {
return
}
if let object = self as? NSObject where child.value is T {
object.setValue(value as? AnyObject, forKey: property)
}
}
}
Then try out:
let fruit = Fruit()
fruit.mutateProperty("name")("Google")
print(fruit.name) // "Google"
It works, but if you want to work with value types rather than with reference ones it won't work. There might be some low level solution but I'm not familiar with one. If anyone knows how to, please leave your answer here! :)
I have a simple class below
import Foundation
public class UsefulClass: NSObject{
var test:NSNumber{
get{return self.test}
set{
println(newValue)
self.test = newValue
}
}
override init() {
super.init()
self.test = 5;
}
}
and I'm initializing it here
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var testClass = UsefulClass()
}
}
But it results in xcode printing out 200 5s and then crashing due to EXC_BAD_ACCESS code = 2. Why does this happen?
#vadian has provided a solution in his answer, which should fix your problem. Let me just explain what's happening.
You have created a computed property, i.e. a property which is not backed by a variable, instead both the getter and the setter do some processing, usually on another stored property, in order to respectively return a value and set a new value.
This is your computed property:
var test: NSNumber {
get { return self.test }
set {
println(newValue)
self.test = newValue
}
}
Look at the getter implementation:
return self.test
What does it do? It reads the test property of the current instance, and returns it. Which is the test property? It's this one:
var test: NSNumber {
get { return self.test }
set {
println(newValue)
self.test = newValue
}
}
Yes, it's the same property. What your getter does is to recursively and indefinitely calling itself, until a crash happen at runtime.
The same rule applies to the setter:
self.test = newValue
it keeps invoking itself, until the app crashes.
Swift variables are synthesized properties by default.
In the most cases this is sufficient (it's recommended to prefer Swift types)
var test: Int
override init() {
super.init()
test = 5
}
If you need to do something after a variable is set, use
var test: Int {
didSet{
println("\(oldValue) - \(newValue)")
}
}
your code sets the variable permanently by calling the setter which calls the setter which …
It's an infinite loop; your setter is recursively calling itself.
var test: NSNumber {
set {
test = newValue
}
}
This compiles fine, and an Objective-C programmer might expect no loop due to instead setting a "backing ivar" such as _test rather than re-calling the setter method.
But property-backing instance variable _ivars do not exist in Swift for computed properties unless you create them yourself.
I am converting a project in to Swift code and have come across an issue in a setter. My Objective-C code looked like this:
- (void)setDocument:(MyDocument *)document
{
if (![_document isEqual:document]) {
_document = document;
[self useDocument];
}
}
and allowed my View Controller to run this each time the document was set (typically in the prepareForSegue: method of the presenting View Controller).
I have found the property observers willSet and didSet but they only work when the property is being updated, not when it’s initialised and updated.
Any ideas? Thanks
UPDATE
after trying get{} and set{} I get the EXC_BAD_ACCESS error
var document: UIDocument? {
get {
return self.document!
}
set {
self.document = newValue
useDocument()
}
}
You can't use set like that because when you call self.document = newValue you're just calling the setter again; you've created an infinite loop.
What you have to do instead is create a separate property to actually store the value in:
private var _document: UIDocument? = nil
var document: UIDocument? {
get {
return self._document
}
set {
self._document = newValue
useDocument()
}
}
Here's a Swift 3 version
var document : UIDocument? {
didSet {
useDocument()
}
}
I want to implement my custom MKAnnotation. I took a look at MKAnnotation protocol(MKAnnotation.h).
It's as follow:
//
// MKAnnotation.h
// MapKit
//
// Copyright (c) 2009-2014, Apple Inc. All rights reserved.
//
protocol MKAnnotation : NSObjectProtocol {
// Center latitude and longitude of the annotation view.
// The implementation of this property must be KVO compliant.
var coordinate: CLLocationCoordinate2D { get }
// Title and subtitle for use by selection UI.
#optional var title: String! { get }
#optional var subtitle: String! { get }
// Called as a result of dragging an annotation view.
#optional func setCoordinate(newCoordinate: CLLocationCoordinate2D)
}
Please note the coordinate property (which is a read-only stored property).
And here is how I've implemented this protocol:
class RWDefaultPin: NSObject, MKAnnotation {
var title:String = ""
var subtitle:String = ""
var groupTag:String = ""
var coordinate: CLLocationCoordinate2D { get {
return self.coordinate // this is obviously wrong because property's trying to return itself
} };
init(coordinate:CLLocationCoordinate2D) {
super.init()
self.coordinate = coordinate
}
}
But obviously compiler complaints on my init method where I'm trying to assign to my coordinate property Cannot assign to 'coordinate' in 'self' obviously because it's a read-only property.
Previously in Objective-C we could overcome this issue as properties were backed by ivars.
I wish there was access modifier in Swift so I could define a private property in my class and set its value on init, and returning its value on get action of coordinate, but there is no such thing!
I don't quiet know how to fix this issue in Swift, or maybe I need to make it wide open and change my coordinate to be readable/writable?
You should be able to just add a setter to it and store the information in an inner coordinate value. Since you have a getter it is still conforming to the protocol:
var innerCoordinate: CLLocationCoordinate2D
var coordinate: CLLocationCoordinate2D {
get {
return self.innerCoordinate
}
set {
self.innerCoordinate = newValue
}
};
init(coordinate:CLLocationCoordinate2D) {
super.init()
self.innerCoordinate = coordinate
}
This is actually how I implement readonly and private properties (with protocols and the factory pattern). I setup protocols with the public interface and classes with private variables and setters. It is actually super clean way to setup your code (and gets around the lack of protected/private properties in Swift).
Here is a abstracted example of what I am talking about (if you care):
// this is your MKAnnotation in this example
protocol SomeProtocol {
var getterProperty: String { get }
var setterProperty: String { set get }
func publicFunction(someStirng: String) -> ();
}
// setup a function that returns a class conforming to your needed protocol
func SomeClassMaker() -> SomeProtocol {
// your internal class that no one can access unless by calling the maker function
class SomeClassInternal: NSObject, SomeProtocol {
// private and no one can get to me!
var innerSetterProperty = "default setter";
var getterProperty = "default getter"
var setterProperty: String {
get {
return self.innerSetterProperty;
}
set {
"hit"
self.innerSetterProperty = newValue
}
}
func publicFunction(someString: String) -> () {
// anyone get me
self.getterProperty = someString;
}
func privateFunction() -> () {
// no one can get me except internal functions
}
}
return SomeClassInternal();
}
// create the class
var classInstance = SomeClassMaker();
// totally fine!
classInstance.setterProperty = "secret string"
// prints "secret string"
classInstance.setterProperty;
// error! no public setter for "getter"
classInstance.getterProperty = "another secret"
classInstance.publicFunction("try secret again")
// prints "try secret again"
let blahed = classInstance.getterProperty
// error!
classInstance.privateFunction()
Even though the property is { get } in the protocol, that is just establishing a minimum criteria. It's perfectly acceptable to define it as read-write:
class MyAnnotation:NSObject, MKAnnotation
{
var coordinate:CLLocationCoordinate2D
init(coordinate:CLLocationCoordinate2D) {
self.coordinate = coordinate
}
}
Or, if you really want to keep it as read-only, you can use let:
class MyAnnotation:NSObject, MKAnnotation
{
let coordinate:CLLocationCoordinate2D
init(coordinate:CLLocationCoordinate2D) {
self.coordinate = coordinate
}
}