How to Have Stored Properties in Swift, the Same Way I Had on Objective-C

How to have stored properties in Swift, the same way I had on Objective-C?

Associated objects API is a bit cumbersome to use. You can remove most of the boilerplate with a helper class.

public final class ObjectAssociation<T: AnyObject> {

private let policy: objc_AssociationPolicy

/// - Parameter policy: An association policy that will be used when linking objects.
public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) {

self.policy = policy
}

/// Accesses associated object.
/// - Parameter index: An object whose associated object is to be accessed.
public subscript(index: AnyObject) -> T? {

get { return objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T? }
set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) }
}
}

Provided that you can "add" a property to objective-c class in a more readable manner:

extension SomeType {

private static let association = ObjectAssociation<NSObject>()

var simulatedProperty: NSObject? {

get { return SomeType.association[self] }
set { SomeType.association[self] = newValue }
}
}

As for the solution:

extension CALayer {

private static let initialPathAssociation = ObjectAssociation<CGPath>()
private static let shapeLayerAssociation = ObjectAssociation<CAShapeLayer>()

var initialPath: CGPath! {
get { return CALayer.initialPathAssociation[self] }
set { CALayer.initialPathAssociation[self] = newValue }
}

var shapeLayer: CAShapeLayer? {
get { return CALayer.shapeLayerAssociation[self] }
set { CALayer.shapeLayerAssociation[self] = newValue }
}
}

How to build a Swift object that can control the mutability of its stored properties

import Foundation

protocol PropertyWrapperWithLockableObject {
var enclosingObject: LockableObjectBase! {get set}
}

@propertyWrapper
class Lockable<Value>: PropertyWrapperWithLockableObject {
private var _wrappedValue: Value
var enclosingObject: LockableObjectBase!

init (wrappedValue: Value) { self._wrappedValue = wrappedValue }

var wrappedValue: Value {
get {
precondition(enclosingObject.isLocked, "Cannot access object properties until object is locked")
return _wrappedValue
}
set {
precondition(!enclosingObject.isLocked, "Cannot modify object properties after object is locked")
_wrappedValue = newValue
}
}
}

class LockableObjectBase {
internal var isLocked: Bool = false {
didSet { isLocked = true }
}

init () {
let mirror = Mirror(reflecting: self)
for child in mirror.children {
if var child = child.value as? PropertyWrapperWithLockableObject {
child.enclosingObject = self
}
}
}
}

Usage:

class DataObject: LockableObjectBase {
@Lockable var someString: String = "Zork"
@Lockable var someInt: Int

override init() {
someInt = 42
// super.init() // Not needed in this particular example.
}
}

var testObject = DataObject()
testObject.isLocked = true
print(testObject.someInt, testObject.someString) // 42, Zork
testObject.isLocked = false // Has no effect: Object remained locked
print (testObject.isLocked) // true
testObject.someInt = 2 // Aborts the program

arsenius's answer here provided the vital reflection clue!

Extensions with stored properties

You can do something like this:

extension UITextView {

private struct AssociatedKeys {
static var placeholder = "placeholder"
}

var placeholder: String! {
get {
guard let placeholder = objc_getAssociatedObject(self, &AssociatedKeys.placeholder) as? String else {
return String()
}

return placeholder
}

set(value) {
objc_setAssociatedObject(self, &AssociatedKeys.placeholder, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}

Correct way of initializing stored properties? - Xcode 8.3.3 / Swift 3

In you init(sound: String) you don't assign any value to image.
Since this property is mandatory and none optional.

You have two options, make the property optional:

var image: UIImage? = nil

Or assign an new image object to it:

init(sound: String) {
self.sound = sound
self.image = UIImage()
}

Why does Swift not allow stored properties in extensions?

Extensions are for extending the functionality of an existing class without changing the memory structure. It's more or less syntactic sugar. Imagine you could add stored properties and methods, what would it be? Nothing else but inheritance. So if you like to add new properties and methods just inherit from the class.

How do I create a weak stored property in a Swift extension?

I explored using obj_storeWeak and objc_loadWeak but ended up simply using a wrapper class:

internal class WeakWrapper : NSObject {
weak var weakObject : NSObject?

init(_ weakObject: NSObject?) {
weakObject = weakObject
}
}

extension MyClass {
var myVar: NSObject? {
get {
var weakWrapper: WeakWrapper? = objc_getAssociatedObject(self, &associationKey) as? WeakWrapper
return weakWrapper?.weakObject
}
set {
var weakWrapper: WeakWrapper? = objc_getAssociatedObject(self, &associationKey) as? WeakWrapper
if weakWrapper == nil {
weakWrapper = WeakWrapper(newValue)
objc_setAssociatedObject(self, &associationKey, weakWrapper, UInt(OBJC_ASSOCIATION_RETAIN))
} else {
weakWrapper!.weakObject = newValue
}
}
}
}

How Memory safety is preserved because the two stored properties don’t interact in any way in Swift?

With robGlobal.someFunction(), you're calling shareHealth on a separate local instance of robLocal. Obviously there are no memory safety issues in that case.

But with robGlobal.anotherFunction(), you're then calling an instance method of the robGlobal, but then passing a yet another reference to the same robGlobal to shareHealth, hence the memory safety violation.

But that is good. This is functionally equivalent to one of the other attempts that the memory safety checks protected you against:

robGlobal.shareHealth(with: &robGlobal) 

The whole idea is to share health with another player. Setting aside the memory safety checks, what does it even mean to share one player’s health with itself? E.g. your current health is 11. You then “share” it with yourself. So, is your health now 5? Or 6? And does it make sense that your health was 11 and now that after you've shared it with yourself that it is now a smaller value? The whole idea of sharing with yourself doesn't make sense. The “memory safety” checks are protecting you from this sort of misuse.


Personally, I would

  • make balance a static method because all it is doing it “balancing” two integer values, not doing anything with the current instance;

  • this should also probably be private as there is no need to expose this function; and

  • since you seem to want to “share health” to balance heath between two players as well as balance the health and energy for a given player, I would write those two methods.

Thus, we end up with something like:

struct Player {
var name: String
var health: Int
var energy: Int

static let maxHealth = 10
}

// MARK: - Interface

extension Player {
mutating func restoreHealth() {
health = Player.maxHealth
}

mutating func shareHealth(with teammate: inout Player) {
Self.balance(&teammate.health, &health)
}

mutating func balanceHealthAndEnergy() {
Self.balance(&health, &energy)
}
}

// MARK: - Private utility methods

private extension Player {
static func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
}

Then you can do something like:

func someFunction() {
var rob = Player(name: "Rob", health: 20, energy: 10)
rob.balanceHealthAndEnergy()
}

func anotherFunction() {
var rob = Player(name: "Rob", health: 10, energy: 10)
var matt = Player(name: "Matt", health: 5, energy: 10)

rob.shareHealth(with: &matt)
}

This new interface (the shareHealth and balanceHeathAndEnergy) streamline the interface. You can call them on local vars or properties (or globals, if you really want to; but having globals is generally a bad idea). But you would never shareHealth with yourself, and now that balance is private, it minimizes the sort of misuse outlined in your question.

FWIW, I would not make these test methods instance methods of Player as they are not doing anything with the current instance. You could make them static methods. Or you could just make them methods in your model (or tests or whatever). But they do not make sense as Player instance methods.



Related Topics



Leave a reply



Submit