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
astatic
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
Ios Uiimagepickercontroller Result Image Orientation After Upload
Getting the Difference Between Two Dates (Months/Days/Hours/Minutes/Seconds) in Swift
How to Increment the Filename If File Already Exists
Disabling Auto-Play in Full Screen on Ios
Set the Maximum Character Length of a Uitextfield
Hide Strange Unwanted Xcode Logs
Test iOS App on Device Without Apple Developer Program or Jailbreak
Programmatically Get Own Phone Number in Ios
Create Tap-Able "Links" in the Nsattributedstring of a Uilabel
Ios Swift - Objective C Code Migration to Swift
How to Animate Constraint Changes
Converting Nsstring to Nsdate (And Back Again)
How to Make a Uitextfield Move Up When the Keyboard Is Present - on Starting to Edit
How to Create Delegates in Objective-C
Ansible Regex_Findall Multiple Strings
Why Does Viewwillappear Not Get Called When an App Comes Back from the Background