How to Convert Anyclass to a Specific Class and Init It Dynamically in Swift

How to convert AnyClass to a specific Class and init it dynamically in Swift?

You can specify the array to be of the common superclass' type, then type deduction does the right thing (Beta-3 syntax):

let classArray: [UIViewController.Type] = [
OneViewController.self, TwoViewController.self
]
let controllerClass = classArray[index]
let controller = controllerClass.init()

How to use AnyClass to init an specific class instance in swift3.x

You have to add init() as requirement to the protocol definition
in order to call it on the array values of type Effect.Type:

protocol Effect {
init()
func des()
}

Then everything works as expected:

let array: [Effect.Type] = [ A.self, B.self ]

array[0].init().des() // This is A
array[1].init().des() // This is B

How to create an instance of a class from a String in Swift

You can try this:

func classFromString(_ className: String) -> AnyClass! {

/// get namespace
let namespace = Bundle.main.infoDictionary!["CFBundleExecutable"] as! String

/// get 'anyClass' with classname and namespace
let cls: AnyClass = NSClassFromString("\(namespace).\(className)")!

// return AnyClass!
return cls
}

use the func like this:

class customClass: UITableView {}   

let myclass = classFromString("customClass") as! UITableView.Type
let instance = myclass.init()

Swift init from unknown class which conforms to protocol

You will likely want to rethink this code in the future, to follow more Swift-like patterns, but it's not that complicated to convert, and I'm sure you have a lot of existing code that relies on behaving the same way.

The most important thing is that all the objects must be @objc classes. They can't be structs, and they must subclass from NSObject. This is the major reason you'd want to change this to a more Swifty solution based on Codable.

You also need to explicitly name you types. Swift adds the module name to its type names, which tends to break this kind of dynamic system. If you had a type Person, you would want to declare it:

@objc(Person)  // <=== This is the important part
class Person: NSObject {
required init(json: NSDictionary) { ... }
}

extension Person: JsonProtocol {
func convertToJSON() -> NSDictionary { ... }
}

This makes sure the name of the class is Person (like it would be in ObjC) and not MyGreatApp.Person (which is what it normally would be in Swift).

With that, in Swift, this code would be written this way:

if let className = obj[JSON_CLASS] as? String,
let jsonClass = NSClassFromString(className) as? JsonProtocol.Type {
arr.add(jsonClass.init(json: obj))
}

The key piece you were missing is as? JsonProtocol.Type. That's serving a similar function to +conformsToProtocol: plus the cast. The .Type indicates that this is a metatype check on Person.self rather than a normal type check on Person. For more on that see Metatype Type in the Swift Language Reference.

Note that the original ObjC code is a bit dangerous. The -initWithJSON must return an object. It cannot return nil, or this code will crash at the addObject call. That means that implementing JsonProtocol requires that the object construct something even if the JSON it is passed is invalid. Swift will enforce this, but ObjC does not, so you should think carefully about what should happen if the input is corrupted. I would be very tempted to change the init to an failable or throwing initializer if you can make that work with your current code.

I also suggest replacing NSDictionary and NSArray with Dictionary and Array. That should be fairly straightforward without redesigning your code.

Pass a class type for use inside a method

How about the generic Swift way.

The code constrains the generic type T to MyClass since it must have a name property.

class MyClass : NSObject {
var name : String

override required init() {
self.name = ""
super.init()
}
}

class OneClass : MyClass {
required init() {
super.init()
self.name = "One"
}
}

class TwoClass : MyClass {
required init() {
super.init()
self.name = "Two"
}
}


class Thing : NSObject {
func doStuff<T : MyClass>(withClass cls: T.Type) -> String {
let x = cls.init()
return x.name
}
}

let z = Thing()
print(z.doStuff(withClass: OneClass.self))
print(z.doStuff(withClass: TwoClass.self))

Or use a protocol.

protocol Nameable {
var name : String { get }
init()
}

class MyClass : NSObject, Nameable { ...

...

class Thing : NSObject {
func doStuff<T : Nameable>(withClass cls: T.Type) -> String {
let x = cls.init()
return x.name
}
}

Swift class introspection & generics

Well, for one, the Swift equivalent of [NSString class] is .self (see Metatype docs, though they're pretty thin).

In fact, NSString.class doesn't even work! You have to use NSString.self.

let s = NSString.self
var str = s()
str = "asdf"

Similarly, with a swift class I tried...

class MyClass {

}

let MyClassRef = MyClass.self

// ERROR :(
let my_obj = MyClassRef()

Hmm… the error says:

Playground execution failed: error: :16:1: error: constructing an object of class type 'X' with a metatype value requires an '@required' initializer

 Y().me()
^
<REPL>:3:7: note: selected implicit initializer with type '()'
class X {
^

It took me a while to figure out what this means… turns out it wants the class to have a @required init()

class X {
func me() {
println("asdf")
}

required init () {

}
}

let Y = X.self

// prints "asdf"
Y().me()

Some of the docs refer to this as .Type, but MyClass.Type gives me an error in the playground.

Access one specific object from class name?

I used a global variable and a calculated type property. Only the constant type property are not supported yet.

private var globalVar: MyClass? = nil

class MyClass {

internal class var sharedInstance : MyClass {
get {
return globalVar
set {
globalVar = newValue
}
}

}

Before using it, it must be set from any of the instances:

MyClass.sharedInstance = self

Now I could set and get the type property like I could in every other language, until Apple makes it easier and supports constant type properties.
I can get the sharedInstance from anywhere inside the project (even accessible from Objective-C, when you add @objc before the internal-keyword

Swift:

MyClass.sharedInstance

Objective-C:

[MyClass sharedInstance];


Related Topics



Leave a reply



Submit