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 valuevar depth: Int?
Or give it default value
var depth: Int = 1
With this second option you can create custom
init
with default valuestruct 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
How to Reload a UI View's Content Swift
How to Repeat Animation (Using Uiviewpropertyanimator) Certain Number of Times
How Is a Type-Erased Generic Wrapper Implemented
Macos/Swift Capture Audio with Avcapturesession
Using Nstimer in Swift Playground
Saving Array to Realm in Swift
Select All Text in Textfield Upon Click Swiftui
Where Is the .Camera Anchorentity Located
Swift - Class Method Which Must Be Overridden by Subclass
Occlusion Material or Hold-Out Shader in Arkit and Scenekit
Subclass Nsapplication in Swift
App Crash on Sign in (Xcode 9.3) Exc_Bad_Access (Code=1, Address=0X1)
Skip Item When Performing Map in Swift
Hstack with Sf Symbols Image Not Aligned Centered
Creating a Custom Scngeometry Polygon Plane with Scngeometryprimitivetype Polygon Crash/Error