Làm thế nào để đạt được phản xạ trong Ngôn ngữ Swift?
Làm cách nào để tạo một lớp học
[[NSClassFromString(@"Foo") alloc] init];
Câu trả lời:
Giải pháp ít hacky hơn tại đây: https://stackoverflow.com/a/32265287/308315
Lưu ý rằng bây giờ các lớp Swift có không gian tên nên thay vì "MyViewController", nó sẽ là "AppName.MyViewController"
Không được chấp nhận kể từ XCode6-beta 6/7
Giải pháp được phát triển bằng XCode6-beta 3
Nhờ câu trả lời của Edwin Vermeer, tôi đã có thể xây dựng thứ gì đó để khởi tạo các lớp Swift thành một lớp obj-C bằng cách thực hiện điều này:
// 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
}
BIÊN TẬP
Bạn cũng có thể làm điều đó trong obj-c thuần túy:
- (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);
}
Tôi hy vọng điều này sẽ giúp ai đó!
Bạn phải đặt @objc(SwiftClassName)
trên lớp nhanh nhẹn của bạn.
Giống:
@objc(SubClass)
class SubClass: SuperClass {...}
NSClassFromString()
chức năng cần tên do @objc
thuộc tính chỉ định .
@objc(SubClass)
hoạt động, nhưng @objc class SubClass
không?
@objc class SubClass
hình thức, tên được ngụ ý giống với tên Lớp con. Và ở @objc(SubClass) class SubClass
dạng nó được chỉ định trực tiếp. Tôi đoán rằng trình biên dịch không thể tự tìm ra nó ở dạng đầu tiên vì một số lý do.
Đây là cách tôi bắt nguồn từ UIViewController theo tên lớp
var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass()
Thông tin thêm ở đây
Trong iOS 9
var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass.init()
CẬP NHẬT: Bắt đầu với phiên bản beta 6 NSStringFromClass sẽ trả về tên gói cộng với tên lớp được phân tách bằng dấu chấm. Vì vậy, nó sẽ giống như MyApp.MyClass
Các lớp Swift sẽ có tên bên trong được xây dựng được tạo thành từ các phần sau:
Vì vậy, tên lớp của bạn sẽ giống như _TtC5MyApp7MyClass
Bạn có thể lấy tên này dưới dạng một chuỗi bằng cách thực thi:
var classString = NSStringFromClass(self.dynamicType)
Cập nhật Trong Swift 3, điều này đã thay đổi thành:
var classString = NSStringFromClass(type(of: self))
Sử dụng chuỗi đó, bạn có thể tạo một phiên bản của lớp Swift của mình bằng cách thực thi:
var anyobjectype : AnyObject.Type = NSClassFromString(classString)
var nsobjectype : NSObject.Type = anyobjectype as NSObject.Type
var rec: AnyObject = nsobjectype()
Nó gần như giống nhau
func NSClassFromString(_ aClassName: String!) -> AnyClass!
Kiểm tra tài liệu này:
NSClass
các lớp chứ không phải các lớp Swift. NSClassFromString("String")
trả lại nil
, nhưng NSClassFromString("NSString")
không.
var myVar:NSClassFromString("myClassName")
String
không phải là một lớp; nó là một struct
NSClassFromString
trả về nil
cho tất cả các lớp Swift.
Tôi đã có thể khởi tạo động một đối tượng
var clazz: NSObject.Type = TestObject.self
var instance : NSObject = clazz()
if let testObject = instance as? TestObject {
println("yes!")
}
Tôi chưa tìm thấy cách tạo AnyClass
từ a String
(mà không sử dụng obj-C). Tôi nghĩ họ không muốn bạn làm điều đó vì về cơ bản nó phá vỡ hệ thống loại.
Đối với swift2, tôi đã tạo một tiện ích mở rộng rất đơn giản để thực hiện việc này nhanh chóng hơn 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()
}
}
Trong trường hợp của tôi, tôi làm điều này để tải ViewController mà tôi muốn:
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)
}
Thao tác này sẽ giúp bạn có được tên của lớp mà bạn muốn khởi tạo. Sau đó, bạn có thể sử dụng Edwins answer để khởi tạo một đối tượng mới trong lớp của bạn.
Kể từ phiên bản beta 6, _stdlib_getTypeName
có tên kiểu bị xáo trộn của một biến. Dán cái này vào một sân chơi trống:
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))")
Đầu ra là:
TypeName0 = NSString
TypeName1 = _TtC13__lldb_expr_014PureSwiftClass
TypeName2 = _TtSi
TypeName3 = _TtSS
Mục blog của Ewan Swick giúp giải mã những chuỗi này: http://www.eswick.com/2014/06/inside-swift/
ví dụ: _TtSi
viết tắt của Int
kiểu nội bộ của Swift .
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()
}
chuỗi từ lớp
let classString = NSStringFromClass(TestViewController.self)
hoặc là
let classString = NSStringFromClass(TestViewController.classForCoder())
init một lớp UIViewController từ chuỗi:
let vcClass = NSClassFromString(classString) as! UIViewController.Type
let viewController = vcClass.init()
Tôi đang sử dụng danh mục này cho 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
}
}
Việc sử dụng sẽ là:
func getViewController(fromString: String) -> UIViewController? {
guard let viewController: UIViewController.Type = "MyViewController".converToClass() else {
return nil
}
return viewController.init()
}
Tôi nghĩ tôi đúng khi nói rằng bạn không thể, ít nhất là không với bản beta hiện tại (2). Hy vọng rằng đây là một cái gì đó sẽ thay đổi trong các phiên bản tương lai.
Bạn có thể sử dụng NSClassFromString
để lấy một biến kiểu AnyClass
nhưng dường như không có cách nào trong Swift để khởi tạo nó. Bạn có thể sử dụng một cầu nối với Mục tiêu C và thực hiện nó ở đó hoặc - nếu nó hoạt động trong trường hợp của bạn - quay lại sử dụng câu lệnh switch .
Rõ ràng, không thể (nữa) khởi tạo một đối tượng trong Swift khi tên của lớp chỉ được biết trong thời gian chạy. Có thể có một trình bao bọc Objective-C cho các lớp con của NSObject.
Ít nhất bạn có thể khởi tạo một đối tượng cùng lớp với một đối tượng khác được cung cấp trong thời gian chạy mà không có trình bao bọc Objective-C (sử dụng xCode Phiên bản 6.2 - 6C107a):
class Test : NSObject {}
var test1 = Test()
var test2 = test1.dynamicType.alloc()
Trong Swift 2.0 (được thử nghiệm trong bản beta2 của Xcode 7), nó hoạt động như sau:
protocol Init {
init()
}
var type = NSClassFromString(className) as? Init.Type
let obj = type!.init()
Để chắc chắn loại đến từ NSClassFromString
phải triển khai giao thức init này.
Tôi hy vọng nó là rõ ràng, className
là một Chuỗi chứa tên thời gian chạy obj-C của lớp theo mặc định KHÔNG chỉ là "Foo", nhưng cuộc thảo luận này IMHO không phải là chủ đề chính của câu hỏi của bạn.
Bạn cần giao thức này vì mặc định tất cả các lớp Swift không triển khai một init
phương thức.
Có vẻ như câu thần chú chính xác sẽ là ...
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
}
... trong đó "p" là viết tắt của "packaged" - một vấn đề khác biệt.
Nhưng quá trình truyền quan trọng từ AnyClass sang T hiện đang gây ra sự cố trình biên dịch, vì vậy trong thời gian chờ đợi, người ta phải bắt việc khởi tạo k thành một bao đóng riêng biệt, điều này biên dịch tốt.
Tôi sử dụng các mục tiêu khác nhau và trong trường hợp này không tìm thấy lớp nhanh. Bạn nên thay thế CFBundleName bằng CFBundleExecutable. Tôi cũng đã sửa các cảnh báo:
- (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);
}
Không phải là giải pháp đơn giản như thế này?
// Given the app/framework/module named 'MyApp'
let className = String(reflecting: MyClass.self)
// className = "MyApp.MyClass"
Ngoài ra trong Swift 2.0 (có thể trước đó?), Bạn có thể truy cập trực tiếp vào loại với dynamicType
tính
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)
Đầu ra
aUser: User = {
name = "Tom"
}
bUser: User = {
name = ""
}
Hoạt động cho trường hợp sử dụng của tôi
Tôi đã triển khai như thế này,
if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{
ImplementationClass.init()
}
Swift 5 , dễ sử dụng, nhờ @Ondrej Rafaj's
Mã nguồn:
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
}
}
Gọi như thế này:
guard let ctrl = "ViewCtrl".controller else {
return
}
// ctrl do sth
Một ví dụ về nhảy trang được hiển thị ở đây, hy vọng có thể giúp bạn!
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)
}
}
Đây là một ví dụ tốt:
class EPRocks {
@require init() { }
}
class EPAwesome : EPRocks {
func awesome() -> String { return "Yes"; }
}
var epawesome = EPAwesome.self();
print(epawesome.awesome);