Як досягти роздумів на швидкій мові?
Як я можу створити екземпляр класу
[[NSClassFromString(@"Foo") alloc] init];
Відповіді:
Менш невдале рішення тут: https://stackoverflow.com/a/32265287/308315
Зверніть увагу, що класи Swift тепер мають простір імен, тому замість "MyViewController" це буде "AppName.MyViewController"
Заборонено з XCode6-beta 6/7
Рішення, розроблене з використанням XCode6-beta 3
Завдяки відповіді Едвіна Вермеера я зміг побудувати щось для створення екземплярів класів Swift в класі Obj-C, виконавши це:
// 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
}
РЕДАГУВАТИ
Ви також можете зробити це в чистому 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);
}
Сподіваюся, це комусь допоможе!
Ви повинні поставити @objc(SwiftClassName)
вище вашого швидкого класу.
Люблю:
@objc(SubClass)
class SubClass: SuperClass {...}
NSClassFromString()
функції потрібно ім'я, вказане @objc
атрибуцією.
@objc(SubClass)
працює, а @objc class SubClass
ні?
@objc class SubClass
формі ім’я передбачається таким самим, як ім’я SubClass. А у @objc(SubClass) class SubClass
формі це вказано безпосередньо. Я думаю, компілятор просто не може з якихось причин зрозуміти це сам по собі в першій формі.
Це спосіб, яким я ініціював виведення UIViewController за назвою класу
var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass()
Більше інформації тут
В iOS 9
var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass.init()
ОНОВЛЕННЯ: Починаючи з бета-версії 6 NSStringFromClass поверне ім’я вашого пакета та ім’я класу, відокремлене крапкою. Тож це буде щось на зразок MyApp.MyClass
Класи Swift матимуть побудовану внутрішню назву, яка складається з таких частин:
Отже, ім’я вашого класу матиме щось на зразок _TtC5MyApp7MyClass
Ви можете отримати це ім'я у вигляді рядка, виконавши:
var classString = NSStringFromClass(self.dynamicType)
Оновлення в Swift 3 змінилося на:
var classString = NSStringFromClass(type(of: self))
Використовуючи цей рядок, ви можете створити екземпляр свого класу Swift, виконавши:
var anyobjectype : AnyObject.Type = NSClassFromString(classString)
var nsobjectype : NSObject.Type = anyobjectype as NSObject.Type
var rec: AnyObject = nsobjectype()
Це майже однаково
func NSClassFromString(_ aClassName: String!) -> AnyClass!
Перевірте цей документ:
NSClass
класами, а не з класами Swift. NSClassFromString("String")
повертається nil
, але NSClassFromString("NSString")
не повертається .
var myVar:NSClassFromString("myClassName")
String
- це не клас; це структура
NSClassFromString
повернення nil
для всіх класів Swift також у будь-якому випадку.
Я зміг створити об'єкт динамічно
var clazz: NSObject.Type = TestObject.self
var instance : NSObject = clazz()
if let testObject = instance as? TestObject {
println("yes!")
}
Я не знайшов способу створення AnyClass
з String
(без використання Obj-C). Я думаю, вони не хочуть, щоб ти це робив, оскільки це в основному порушує систему типів.
Для swift2 я створив дуже просте розширення, щоб зробити це швидше 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()
}
}
У моєму випадку я роблю це, щоб завантажити потрібний ViewController:
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)
}
Це дасть вам ім’я класу, який ви хочете створити. Тоді ви можете використовувати відповідь Едвінса щоб створити інстанцію нового об’єкта вашого класу.
Станом на бета-версію 6 _stdlib_getTypeName
отримує спотворене ім'я типу змінної. Вставте це на порожній дитячий майданчик:
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))")
Вихід:
TypeName0 = NSString
TypeName1 = _TtC13__lldb_expr_014PureSwiftClass
TypeName2 = _TtSi
TypeName3 = _TtSS
Запис у блозі Юана Свіка допомагає розшифрувати ці рядки: http://www.eswick.com/2014/06/inside-swift/
наприклад, _TtSi
означає внутрішній Int
тип Свіфта .
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)
}
рядок з класу
let classString = NSStringFromClass(TestViewController.self)
або
let classString = NSStringFromClass(TestViewController.classForCoder())
ініціювати клас UIViewController із рядка:
let vcClass = NSClassFromString(classString) as! UIViewController.Type
let viewController = vcClass.init()
Я використовую цю категорію для 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
}
}
Використання буде:
func getViewController(fromString: String) -> UIViewController? {
guard let viewController: UIViewController.Type = "MyViewController".converToClass() else {
return nil
}
return viewController.init()
}
Думаю, я правий, кажучи, що ви не можете, принаймні, не з поточною бета-версією (2). Сподіваємось, це щось зміниться в наступних версіях.
Ви можете використовувати, NSClassFromString
щоб отримати змінну типу, AnyClass
але, схоже, у Swift немає способу створити її екземпляр. Ви можете використовувати міст до Цілі C і зробити це там, або - якщо це працює у Вашому випадку - повернутися до використання оператора switch .
Очевидно, неможливо (більше) створити екземпляр об'єкта в Swift, коли ім'я класу відомо лише під час виконання. Обгортка Objective-C можлива для підкласів NSObject.
Принаймні ви можете створити екземпляр об'єкта того самого класу, що й інший об'єкт, заданий під час виконання, без обгортки Objective-C (використовуючи xCode версії 6.2 - 6C107a):
class Test : NSObject {}
var test1 = Test()
var test2 = test1.dynamicType.alloc()
У Swift 2.0 (протестовано в бета-версії Xcode 7) це працює так:
protocol Init {
init()
}
var type = NSClassFromString(className) as? Init.Type
let obj = type!.init()
Точно тип, що походить NSClassFromString
, повинен реалізувати цей протокол ініціювання.
Я сподіваюся, це зрозуміло, className
це рядок, що містить ім'я середовища виконання класу Obj-C, яке за замовчуванням НЕ просто "Foo", але це обговорення IMHO не є основною темою вашого запитання.
Вам потрібен цей протокол, оскільки за замовчуванням усі класи Swift не реалізують init
метод.
Схоже, правильним заклинанням буде ...
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
}
... де "p" означає "упакований" - окрема проблема.
Але критичний привід з AnyClass на T в даний час спричиняє збій компілятора, тому тим часом потрібно перевести ініціалізацію k в окреме закриття, яке добре компілюється.
Я використовую різні цілі, і в цьому випадку швидкий клас не знайдений. Вам слід замінити CFBundleName на CFBundleExecutable. Я також виправив попередження:
- (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);
}
Хіба рішення не таке просте, як це?
// Given the app/framework/module named 'MyApp'
let className = String(reflecting: MyClass.self)
// className = "MyApp.MyClass"
Також у Swift 2.0 (можливо, раніше?) Ви можете отримати доступ до типу безпосередньо з dynamicType
властивістю
тобто
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)
Вихідні дані
aUser: User = {
name = "Tom"
}
bUser: User = {
name = ""
}
Працює для мого випадку використання
Я реалізував так,
if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{
ImplementationClass.init()
}
Swift 5 , простий у використанні, завдяки @Ondrej Rafaj's
Вихідний код:
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
}
}
Телефонуйте так:
guard let ctrl = "ViewCtrl".controller else {
return
}
// ctrl do sth
Приклад переходу на сторінку, показаний тут, надія може вам допомогти!
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)
}
Свіфт3 +
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)
}
}
Ось хороший приклад:
class EPRocks {
@require init() { }
}
class EPAwesome : EPRocks {
func awesome() -> String { return "Yes"; }
}
var epawesome = EPAwesome.self();
print(epawesome.awesome);