How to send information from one class A to another class B using rxswift ? In iOS . for example I want to send my class Account.
Maybe rxswift is not for delegates or sending information at all.
https://www.reddit.com/r/swift/comments/7pxt4h/rxswift_vs_delegation_which_is_better/
class Account
{
var myId = 0
var Name = "name"
init (myId inputMyId:Int , Name inputName: String)
{
myId = inputMyId
Name = inputName
}
}
class B
{
var myAccount : Account
func receive()
{
// here self.myAccount should be replaced with myA.myAccount
// where myA is object of class A
}
}
class A
{
var myAccount : Account
func send()
{
// here myB.myAccount should be replaced with self.myAccount
// where myB is object of class B
}
}
solution 1
create class C that will make observables equal from A and B
class SimpleC
{
func makeAB()
{
var theB : SimpleB = SimpleB()
var theA : SimpleA = SimpleA()
theA.valueAccount = theB.valueAccount
theB.receive()
theA.send()
}
}
class SimpleA
{
var myAccount : Account = Account(myId : 0, Name : "")
var valueAccount = PublishSubject<Account>()
var myB : SimpleB?
func send()
{
// here myB.myAccount should be replaced with self.myAccount
// where myB is object of class B
self.myAccount = Account(myId : 1, Name : "MicrosoftTest5")
self.valueAccount.onNext(self.myAccount)
self.myAccount = Account(myId : 2, Name : "bestWorkTest5")
self.valueAccount.onNext(self.myAccount)
}
}
class SimpleB
{
var myAccount : Account = Account(myId : 0, Name : "")
var valueAccount = PublishSubject<Account>()
let disposer = DisposeBag()
func receive()
{
// here self.myAccount should be replaced with myA.myAccount
// where myA is object of class A
valueAccount.subscribe(onNext: { (newValue) in
print("SimpleB subscribe")
self.myAccount = newValue
var theString = "Account{" + "\(self.myAccount.myId)"
theString = theString + "} {"
theString = theString + "\(self.myAccount.Name)" + "}"
print(theString)
}).addDisposableTo(disposer)
}
}
Answering this question in hope to cover two scinarios -
Case 1: passing data while object creation (I can create and wrap it in variables, and pass along), this is same as vanilla swift
This holds true in cases of pushing/ presenting as well
class Account
{
var myId = Variable(0)
var name = Variable("")
init (myId inputMyId:Int , Name inputName: String)
{
myId.value = inputMyId
name.value = inputName
}
func passDataAnotherAccount() {
let anotherAccount = AnotherAccount(id: myId, name: name)
anotherAccount.makeChangesToVars()
}
}
class AnotherAccount {
var classListenerOfMyId = Variable(0)
var classListenerOfMyName = Variable("name")
init(id: Variable<Int>, name: Variable<String>) {
self.classListenerOfMyId = id
self.classListenerOfMyName = name
}
func makeChangesToVars() {
self.classListenerOfMyId.value = self.classListenerOfMyId.value + 1
self.classListenerOfMyName.value = Date().description
}
}
case 2 (Alternative approach to DELEGATION and callbacks)
lets say you want to know about changes from class AnotherAccount in previous class, you can make use of OBSERVABLES in that case
change the method from class Account to this
func passDataAnotherAccount() {
let anotherAccount = AnotherAccount(id: myId, name: name)
anotherAccount.makeChangesToVars()
// this will observe changes from AnotherAccount class
// we just created above in my current class,
// i.e. Account class
anotherAccount.classListenerOfMyId.asObservable().subscribe(onNext:{[weak self] valueChange in
print(valueChange)
}).disposed(by: DisposeBag())
anotherAccount.classListenerOfMyName.asObservable().subscribe(onNext:{[weak self] valueChange in
print(valueChange)
}).disposed(by: DisposeBag())
}
for handling delegations/callbacks you need to observe the changes by subscribing to the underlying Variables/Subjects/Observable.
Points to Note
Please use single dispose bag for one class, unlike me, i have disposed using DisposeBag() itself.
Read More about replacing delegates in RxSwift with Observables (this will be helpful)
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)
}
}
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.
How to achieve reflection in Swift Language?
How can I instantiate a class
[[NSClassFromString(#"Foo") alloc] init];
You must put #objc(SwiftClassName) above your swift class.
Like:
#objc(SubClass)
class SubClass: SuperClass {...}
This is the way I init derived UIViewController by class name
var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass()
More information is here
In iOS 9
var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass.init()
Less hacky solution here: https://stackoverflow.com/a/32265287/308315
Note that Swift classes are namespaced now so instead of "MyViewController" it'd be "AppName.MyViewController"
Deprecated since XCode6-beta 6/7
Solution developed using XCode6-beta 3
Thanks to the answer of Edwin Vermeer I was able to build something to instantiate Swift classes into an Obj-C class by doing this:
// swift file
// extend the NSObject class
extension NSObject {
// create a static method to get a swift class for a string name
class func swiftClassFromString(className: String) -> AnyClass! {
// get the project name
if var appName: String? = NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as String? {
// generate the full name of your class (take a look into your "YourProject-swift.h" file)
let classStringName = "_TtC\(appName!.utf16count)\(appName)\(countElements(className))\(className)"
// return the class!
return NSClassFromString(classStringName)
}
return nil;
}
}
// obj-c file
#import "YourProject-Swift.h"
- (void)aMethod {
Class class = NSClassFromString(key);
if (!class)
class = [NSObject swiftClassFromString:(key)];
// do something with the class
}
EDIT
You can also do it in pure obj-c:
- (Class)swiftClassFromString:(NSString *)className {
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleName"];
NSString *classStringName = [NSString stringWithFormat:#"_TtC%d%#%d%#", appName.length, appName, className.length, className];
return NSClassFromString(classStringName);
}
I hope this will help somebody !
UPDATE: Starting with beta 6 NSStringFromClass will return your bundle name plus class name separated by a dot. So it will be something like MyApp.MyClass
Swift classes will have a constructed internal name that is build up of the following parts:
It will start with _TtC,
followed by a number that is the length of your application name,
followed by your application name,
folowed by a number that is the length of your class name,
followed by your class name.
So your class name will be something like _TtC5MyApp7MyClass
You can get this name as a string by executing:
var classString = NSStringFromClass(self.dynamicType)
Update In Swift 3 this has changed to:
var classString = NSStringFromClass(type(of: self))
Using that string, you can create an instance of your Swift class by executing:
var anyobjectype : AnyObject.Type = NSClassFromString(classString)
var nsobjectype : NSObject.Type = anyobjectype as NSObject.Type
var rec: AnyObject = nsobjectype()
It's almost the same
func NSClassFromString(_ aClassName: String!) -> AnyClass!
Check this doc:
https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/#//apple_ref/c/func/NSClassFromString
I was able to instantiate an object dynamically
var clazz: NSObject.Type = TestObject.self
var instance : NSObject = clazz()
if let testObject = instance as? TestObject {
println("yes!")
}
I haven't found a way to create AnyClass from a String (without using Obj-C). I think they don't want you to do that because it basically breaks the type system.
For swift2, I created a very simple extension to do this more quickly
https://github.com/damienromito/NSObject-FromClassName
extension NSObject {
class func fromClassName(className : String) -> NSObject {
let className = NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String + "." + className
let aClass = NSClassFromString(className) as! UIViewController.Type
return aClass.init()
}
}
In my case, i do this to load the ViewController I want:
override func viewDidLoad() {
super.viewDidLoad()
let controllers = ["SettingsViewController", "ProfileViewController", "PlayerViewController"]
self.presentController(controllers.firstObject as! String)
}
func presentController(controllerName : String){
let nav = UINavigationController(rootViewController: NSObject.fromClassName(controllerName) as! UIViewController )
nav.navigationBar.translucent = false
self.navigationController?.presentViewController(nav, animated: true, completion: nil)
}
This will get you the name of the class that you want to instantiate. Then you can use Edwins answer to instantiate a new object of your class.
As of beta 6 _stdlib_getTypeName gets the mangled type name of a variable. Paste this into an empty playground:
import Foundation
class PureSwiftClass {
}
var myvar0 = NSString() // Objective-C class
var myvar1 = PureSwiftClass()
var myvar2 = 42
var myvar3 = "Hans"
println( "TypeName0 = \(_stdlib_getTypeName(myvar0))")
println( "TypeName1 = \(_stdlib_getTypeName(myvar1))")
println( "TypeName2 = \(_stdlib_getTypeName(myvar2))")
println( "TypeName3 = \(_stdlib_getTypeName(myvar3))")
The output is:
TypeName0 = NSString
TypeName1 = _TtC13__lldb_expr_014PureSwiftClass
TypeName2 = _TtSi
TypeName3 = _TtSS
Ewan Swick's blog entry helps to decipher these strings: http://www.eswick.com/2014/06/inside-swift/
e.g. _TtSi stands for Swift's internal Int type.
In Swift 2.0 (tested in the Xcode 7.01) _20150930
let vcName = "HomeTableViewController"
let ns = NSBundle.mainBundle().infoDictionary!["CFBundleExecutable"] as! String
// Convert string to class
let anyobjecType: AnyObject.Type = NSClassFromString(ns + "." + vcName)!
if anyobjecType is UIViewController.Type {
// vc is instance
let vc = (anyobjecType as! UIViewController.Type).init()
print(vc)
}
xcode 7 beta 5:
class MyClass {
required init() { print("Hi!") }
}
if let classObject = NSClassFromString("YOURAPPNAME.MyClass") as? MyClass.Type {
let object = classObject.init()
}
string from class
let classString = NSStringFromClass(TestViewController.self)
or
let classString = NSStringFromClass(TestViewController.classForCoder())
init a UIViewController class from string:
let vcClass = NSClassFromString(classString) as! UIViewController.Type
let viewController = vcClass.init()
I am using this category for Swift 3:
//
// String+AnyClass.swift
// Adminer
//
// Created by Ondrej Rafaj on 14/07/2017.
// Copyright © 2017 manGoweb UK Ltd. All rights reserved.
//
import Foundation
extension String {
func convertToClass<T>() -> T.Type? {
return StringClassConverter<T>.convert(string: self)
}
}
class StringClassConverter<T> {
static func convert(string className: String) -> T.Type? {
guard let nameSpace = Bundle.main.infoDictionary?["CFBundleExecutable"] as? String else {
return nil
}
guard let aClass: T.Type = NSClassFromString("\(nameSpace).\(className)") as? T.Type else {
return nil
}
return aClass
}
}
The use would be:
func getViewController(fromString: String) -> UIViewController? {
guard let viewController: UIViewController.Type = "MyViewController".converToClass() else {
return nil
}
return viewController.init()
}
I think I'm right in saying that you can't, at least not with the current beta (2). Hopefully this is something that will change in future versions.
You can use NSClassFromString to get a variable of type AnyClass but there appears to be no way in Swift to instantiate it. You can use a bridge to Objective C and do it there or -- if it works in your case -- fall back to using a switch statement.
Apparently, it is not possible (anymore) to instantiate an object in Swift when the name of the class is only known at runtime. An Objective-C wrapper is possible for subclasses of NSObject.
At least you can instantiate an object of the same class as another object given at runtime without an Objective-C wrapper (using xCode Version 6.2 - 6C107a):
class Test : NSObject {}
var test1 = Test()
var test2 = test1.dynamicType.alloc()
In Swift 2.0 (tested in the beta2 of Xcode 7) it works like this:
protocol Init {
init()
}
var type = NSClassFromString(className) as? Init.Type
let obj = type!.init()
For sure the type coming from NSClassFromString have to implement this init protocol.
I expect it is clear, className is a String containing the Obj-C runtime name of the class which is by default NOT just "Foo", but this discussion is IMHO not the major topic of your question.
You need this protocol because be default all Swift classes don't implement an init method.
Looks like the correct incantation would be...
func newForName<T:NSObject>(p:String) -> T? {
var result:T? = nil
if let k:AnyClass = NSClassFromString(p) {
result = (k as! T).dynamicType.init()
}
return result
}
...where "p" stands for "packaged" – a distinct issue.
But the critical cast from AnyClass to T currently causes a compiler crash, so in the meantime one must bust initialization of k into a separate closure, which compiles fine.
I use different targets, and in this case the swift class is not found. You should replace CFBundleName with CFBundleExecutable. I also fixed the warnings:
- (Class)swiftClassFromString:(NSString *)className {
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleExecutable"];
NSString *classStringName = [NSString stringWithFormat:#"_TtC%lu%#%lu%#", (unsigned long)appName.length, appName, (unsigned long)className.length, className];
return NSClassFromString(classStringName);
}
Isn't the solution as simple as this?
// Given the app/framework/module named 'MyApp'
let className = String(reflecting: MyClass.self)
// className = "MyApp.MyClass"
Also in Swift 2.0 (possibly before?) You can access the type directly with the dynamicType property
i.e.
class User {
required init() { // class must have an explicit required init()
}
var name: String = ""
}
let aUser = User()
aUser.name = "Tom"
print(aUser)
let bUser = aUser.dynamicType.init()
print(bUser)
Output
aUser: User = {
name = "Tom"
}
bUser: User = {
name = ""
}
Works for my use case
Try this.
let className: String = String(ControllerName.classForCoder())
print(className)
I have implemented like this,
if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{
ImplementationClass.init()
}
Swift 5, easy to use, thanks to #Ondrej Rafaj's
Source code:
extension String {
fileprivate
func convertToClass<T>() -> T.Type? {
return StringClassConverter<T>.convert(string: self)
}
var controller: UIViewController?{
guard let viewController: UIViewController.Type = convertToClass() else {
return nil
}
return viewController.init()
}
}
class StringClassConverter<T> {
fileprivate
static func convert(string className: String) -> T.Type? {
guard let nameSpace = Bundle.main.infoDictionary?["CFBundleExecutable"] as? String, let aClass = NSClassFromString("\(nameSpace).\(className)") as? T.Type else {
return nil
}
return aClass
}
}
Call like this:
guard let ctrl = "ViewCtrl".controller else {
return
}
// ctrl do sth
A page jump example shown here, the hope can help you!
let vc:UIViewController = (NSClassFromString("SwiftAutoCellHeight."+type) as! UIViewController.Type).init()
self.navigationController?.pushViewController(vc, animated: true)
// Click the Table response
tableView.deselectRow(at: indexPath, animated: true)
let sectionModel = models[(indexPath as NSIndexPath).section]
var className = sectionModel.rowsTargetControlerNames[(indexPath as NSIndexPath).row]
className = "GTMRefreshDemo.\(className)"
if let cls = NSClassFromString(className) as? UIViewController.Type {
let dvc = cls.init()
self.navigationController?.pushViewController(dvc, animated: true)
}
Swift3+
extension String {
var `class`: AnyClass? {
guard
let dict = Bundle.main.infoDictionary,
var appName = dict["CFBundleName"] as? String
else { return nil }
appName.replacingOccurrences(of: " ", with: "_")
let className = appName + "." + self
return NSClassFromString(className)
}
}
Here is a good example:
class EPRocks {
#require init() { }
}
class EPAwesome : EPRocks {
func awesome() -> String { return "Yes"; }
}
var epawesome = EPAwesome.self();
print(epawesome.awesome);