Achieve dynamic initialisation based on Class passed as parameter in Swift - ios

I would like to create objects based on their class passed into a function.
First, I have an interface every generatable object should conform to:
interface Generatable{
init(raw: NSDictionary)
}
and a function that would take the class as a parameter
func generateDynamicObjectFromClass(generatable: Generatable.Type){
var someJSONData : NSDictionary = NSDictionary()
var myGeneratedObject : Generatable = generatable(raw: someJSONData) //custom initialiser of Generatable class
}
and then, call it like that:
generateDynamicObjectFromClass(MyGeneratableObject.Type)
MyGeneratableObject class
class MyGeneratableObject : NSObject, Generatable{
init(raw: NSDictionary){
//some initialisation
}
}
However, MyGeneratableObject does not have a Type property, so the problem is to get the corresponding class of the underlying object during runtime. Is that possible ?

You have to define generateDynamicObjectFromClass as a generic function:
protocol Generatable {
init(raw: NSDictionary)
}
func generateDynamicObjectFromClass<T where T:Generatable>(generatable:T.Type, otherParam: NSString = "") -> T {
var someJSONData : NSDictionary = NSDictionary()
var myGeneratedObject = T(raw: someJSONData)
return myGeneratedObject
}
class MyGeneratableObject : NSObject, Generatable {
init(raw: NSDictionary){
println("MyGeneratableObject init")
}
}
var myObject1 = generateDynamicObjectFromClass(MyGeneratableObject.self, otherParam: "foo")
var myObject2 = generateDynamicObjectFromClass(MyGeneratableObject.self)
Alternatively, you can create the object as
var myObject = MyGeneratableObject(raw: NSDictionary())
without the need for a separate function.

Related

Swift - Passing custom objects as a parameter

