Set value of read-only stored property during initializing in Swift - swift

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
}
}

Related

Why is conformance to an object required for read/write extension properties to work on let variables?

I know the title may be confusing, but this should clear it up.
Say I define the following extension on UIView...
extension UIView {
var isVisible:Bool {
get { return !isHidden }
set { isHidden = !newValue }
}
}
In code, I can do this without issue...
let myView = UIView()
myView.isVisible = true
But if I try pulling out the extension into a reusable protocol (so I can apply it to both UIView and NSView without having to duplicate the code) like so...
public protocol ExtendedView {
var isHidden: Bool { get set }
}
public extension ExtendedView {
var isVisible: Bool {
get { return !isHidden }
set { isHidden = !newValue }
}
}
extension UIView: ExtendedView {}
extension NSView: ExtendedView {}
...then while I can read it like so...
let myView = UIView()
if myView.isVisible {
....
}
...This line will not compile!
myView.isVisible = true
It gives the following compile-time error...
cannot assign to property: 'myView' is a 'let' constant
To fix it, I have to either change the variable to a var (not what I want to do), or conform the protocol to AnyObject, like so...
public protocol ExtendedView : AnyObject {
var isHidden: Bool { get set }
}
My question is why? I mean the compiler knows at compile time the type of item the extension is being applied to so why does the protocol have to conform to AnyObject? (Yes, I do acknowledge that extending UIView (or NSView) implies an object, but still... doesn't the call site know it's not a value type?)
doesn't the call site know it's not a value type?
That doesn't matter. Protocol members allows for mutation of self. For example, if you don't constrain the protocol to AnyObject, this will always compile:
set { self = newValue as? Self ?? self }
I.e. protocols provide the only way to be able to change a reference internally. Even though you're not actually doing that in your code, the possibility of the reference mutation is there.
And even if you don't actually cause any mutation, property observers are still going to be triggered by mutating protocol members.
var myView = UIView() {
didSet {
print("Still the same \(myView) after `isVisible` changes, but that's not provable at compile-time.")
}
}
Your particular issue is due to the default of set accessors.
{ get set }
is shorthand for
{ nonmutating get mutating set }
If you change the get to be mutating as well, you'll run into the same issue.
public protocol ExtendedView {
var isHidden: Bool { get }
}
public extension ExtendedView {
var isVisible: Bool {
mutating get { !isHidden }
}
}
// Cannot use mutating getter on immutable value: 'myView' is a 'let' constant
let myView = UIView()
myView.isVisible
I have to either change the variable to a var (not what I want to do), or conform the protocol to AnyObject
Although it's not apparent why you shouldn't be constraining to AnyObject or something more restrictive, you can just use
var isHidden: Bool { get nonmutating set }
That's enough to be able to make myView a constant. However, it's more accurate to mark isVisible completely nonmutating as well, which will stop property observers triggering.
nonmutating set { isHidden = !newValue }
Ultimately, constraining as much as possible is going to make working with any protocol easier. Especially when it allows you to enforce reference semantics.
public enum OldUIFramework { }
#if os(macOS)
import AppKit
public extension OldUIFramework {
typealias View = NSView
}
#else
import UIKit
public extension OldUIFramework {
typealias View = UIView
}
#endif
extension OldUIFramework.View: ExtendedView { }
public protocol ExtendedView: OldUIFramework.View {
var isHidden: Bool { get set }
}
If you really need ExtendedView to apply to value types sometimes, then make a constrained extension for the other cases, calling the value type code.
any should be some, here, but the compiler has bugs that make it not work right now.
public extension ExtendedView where Self: OldUIFramework.View {
var isVisible: Bool {
get {
let `self`: any ExtendedView = self
return `self`.isVisible
}
nonmutating set {
var `self`: any ExtendedView = self
`self`.isVisible = newValue
}
}
}
I mean the compiler knows at compile time the type of item the extension is being applied to
I know, it looks like the compiler knows that, especially when you write the lines next to each other like that:
let myView = UIView()
myView.isVisible = true
But command-click on isVisible in that code, and where do you end up? In the protocol ExtendedView. In other words, isVisible is not ultimately a property declared by UIView; it's a property declared by ExtendedView.
And nothing about the protocol itself guarantees that the adopting object will be a reference type — unless you guarantee it by qualifying the protocol, either directly or in an extension of the protocol, by saying what kind of object can adopt it.
I would just like to add that the situation you've posited is extremely specialized: the issue only arises in exactly the situation you've created, where a protocol extension injects a computed property implementation into its adopters. That's not a common thing to do.

