Change superclass property type on swift - swift

In a super Class called TableViewCell I have a property
class TableViewCell {
var model: AnyObject?
}
In a class called CountryTableViewCell I wrote this code
class CountryTableViewCell : TableViewCell {
var model:[AnyObject]? {
didSet {
// do some stuff
}
}
}
and I got this error
property model with [AnyObject]? cannot override a property with type
Anyobject?
Is it not possible to change the property model to an array?

No, you cannot use like that. Also AnyObject should be replaced by Any if using Swift 3.
You can change your code as below:
class TableViewCell {
var model: Any?
}
class CountryTableViewCell : TableViewCell {
override var model: Any? {
didSet {
}
}
}
Now if you want to get an array of the model in didSet then you can type cast it as below code.
class CountryTableViewCell : TableViewCell {
override var model: Any? {
didSet {
if let arrModel = model as? [Any] {
// Do Stuff...
}
}
}
}

No the property's type cannot be changed. If this is allowed, it would violate type safety by:
let subcell: CountryTableViewCell = CountryTableViewCell()
let supercell: TableViewCell = subcell
supercell.model = "anything that is not an array" as NSString
let wrong = subcell.model // not an array!
#ParthAdroja's answer showed a runtime workaround for this. You won't get an array at compile type, but at least you can ensure you have an array at runtime.
In principle if the property is read-only this specialization in subclass should work (it is same as restricting the return type of a function), but it doesn't work as of Swift 3. Anyone care about this can file an SE request to the Swift team ☺.
(Additionally, Apple do have some magic to make it work with bridged Objective-C classes, but we don't know what it is yet.)

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.

objc_getAssociatedObject and Runtime attributes

I have the following extension which was used to automatically save/retrieve runtime attributes unique to a UIImageView:
import UIKit
var imgAttributeKey:String? = nil
extension UIImageView {
var imgAttribute: String? {
get { return objc_getAssociatedObject(self, &imgAttributeKey) as? String }
set { objc_setAssociatedObject(self, &imgAttributeKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) }
}
}
This was working fine but after trying the code again recently, the getters were always returning nil. Did something change in Swift 5 version that could be breaking this implementation? Any suggestions on how to go about it?
Thanks to Tarun Tyagi for pointing out the correct fix.
#objc needs to be added to the property reference in the extension. Incorrectly marking the outside property results in a objc can only be used with members of classes, #objc protocols, and concrete extensions of classes error.
Working code is as follows:
import UIKit
var imgAttributeKey:String? = nil
extension UIImageView {
#objc var imgAttribute: String? {
get { return objc_getAssociatedObject(self, &imgAttributeKey) as? String }
set { objc_setAssociatedObject(self, &imgAttributeKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) }
}
}
The reason why this was needed it that this project was originally written in Swift 3 but starting from Swift 4, an explicit annotation #objc for dynamic Objective-C features (such as User Defined Runtime Attributes) is required.

How can I "blindly inject" an object variable with arbitrary value?

For example, I have a variable with type AnyObject. I do not know what class is that. But I want to test, whether if the object can accept specific attribute, and want to assign value to it.
For example, if I have:
class BaseViewController : UIViewController {
var containerVc : UIViewController?;
}
If I know that a variable can be typecasted into BaseViewController, then, of course, I can just typecast it to BaseViewController, and then assign the variable to it.
let vc : UIViewController?;
vc = BaseViewController();
(vc as? BaseViewController)?.containerVc = self;
The problem is if the BaseViewController type itself is inaccessible or unknowable.
So what I want to do is that I just want to test if an attribute is available to be set, if the operation can't be performed, it can fail silently. So for example, the code I have in mind if this is possible:
var vc : UIViewController? = generateUnknownVc();
vc.setValue(self, forAttribute: "containerVc");
or genericaly:
var abc : AnyObject = generateRandomObject();
abc.setValue(123, forAttribute: "randomAttribute");
I ask this because I remember somewhere that you can supply value to an object the way Storyboard does (User Defined Runtime Attributes). But I don't know how that works programmatically.
CONCLUSION:
This is the code I finally ended up with, borrowed heavily from Ehsan Saddique's answer. This code has been improved to also check the ancestors (superclass).
extension NSObject {
func safeValue(forKey key: String) -> Any? {
var copy : Mirror? = Mirror(reflecting: self);
while copy != nil {
for child in copy!.children.makeIterator() {
if let label = child.label, label == key {
return child.value
}
}
copy = copy?.superclassMirror;
}
return nil
}
func setValueSafe(_ value: Any?, forKey key: String) {
if safeValue(forKey: key) != nil { self.setValue(value, forKey: key); }
}
}
And from Andreas Oetjen's answer, I need to make mental note that this only works if the object is descendant from NSObject or tagged with #objc, and the function is also tagged with #objc.
Thanks!
UIViewController is inherited from NSObject. You can use Key-Value-Coding to find if the key exists. Add this extension to your code.
extension NSObject {
func safeValue(forKey key: String) -> Any? {
let copy = Mirror(reflecting: self)
for child in copy.children.makeIterator() {
if let label = child.label, label == key {
return child.value
}
}
return nil
}
}
Now you can use if-let to check if key exists.
if let key = yourViewController.safeValue(forKey: "someKey") {
print("key exists")
yourViewController.setValue("someValue", forKey:"someKey")
}
else {
print("key doesn't exist")
}
You will have to mark your properties with #objc to use KVC.
You would use Key-Value-Coding, which is supported in swift if (and only if)
Your class is somehow a subclass of NSObject or tagged with #objc
Your properties you want to access are tagged with #objc
I currently have no Xcode available, but this sample code should work:
class A : NSObject {
#objc var name:String = "hello"
}
var theA = A()
theA.setValue("world", forKey:"name")
print(theA.name) // shoud print "world"
To check if an property exists (instead of just crashing), see this answer: Check if class has a value for a key