I started learning Swift today and in my first test app I am getting this error:
TestClass is not convertible to AnotherClass
The following is the TestClass:
class TestClass : NSObject {
var parameter1 : String = ""
var parameter2 : String = ""
override init() {
super.init()
}
func createJob(parameter1: String, parameter2: String) -> TestClass {
self.parameter1 = parameter1
self.parameter2 = parameter2
return self;
}
}
And this is the AnotherClass:
class AnotherClass: NSObject {
private struct internalConstants {
static let test1 = "testData"
static let test2 = "testData2"
}
var current : String
override init() {
self.current = internalConstants.test1
super.init()
}
func executeTask(testClass : TestClass) {
if testClass.parameter1 == "abc" {
return;
}
}
}
And this is the ViewController where I am getting the compiler error:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let obj = TestClass()
AnotherClass.executeTask(obj)
}
}
AnotherClass.executeTask line is giving the compiler error.
The obj variable sent as a parameter on this line is highlighted by Xcode with the error
"TestClass is not convertible to AnotherClass".
In C# or Objective C it is allowed to pass custom objects as a parameter to another methods. How can I do it in Swift?
Let's correct first the TestClass. This is how you should init a class like that:
class TestClass : NSObject {
....
init(parameter1: String, parameter2: String) {
....
}
}
Much simpler. Now, going back to your problem,
"TestClass is not convertible to AnotherClass".
Take a look at it again. The line you've mentioned in your question. You are trying to do this:
let obj = TestClass()
AnotherClass.executeTask(obj)
This line, AnotherClass.executeTask(obj), is giving you an error because indeed executeTask() is an instance method. You could do three ways for that.
add static keyword to the func executeTask... So it becomes like this: static func executeTask(testClass : TestClass) {
Instead of static keyword, you could add class. It becomes like so: class func executeTask(....
OR, better if you just instantiate the AnotherClass. Make a new object of AnotherClass. How to instantiate? You tell me. But here:
let anotherClass = AnotherClass()
Either implement executeTask as a class function
class func executeTask(testClass : TestClass) {
if testClass.parameter1 == "abc" {
return;
}
}
or instantiate AnotherClass in vieweDidLoad
let obj = TestClass()
let another = AnotherClass()
another.executeTask(testClass: obj)
Note the slightly different call to executeTask with the argument name.
And there is really no reason for you to subclass NSObject as I see it.
I think it's best to keep is simple. Create an instance of AnotherClass inside of ViewController.
class ViewController: UIViewController {
// Create an instance of AnotherClass which lives with ViewController.
var anotherClass = AnotherClass()
override func viewDidLoad() {
super.viewDidLoad()
let obj = TestClass()
// Use the instance of AnotherClass to call the method.
anotherClass.executeTask(testClass: obj)
}
}

Generic class type doesn't conform to Any

I have a problem with storing my generic classes in an array. How should I format the type for my array while keeping the reference to the original type (I know I could do var myClasses: [Any] = [] but that wouldn't be helpful when retrieving the variable from my array :(
Example is below:
import UIKit
protocol Reusable { }
extension UITableViewCell: Reusable { }
extension UICollectionViewCell: Reusable { }
class SomeClass<T> where T: Reusable {
init() { }
}
var myClasses: [SomeClass<Reusable>] = []
myClasses.append(SomeClass<UITableViewCell>())
myClasses.append(SomeClass<UICollectionViewCell>())
myClasses.append(SomeClass<UITableViewCell>())
myClasses.append(SomeClass<UICollectionViewCell>())
Edit: Just to clarify, I have used the collection and table cells as an example, I am not actually planning on storing them together :)
Edit 2 var myClasses: [SomeClass<Reusable>] = [] generates error: using 'Reusable' as a concrete type conforming to protocol 'Reusable' is not supported
Edit 3 var myClasses: [SomeClass<AnyObject>] = [] generates error: type 'AnyObject' does not conform to protocol 'Reusable'
I think you can create some sort of Holder class that can accept and retrieve your object:
class Holder {
lazy var classes: [Any] = []
func get<T>(_ type: T.Type) -> [T]? {
return classes.filter({ $0 is T }) as? [T]
}
}
And the main part:
let holder = Holder()
holder.classes.append(SomeClass<UITableViewCell>())
if let someTableViewCells = holder.get(SomeClass<UITableViewCell>.self)?.first {
// or filter out again to get specific SomeClass of UITableViewCell
print(someTableViewCells)
}
Or without holder class:
var classes: [Any] = []
classes.append(SomeClass<UITableViewCell>())
if let someTableViewCell = classes.filter({ $0 is SomeClass<UITableViewCell> }).first as? SomeClass<UITableViewCell> {
print(someTableViewCell)
}
You should use array of AnyObject in your case. Because as you know swift is strong typed language and for example
SomeClass<UITableViewCell>
and
SomeClass<UICollectionViewCell>
are different types of objects. As for example Array< Int > and Array< String >, they are both arrays, but still it's a different types of objects. So in this case you'll have to use declaration:
var myClasses: [AnyObject] = []
and check type of object or typecast them every time you'll need.
if (myClasses[0] is SomeClass<UICollectionViewCell>) { do something }
or
if let cell = myClasses[0] as? SomeClass<UICollectionViewCell> { do something }
My suggestion is adding parent protocol SomeClass Container for your SomeClass generic. Then put an array of SomeClass objects inside SomeClass.
protocol Reusable { func cakePlease() }
extension UITableViewCell: Reusable {
func cakePlease() { }
}
extension UICollectionViewCell: Reusable {
func cakePlease() { }
}
protocol SomeClassContainer {
func teaPlease()
func afternoonPlease()
}
class SomeClass<T: Reusable>: SomeClassContainer {
var item: T?
init() { }
func teaPlease() { }
func afternoonPlease() {
teaPlease()
item?.cakePlease()
}
}
var myClasses = [SomeClassContainer]()
myClasses.append(SomeClass<UITableViewCell>())
myClasses.append(SomeClass<UICollectionViewCell>())
myClasses.append(SomeClass<UITableViewCell>())
myClasses.append(SomeClass<UICollectionViewCell>())
myClasses[0].teaPlease()
if let item = (myClasses[0] as? SomeClass<UITableViewCell>)?.item {
item.cakePlease()
}
for myClass in myClasses {
if let tableCell = (myClass as? SomeClass<UITableViewCell>)?.item {
tableCell.cakePlease()
} else if let collectionCell = (myClass as SomeClass<UICollectionViewCell>)?.item {
collectionCell.cakePlease()
}
}
myClasses.forEach({ $0.afternoonPlease() })
Generally the way to type your array would be to go as specific as possible whilst still covering all bases.
What I mean by this for example is storing an array of UIViewController objects, even though each will actually be a different type. You wouldn't use Any here because you really don't need to be that general.
In your case, why not use Reusable? Since all your generic classes conform to it anyway, it is as general as you can go whilst still maintaining convenience.

Casting array of objects to array of subclassed objects in Swift

I have an array of EKReminder, like so:
var currentReminders: [EKReminder]? = ....
I want to cast this array to an array of subclasses of EKReminder. Let's say this is the subclass:
import Foundation
import EventKit
class NNReminder: EKReminder {
var additionalNotes: String?
}
How would I cast currentReminders to [NNReminder]? I tried several ways but they all failed.
Provided you are sure that all members of currentReminders are, in fact, NNReminders, you can cast them all like this:
currentReminders = currentReminders.map { $0 as! NNReminder }
Edit: if only some of the reminders are of type NNReminder, or you're not sure of the array's contents, you can use flatMap to remove the nil values:
currentReminders = currentReminders.flatMap { $0 as? NNReminder }
If you are asking how to transform a bunch of objects that were initialized as EKReminder, you should write a custom init in NNReminder that takes an EKReminder, and use this init in the above map method.
I was tired of having cryptic .map clauses all over my code so i wrote a small extension to make things neat:
extension Array{
func cast<T>(type:T.Type? = nil) -> [T]{
return self.map { $0 as! T }
}
}
Example:
class A:B{
var value:String = "default"
init(_ value:String){
self.value = value
}
}
class B{
var someNum:CGFloat = 1
init(){
}
}
var arr1:Array<A> = [A("a"),A("b"),A("c")]
let arr2:Array<B> = arr1.cast(B.self)//neat!
let arr3:Array<B> = arr1.cast()//epic!
NOTE:
the cast method supports protocols as well

How can I check (reflection) if a property is an Array (of any type)?

How can I check if a property is an Array (of any type)? This code always only prints "Worker". Is there a way (dynamically) to know if a property is an Array without inform the type?
final class Worker: NSObject {
var id: Int?
var array: Array<Worker>?
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let worker = Worker()
worker.id = Int(2) as Int?
worker.array = [Worker(),Worker(),Worker()]
let mirror = reflect(worker)
for i in 0..<mirror.count {
let (name, childMirror) = mirror[i]
if childMirror.disposition == .Optional {
let (newName,subChildMirror) = childMirror[0]
if subChildMirror.valueType is Array<AnyClass>.Type {
println("AnyClass")
}
if subChildMirror.valueType is Array<AnyObject>.Type {
println("AnyObject")
}
if subChildMirror.valueType is Array<Any>.Type {
println("Any")
}
if subChildMirror.valueType is Array<NSObject>.Type {
println("NSObject")
}
if subChildMirror.valueType is Array<Worker>.Type {
println("Worker")
}
}
}
}
}
Ps.: I need to deal with Array<>
An array of any type can always be casted to a NSArray. So you could check if it's an array with code like this:
if _ = subChildMirror.valueType as? NSArray {
println("Array")
}
It's also possible to dynamically get the type of the objects of that array. In my EVReflection library I do something similar. I extended the Array in order to get a new element of an object what should be in that Array. In your case you could then get the .dynamicType from dat object.
So the code would become:
let arrayType = worker.array.getTypeInstance().dynamicType
Here is the Array extension
extension Array {
/**
Get the type of the object where this array is for
:returns: The object type
*/
public func getTypeInstance<T>(
) -> T {
let nsobjectype : NSObject.Type = T.self as! NSObject.Type
let nsobject: NSObject = nsobjectype.init()
return nsobject as! T
}
}

Is there a way to dynamically instantiate a class type

I would like to do something on the lines of (pseudo code):
let class_name = "MyClass"
let RealClass = class_name()
I am familiar with http://ijoshsmith.com/2014/06/05/instantiating-classes-by-name-in-swift/
You can Use NSClassFromString to get class then cast to the type you want.
For example:
var dynamicClass = NSClassFromString("Test") as? Test.Type
if dynamicClass != nil{
var instance = dynamicClass!()
instance.test()
}
The class Test
#objc(Test)
class Test{
func test() {
println("dynamic")
}
required init() {
}
}

Resources