Extend Private class defined in an extension of class in Swift

TL;DR
Is it possible to extend a privately owned and defined-in-extension class, i.e. NewsParser?
Related documents
swift2 - Extension of a nested type in Swift - Stack Overflow talks about similar situation, except the nested class type is not private.
I have a class NewsPost:
class NewsPost {
var title: String?
var author: String?
var mainContent: NSAttributedString?
var data: Data? {
didSet {
let newsParser = NewsParser(delegate: self)
newsParser.parse()
}
}
// Init methods and other stuff...
}
And a NewsPost-owned class NewsParser: (in another Swift file, but this does not seem to be a factor, due to SR-631)
private extension NewsPost {
private class NewsParser {
weak var delegate: NewsPost?
// Other properties for parsing...
init(delegate: NewsPost) {
self.delegate = delegate
}
func parse() {
// parse the delegate.data and update properties in delegate (NewsPost instance)
}
// Other methods to be called for parsing...
}
}
But it does not seem to possible to extend NewsPost.NewsParser.
The following attempts do not work:
Attempt 1
Error: 'NewsParser' is inaccessible due to 'fileprivate' protection level
private extension NewsPost { // Notice the "private" prefix
class NewsParser {
weak var delegate: NewsPost?
//Other properties for parsing...
init(delegate: NewsPost) {
self.delegate = delegate
}
func parse() {
// parse the delegate.data and update properties in delegate (NewsPost instance)
}
// Other methods to be called for parsing...
}
}
Error happens in NewsPost definition:
var data: Data? {
didSet {
let newsParser = NewsParser(delegate: self) // error happens here
newsParser.parse()
}
}
Attempt 2
Error: 'NewsParser' is inaccessible due to 'private' protection level
extension NewsPost {
private class NewsParser { // Notice the "private" prefix
var delegate: NewsPost
// Other properties for parsing...
func parse() {
// parse the delegate.data and update properties in delegate (NewsPost instance)
}
// Other methods to be called for parsing...
}
}
extension NewsPost.NewsParser { // error happens here
// extensions here...
// many kinds of errors happen here
}
Is it possible to extend a privately owned and defined-in-extension class, i.e. NewsParser?
I tried your code in a playground and it worked like a charm with a private class nested in a private extension :
Called that way :
var str = "Hello, playground"
let post = NewsPost()
post.data = str.data(using: .utf8)
Your main problem is that your probably declared your private extension in a separate file and private means fileprivate for an extension. Put your extension and your NewsPostclass in the same file and your error should go away!
If you really want to extend NewsParser you have to make it internal.
Extension declaration are only valid at file scope so if you create a private class you have no way of extending it.
Note that an internal nested class would not be visible outside its target. So using Frameworks you should be able to hide your NewsParser class from your UI code.

Cannot assign to property: 'xxxx' is a get-only property

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

Extension property not working as instance property

