Is It Possible in Swift to Add Variables to an Object at Runtime

Is it possible in Swift to add variables to an object at runtime?

The previous answer about objc_setAssociatedObject() is the right approach, but I think Apple's APIs for this have not yet been vetted, because I've had difficulty using them the way I think they ought to be used. (I shouldn't have to muck about with unsafe pointers and such.) Here's the solution I'm using now.

First, you need a bit of Objective-C glue (follow Apple's instructions for mixing Objective-C and Swift in the same project:

// RuntimeGlue.h
// Should be included from your bridging header.

@import Foundation;

void setAssociatedObject_glue(NSObject *object, const NSString *key, NSObject *value);
NSObject *getAssociatedObject_glue(NSObject *object, const NSString* key);


// RuntimeGlue.m

#import "RuntimeGlue.h"
#import <objc/runtime.h>

void setAssociatedObject_glue(NSObject *object, const NSString *key, NSObject *value) {
objc_setAssociatedObject(object, (__bridge const void *)(key), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

NSObject *getAssociatedObject_glue(NSObject *object, const NSString* key) {
return objc_getAssociatedObject(object, (__bridge const void *)(key));
}

Next, the Swift methods you'll call from the rest of your program:

// Runtime.swift

import Foundation

public func setAssociatedObject(#object: NSObject, #key: NSString, #value: NSObject?) {
setAssociatedObject_glue(object, key, value)
}

public func getAssociatedObject(#object: NSObject, #key: NSString) -> NSObject? {
return getAssociatedObject_glue(object, key)
}

Finally, an example of use to tag a particular view controller's view as "debugging".

// MyViewController.swift

import UIKit

let debugKey: NSString = "DebugKey"

class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

setAssociatedObject(object: self.view, key: debugKey, value: "debugging")
}

override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)

let val = getAssociatedObject(object: self.view, key: debugKey)
println("val:\(val)")
}
}

This approach lets you pass nil for value to the setter in order to clear the value for the key, and returns an optional from the getter. Note also that the key argument must be identical in both cases (k1 === k2) and not merely equivalent (k1 == k2).

Also note that this only lets you tag instances of NSObject or its subclasses-- it does not work for Swift native classes. The value must also be an NSObject subclass, but both strings and number literals automatically bridge to Objective-C, so you don't need to do any explicit casting.

Swift Struct: adding new parameters at runtime

This is Swift. You should know about properites of your object when you're creating it.

You have to options:

  • Make depth property optional. Then this property doesn't have to have any value

    var depth: Int?
  • Or give it default value

    var depth: Int = 1

    With this second option you can create custom init with default value

    struct Size {
    var width, height, depth: Int
    }

    extension Size {
    init(width: Int, height: Int) {
    self.init(width: width, height: height, depth: 1)
    }
    }

    Size(width: 1, height: 1)
    Size(width: 1, height: 1, depth: 1)

cannot change class variables while runtime swift

This, is not good, because you create a player with an invalid URL:

// NOT GOOD
class PlayerAv {
var audioLink: String = ""
var player: AVPlayer
init()
{
player = AVPlayer(URL: NSURL(string: self.audioLink))
}
}
let myPlayer = PlayerAv()

It's better to initialize the player once you know what URL you want to use:

// GOOD
class PlayerAv {
var audioLink: String?
var player: AVPlayer
init(link: String) {
self.audioLink = link
self.player = AVPlayer(URL: NSURL(string: link))
}
}

Also, make it a var so we can change the content later:

var myPlayer = PlayerAv(link: "http://yourFirstURLhere")

Now, this myPlayer object is your player.

If you want to use it:

myPlayer.player.play()
myPlayer.player.pause()
myPlayer.player.volume = 2

Now what needs to happen if you want a player with a new link?

That's when you create a new one, with the new link, but you want to assign this new player to the same instance variable, so:

myPlayer = PlayerAv(link: "http://yourNewURLhere")
myPlayer.player.play()

Note that we just assigned a new created player object to the existing myPlayer variable.

If you want to print the current link:

println(myPlayer.audiolink!)

Inside the myPlayer object are the player itself (player) and the link (audioLink).

Note: these were examples based on your code. But you should have a look at something like this for interesting working examples of using AVPlayer.

Dynamically create objects and set attributes in swift

I think that this is what you want:

@objc(MyClass)
class MyClass : NSObject {
var someProperty = 0
}

let type = NSClassFromString("MyClass") as! NSObject.Type
let instance = type()
instance.setValue(12, forKey: "someProperty")

In Swift, how can I declare a variable of a specific type that conforms to one or more protocols?

In Swift 4 it is now possible to declare a variable that is a subclass of a type and implements one or more protocols at the same time.

var myVariable: MyClass & MyProtocol & MySecondProtocol