Accessing Class var on AnyClass variable in Swift 4

In Swift 3.2 this (let id = row.tableViewCellClass?.reuseIdentifier) worked:
class DrillDownTableViewCell {
class var reuseIdentifier: String
{
return String(describing: self)
}
}
class RowViewModel: NSObject
{
var tableViewCellClass: AnyClass?
}
class Foo {
var row : RowViewModel?
func setup() {
row = RowViewModel()
row?.Class = DrillDownTableViewCell.self
}
func doThings() {
let id = row?.tableViewCellClass?.reuseIdentifier
}
}
After my Swift 4 update, it's showing "Instance member 'reuseIdentifier' cannot be used on type 'AnyObject'.
How would I access a class variable on a class who's metaType information is stored in an AnyClass variable?
(I assume you mean to have a ? after row in doThings(). I assume that's a typo and not part of the question. There are several other ? missing here and some other typos that I won't dive into.)
If you expect tableViewCellClass to have a reuseIdentifier class property, then it isn't of type AnyClass. There are many classes that don't have that property. You want classes that conform to a protocol:
protocol Identifiying {
static var reuseIdentifier: String { get }
}
So your model requires an Identifying class:
class RowViewModel: NSObject {
var tableViewCellClass: Identifiying.Type?
}
Then you can use this as you're expecting.

Any ideas on how to have a generic class, implementing a protocol, be cast to that protocol and allow retrieving a property?

I have a simple Result object which should contain an object (class, struct or enum) and a Bool to say whether it was cancelled or not. I need to interrogate this object along its path (before it gets to its destination, where the destination knows what kind of object to expect) to determine whether it was cancelled or not (without worrying about the accompanying object at that moment). My object looks like:
import Foundation
#objc protocol Resultable {
var didCancel: Bool { get }
}
class Result<T>: Resultable {
let didCancel: Bool
let object: T?
init(didCancel: Bool, object: T?) {
self.didCancel = didCancel
self.object = object
}
}
The idea being that my Result object can wrap the didCancel flag and the actual object (which can be of any type), and the fact that it implements the Resultable protocol means that I can interrogate it at any point to see whether it was cancelled by casting it to Resultable.
I understand (while not liking it) that the protocol has to be prefixed with #objc so that we can cast to it (according to the Apple docs). Unfortunately, when I run the code below (in a playground or in a project), I get a nasty "does not implement methodSignatureForSelector:" error message:
let test = Result(didCancel: false, object: NSArray())
println(test.didCancel)
// transform the object into the form it will be received in
let anyTest: AnyObject = test
if let castTest = anyTest as? Resultable {
println(castTest.didCancel)
}
It seems that despite the protocol being prefixed with #objc, it also wants the actual class to inherit from NSObject (and this is not a requirement that Apple makes explicit). This is obviously a problem for a generic class.
Is there anything I am missing here? Any way to get this to work? Failing that, are there any workarounds (although I strongly believe that this kind of thing should be possible - perhaps we can hope that Apple will do away with the #objc protocol casting requirement at some stage)?
UPDATE
It looks like this is solved with Swift 1.2
You can now cast to a non-ObjC protocol, and without the object having to inherit from NSObject.
It seems that despite the protocol being prefixed with #objc, it also wants the actual class to inherit from NSObject
This is not true. For example, the following code works as expected:
#objc protocol MyProtocol {
var flag: Bool { get }
}
class MyClass: MyProtocol {
let flag = true
}
let foo:AnyObject = MyClass()
if let p = foo as? MyProtocol {
println(p.flag)
}
The problems is that: Any methods/properties declared in Swift Generic classes are invisible from Objective-C. Hence, from the perspective of #objc protocol Resultable, didCancel property declared in Result<T> is invisible. That's why methodSignatureForSelector: is called.
The workaround here is very annoying: You have to have non Generic base class for Result that implements didCancel.
#objc protocol Resultable {
var didCancel: Bool { get }
}
class ResultBase: Resultable {
let didCancel: Bool
init(didCancel: Bool) { self.didCancel = didCancel }
}
class Result<T>: ResultBase, Resultable {
// ^^^^^^^^^^^^
// You have to explicitly conforms `Resultable` here as well for some reason :/
let object: T?
init(didCancel: Bool, object: T?) {
self.object = object
super.init(didCancel: didCancel)
}
}
let test = Result(didCancel: false, object: NSArray())
let anyTest: AnyObject = test
if let castTest = anyTest as? Resultable {
println(castTest.didCancel) // -> outputs "false"
}