I have created an protocol extension of UIImageView and added a bool property isFlipped to the extension. My problem is that if I set isFlipped true of false for one object is sets the same values for the all the UIImageView objects. Can anyone guide how to handle it separately for each UIImageView objects.
protocol FlipImage {
var isFlipped: Bool { get set }
}
var flippedValue = false
extension UIImageView:FlipImage{
var isFlipped: Bool {
get {
return flippedValue
}
set {
flippedValue = newValue
}
}
}
If you want to add stored properties using extensions, here's a little trick I used that allows you to do it for base classes that you can override (namely view controllers, which is the one I used it for):
This protocol allows a base class to be extended with stored properties in extensions and in protocols:
protocol ExtensibleObject:class
{
var extendedProperties:[String:Any] { get set }
}
extension ExtensibleObject
{
func get<T>(_ defaultValue:T, _ file:String = #file, _ line:Int = #line) -> T
{
return (extendedProperties["\(file):\(line)"] as? T) ?? defaultValue
}
func set<T>(_ newValue:T, _ file:String = #file, _ line:Int = #line)
{
return extendedProperties["\(file):\(line)"] = newValue
}
}
To use the protocol, you need to create a subclass of the base class to add storage for all extended properties (for the class and all its sub classes).
class ExtensibleViewController:UIViewController, ExtensibleObject
{
var extendedProperties:[String:Any] = [:]
}
Note that you can do it directly in the base class if it is yours.
You would then use the "extensible" base class for your own subclass instead of the base class:
class MyVC:ExtensibleViewController
{}
From then on, any of the subclass can receive new "stored" properties in extensions:
extension MyVC
{
var newStoredProperty:Int
{ get { return get(0) } set { set(newValue) } } // set and get must be on same line
}
Stored properties can also be added through protocol adoption for classes implementing the ExtensibleObject protocol:
protocol ListManager:ExtensibleObject
{
var listContent:[String] { get set }
}
extension ListManager
{
var listContent:[String]
{ get { return get([]) } set { set(newValue) } }
}
extension MyVC:ListManager {}
Bear in mind that these extended properties behave as lazy variables and that they have some additional overhead when used. For UI components and view controllers, this is usually not a problem.

Swift Dictionary - Update value in setter function

My code looks a little like:
Protocol
protocol MyProtocol: class {
var featureConfigs: [String: Any]? { get set }
}
Base Class
class MyBaseClass: MyProtocol {
var featureConfigs: [String : Any]? {
get {
return nil
}
set {
self.featureConfigs = newValue
}
}
}
Subclass
class MySubclass: MyBaseClass {
override var featureConfigs: [String : Any]? {
get {
return ["feature-specific-configuration":""]
}
set {
self.featureConfigs = newValue
}
}
}
I want to be able to update the values stored in featuresConfig. I understand that doing self.featureConfigs = newValue causes an infinite loop but I am unsure of how to update the featuresConfig dictionary correctly. I've read about and tried some things with Subscript but I couldn't get that to work either. Any suggestions?
In order to be able to set a persistent value for your featureConfigs, it needs to be a stored property, rather than a calculated one (this could also be a private backing variable that you create for this task, as #AMomchilov says). You can then use willSet or didSet to observe changes.
In your subclass, you can override the stored property declaration with a calculated one in order to allow you to update a given value-key pair in the dictionary when it's accessed, using super in order to refer to the superclass's stored property.
For example:
protocol MyProtocol: class {
var featureConfigs: [String: Any]? { get set }
}
class MyBaseClass: MyProtocol {
var featureConfigs: [String : Any]? { // stored property with setter observers
willSet {
print("about to be set")
}
didSet {
print("was set")
}
}
}
class MySubclass: MyBaseClass {
// calculated property that forwards to super's stored property
override var featureConfigs: [String : Any]? {
get {
// injects the updated value of a given key when accessing
guard var config = super.featureConfigs else {return nil}
config["feature-specific-configuration"] = "baz"
return config
}
set {
super.featureConfigs = newValue
}
}
}
let s = MySubclass()
s.featureConfigs = ["foo":"bar"]
print(s.featureConfigs) // Optional(["feature-specific-configuration": "baz", "foo": "bar"])
What you wrote is a computed property. Unlike stored properties, it's not backed by a persistent instance variable.
You can manually create a private instance variable to back the storage of this psuedo-computed-property. You can then give this variable a default value specific to the subclass it belongs to.