To do an optional variable:

var myVariable: (MyClass & MyProtocol & MySecondProtocol)?

or as the parameter of a method:

func shakeEm(controls: [UIControl & Shakeable]) {}

Apple announced this at WWDC 2017 in Session 402: Whats new in Swift

Second, I want to talk about composing classes and protocols. So, here
I've introduced this shakable protocol for a UI element that can give
a little shake effect to draw attention to itself. And I've gone ahead
and extended some of the UIKit classes to actually provide this shake
functionality. And now I want to write something that seems simple. I
just want to write a function that takes a bunch of controls that are
shakable and shakes the ones that are enabled to draw attention to
them. What type can I write here in this array? It's actually
frustrating and tricky. So, I could try to use a UI control. But not
all UI controls are shakable in this game. I could try shakable, but
not all shakables are UI controls. And there's actually no good way to
represent this in Swift 3. Swift 4 introduces the notion of composing
a class with any number of protocols.

What is the correct method to add additional properties to CLLocation?

You can't. What you need is to create a custom structure and add a location property CLLocation and whatever properties you need there:

struct Location {
let location: CLLocation
let newProperty: Double
}

How to add a variable to category in Objective-C?

As the other stated, you can't. Although has H2CO3 pointed out, you can use associative references. On Apple Documents:

Note that a category can’t declare additional instance variables for
the class; it includes only methods. However, all instance variables
within the scope of the class are also within the scope of the
category. That includes all instance variables declared by the class,
even ones declared @private.

If you want to go for associated object, you can use this answer. Moreover, you can use this post by Ole Begemann.

Dynamic casting using result of NSClassFromString(MyUIViewController)

No, you cannot cast to a runtime type object. You must cast to a compile-time type. This is why we write x as Int, not x as Int.self: Int is a type, and Int.self is an object that represents that type at runtime.

What would it mean to cast to NSClassFromString("MyUIViewController")? Now you have a variable, controller, whose value is some type that the compiler knows nothing about, so the compiler cannot let you do anything with controller. You can't call methods or access properties on it, because the compiler doesn't know what methods or properties it has. You can't pass it as an argument to a function, because the compiler doesn't know whether it is the right type for that argument.

If you edit your question to explain what you want to do with controller (what methods you want to call on it or what properties you want to access or what functions you want to pass it to), then I will revise my answer to address your goal.

How can I add properties to an object at runtime?

It’s possible to add formal properties to a class via class_addProperty():

BOOL class_addProperty(Class cls,
const char *name,
const objc_property_attribute_t *attributes,
unsigned int attributeCount)

The first two parameters are self-explanatory. The third parameter is an array of property attributes, and each property attribute is a name-value pair which follow Objective-C type encodings for declared properties. Note that the documentation still mentions the comma-separated string for the encoding of property attributes. Each segment in the comma-separated string is represented by one objc_property_attribute_t instance. Furthermore, objc_property_attribute_t accepts class names besides the generic @ type encoding of id.

Here’s a first draft of a program that dynamically adds a property called name to a class that already has an instance variable called _privateName:

#include <objc/runtime.h>
#import <Foundation/Foundation.h>

@interface SomeClass : NSObject {
NSString *_privateName;
}
@end

@implementation SomeClass
- (id)init {
self = [super init];
if (self) _privateName = @"Steve";
return self;
}
@end

NSString *nameGetter(id self, SEL _cmd) {
Ivar ivar = class_getInstanceVariable([SomeClass class], "_privateName");
return object_getIvar(self, ivar);
}

void nameSetter(id self, SEL _cmd, NSString *newName) {
Ivar ivar = class_getInstanceVariable([SomeClass class], "_privateName");
id oldName = object_getIvar(self, ivar);
if (oldName != newName) object_setIvar(self, ivar, [newName copy]);
}

int main(void) {
@autoreleasepool {
objc_property_attribute_t type = { "T", "@\"NSString\"" };
objc_property_attribute_t ownership = { "C", "" }; // C = copy
objc_property_attribute_t backingivar = { "V", "_privateName" };
objc_property_attribute_t attrs[] = { type, ownership, backingivar };
class_addProperty([SomeClass class], "name", attrs, 3);
class_addMethod([SomeClass class], @selector(name), (IMP)nameGetter, "@@:");
class_addMethod([SomeClass class], @selector(setName:), (IMP)nameSetter, "v@:@");

id o = [SomeClass new];
NSLog(@"%@", [o name]);
[o setName:@"Jobs"];
NSLog(@"%@", [o name]);
}
}

Its (trimmed) output:

Steve
Jobs

The getter and setter methods should be written more carefully but this should be enough as an example of how to dynamically add a formal property at runtime.



Related Topics



Leave a reply



Submit