Reputation: 133
I am trying to add additional properties to UIViewController.
Code:
protocol AdditionalStoredProperties
{
associatedtype Title
func getAssociatedObject<Title>(key: UnsafePointer<Title> ,
defValue : Title)->Title
}
extension AdditionalStoredProperties
{
func getAssociatedObject<Title>( key: UnsafePointer<Title> , defValue : Title)->Title
{
guard let actual_value = objc_getAssociatedObject(self as! AnyObject, key) as? Title else
{
return defValue
}
return actual_value
}
}
extension UIViewController:AdditionalStoredProperties
{
typealias Title = String
var previousPage : String
{
get { return getAssociatedObject(&self.previousPage, defValue: self.previousPage) }
set { objc_setAssociatedObject(self, &self.previousPage, newValue, .OBJC_ASSOCIATION_RETAIN)}
}
}
But I am getting the following error:
Error: Trying to put the stack in unreadable memory at:
I know that we cannot directly add stored properties to extensions so I am trying it add using objc_setAssociatedObject()
Upvotes: 13
Views: 9282
Reputation: 11587
If someone has the below scenario
If your method is getting called recursively, you may get this error.
Upvotes: 13
Reputation: 80951
There are a number of things wrong with what you're doing:
Attempting to access self.previousPage
within its own getter will call itself recursively.
You cannot use &self.previousPage
as a stable or unique pointer value, as it'll be a pointer to a temporary variable (because you're dealing a computed property). You cannot therefore use it as the key for an associated object. Swift only guarantees stable and unique pointer values for static and global stored variables (see this Q&A for more info).
You should make AdditionalStoredProperties
a class-bound protocol (with : class
), as you can only add associated objects to Objective-C classes (which, on Apple platforms, Swift classes are built on top of). While you can bridge, for example, a struct
to AnyObject
(it'll get boxed in an opaque Obj-C compatible wrapper), it is merely that; a bridge. There's no guarantee you'll get the same instance back, therefore no guarantee the associated objects will persist.
You probably didn't mean for Title
to be an associated type of your protocol; you're not using it for anything (the generic placeholder Title
defined by getAssociatedObject(key:defValue:)
is completely unrelated).
Bearing those points in mind, here's a fixed version of your code:
protocol AdditionalStoredProperties : class {
func getAssociatedObject<T>(ofType: T.Type, key: UnsafeRawPointer,
defaultValue: @autoclosure () -> T) -> T
}
extension AdditionalStoredProperties {
func getAssociatedObject<T>(ofType: T.Type, key: UnsafeRawPointer,
defaultValue: @autoclosure () -> T) -> T {
// or: return objc_getAssociatedObject(self, key) as? T ?? defaultValue()
guard let actualValue = objc_getAssociatedObject(self, key) as? T else {
return defaultValue()
}
return actualValue
}
}
extension UIViewController : AdditionalStoredProperties {
private enum AssociatedObjectKeys {
static var previousPage: Never?
}
var previousPage: String {
get {
// return the associated object with a default of "" (feel free to change)
return getAssociatedObject(ofType: String.self,
key: &AssociatedObjectKeys.previousPage,
defaultValue: "")
}
set {
objc_setAssociatedObject(self, &AssociatedObjectKeys.previousPage,
newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
}
Note that we're:
Using a static
stored property in order to get a pointer value to use as the key for our associated object. Again, this works because Swift guarantees stable and unique pointer values for static and global stored variables.
Using @autoclosure
for the defaultValue:
parameter, as it may not need to be evaluated if an associated object is already present.
Having the key:
parameter take an UnsafeRawPointer
, as the type of the pointee is irrelevant; it's merely the location in memory that's used as the key.
Explicitly satisfying the generic placeholder with an ofType:
parameter. This is mainly a matter of preference, but I prefer to spell these things out explicitly rather than relying on type inference.
Using camelCase
instead of snake_case
, as is Swift convention.
Upvotes: 8