objc_getAssociatedObject and Runtime attributes - swift

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.

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.

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

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.

Change superclass property type on 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.)

swift setter causing exc_bad_access

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.