Ngôn ngữ Swift NSClassFromString


83

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];

1
Hãy thử như thế này, nếu hãy để implementClass: NSObject.Type = NSClassFromString (className) as? NSObject.Type {PaymentClass.init ()}
Pushpa Raja,

Câu trả lời:


37

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 đó!


2
Bắt đầu từ phiên bản beta 7, tính năng này sẽ không hoạt động nữa.NSStringFromClass giờ sẽ chỉ trả về tên gói của bạn cùng với tên lớp được phân tách bằng dấu chấm. Vì vậy, bạn có thể sử dụng mã như: var appName: String = NSBundle.mainBundle (). ObjectForInfoDictionaryKey ("CFBundleName") làm Chuỗi? let classStringName: String = NSStringFromClass (theObject.dynamicType) return classStringName.stringByReplacingOccurrencesOfString (appName + ".", withString: "", option: NSStringCompareOptions.CaseInsensitiveSearch, range: nil
Edwin

@KevinDelord Tôi đã sử dụng kỹ thuật của bạn trong mã của mình. Nhưng biến appName không trả về giá trị chính xác khi ứng dụng có khoảng trắng trong tên của nó. Bất kỳ ý tưởng làm thế nào để sửa chữa nó? Ví dụ: "Tên ứng dụng" thay vì "Tên ứng dụng"
Loadex

Không thể tìm thấy hàm countElements. Bất kỳ giúp đỡ về điều này?
C0D3

Sử dụng cái này cho classStringName thay thế: let classStringName = "_TtC (appName! .Characters.count) (appName) (className.characters.count) (className)"
C0D3

@Loadex, Điều này không hoạt động nếu tên thực thi của bạn có khoảng trắng trong đó. Bạn cũng cần thay thế các khoảng trắng bằng dấu gạch dưới. Nó được gợi ý trong bài đăng blog (hơi khó hiểu) này: medium.com/@maximbilan/…. Mã trong bài đăng đã hoạt động, nhưng bài đăng thực tế nói về việc sử dụng tên ứng dụng và tên đích khác với những gì mã của họ đã làm.
stickj

57

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 {...}

2
NSClassFromString()chức năng cần tên do @objcthuộc tính chỉ định .
eonil

1
Thật tuyệt vời, một việc nhỏ như vậy nhưng lại có tác động rất lớn đến sức khỏe tinh thần của tôi!
Adrian_H

ai đó có thể chỉ cách sử dụng NSClassFromString () sau khi họ thực hiện điều @objc bên trên tên lớp của họ không. Tôi chỉ nhận được AnyClass! đã trở lại
Ryan Bobrowski

Nó làm việc cho tôi. Nhưng tôi có một câu hỏi là tại sao @objc(SubClass)hoạt động, nhưng @objc class SubClasskhông?
pyanfield

@pyanfield từ tài liệu Swift: "Thuộc tính objc tùy ý chấp nhận một đối số thuộc tính duy nhất, bao gồm một số nhận dạng. Số nhận dạng chỉ định tên được hiển thị với Objective-C cho thực thể mà thuộc tính objc áp dụng". Vì vậy, sự khác biệt là cách lớp con Swift của chúng ta lấy tên mà nó sẽ hiển thị cho Mục tiêu C. Trong @objc class SubClasshình thức, tên được ngụ ý giống với tên Lớp con. Và ở @objc(SubClass) class SubClassdạ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.
voiger

56

Đâ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()

11
Nếu tên Ứng dụng có chứa "-", nó nên được thay thế bằng "_"
Zitao Xiong

@RigelChen giải pháp tốt đẹp, cảm ơn nhưng nếu chúng ta chỉ cần gõ (TestViewController) và không muốn khởi tạo nó mỗi lần thì chúng ta phải làm gì?
ArgaPK

@RyanHeitner giải pháp tốt đẹp, cảm ơn nhưng nếu chúng ta chỉ cần gõ (TestViewController) và không muốn khởi tạo nó mỗi lần thì chúng ta phải làm gì?
ArgaPK

@argap Tạo một singleton và truy cập nó: let viewController = aClass.sharedInstance ()
mahboudz Ngày

27

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:

  • Nó sẽ bắt đầu bằng _TtC,
  • theo sau là một số là độ dài của tên ứng dụng của bạn,
  • theo sau là tên ứng dụng của bạn,
  • theo sau bởi một số là độ dài của tên lớp của bạn,
  • theo sau là tên lớp của bạn.

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()

Điều này sẽ chỉ hoạt động cho các lớp NSObject, còn các lớp Swift thì sao. Theo mặc định, họ không có bất kỳ init methode nào và một định dạng cho giao thức dường như không hoạt động, bạn có ý kiến ​​gì không?
Stephan

Swift 1.2 / XCode 6.3.1crash trong trường hợp của tôi với cấu trúc này
Stephan

Tôi đã tạo ra một lớp phản ánh với sự hỗ trợ cho NSCoding, in, Hashable, Equatable và JSON github.com/evermeer/EVReflection
Edwin Vermeer

Sự cố được đề cập đã được khắc phục trong Swift 2.0 ... hãy xem câu trả lời của tôi vì bạn phải gọi init .... chỉ là nsobjectype () không đúng nữa.
Stephan

1
let classString = NSStringFromClass (type (of: self))
Abhishek Thapliyal

11

Nó gần như giống nhau

func NSClassFromString(_ aClassName: String!) -> AnyClass!

Kiểm tra tài liệu này:

https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/Foundation/Mischarge/Foundation_Functions/#//apple_ref/c/func/NSClassFromString


5
Hàm đó chỉ hoạt động với NSClasscá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.
Cezary Wojcik

Tôi không ở phía trước của máy tính ... nhưng bạn có thể thử với: var myVar:NSClassFromString("myClassName")
gabuh

2
@CezaryWojcik: ps Stringkhông phải là một lớp; nó là một struct
newacct

2
@newacct D'oh, bạn nói đúng, tôi tệ. Nhưng dù sao thì cũng NSClassFromStringtrả về nilcho tất cả các lớp Swift.
Cezary Wojcik

Nếu một lớp được chú thích @objc hoặc kế thừa từ NSObject, thì nó sử dụng hệ thống đối tượng Objective-C và tham gia vào NSClassFromString, phương thức swizzling, v.v. Tuy nhiên, nếu không, thì lớp đó nằm trong mã Swift của bạn và không không sử dụng cùng một hệ thống đối tượng. Nó chưa được mô tả đầy đủ, nhưng hệ thống đối tượng của Swift dường như ít bị ràng buộc hơn so với Objective-C.
Bill

9

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 AnyClasstừ 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.


1
Tôi nghĩ câu trả lời này, mặc dù không phải về NSClassFromString, là câu trả lời tốt nhất ở đây để đưa ra cách tập trung vào Swift để thực hiện khởi tạo đối tượng động. Cám ơn vì đã chia sẻ!
Matt Long

Cảm ơn @Sulthan và Matt cũng như đã nêu bật lý do tại sao câu trả lời này nên được đưa lên hàng đầu!
Ilias Karim

7

Đố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)
}

6

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_getTypeNamecó 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ụ: _TtSiviết tắt của Intkiểu nội bộ của Swift .


"Sau đó, bạn có thể sử dụng Edwins answer để khởi tạo một đối tượng mới của lớp mình." Tôi không nghĩ rằng ref. trả lời công việc cho PureSwiftClass. Ví dụ: theo mặc định, lớp này không có phương thức init.
Stephan

6

Trong Swift 2.0 (được thử nghiệm trong 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)
}

5

xcode 7 beta 5:

class MyClass {
    required init() { print("Hi!") }
}
if let classObject = NSClassFromString("YOURAPPNAME.MyClass") as? MyClass.Type {
    let object = classObject.init()
}

3

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()

3

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()
}

2

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 AnyClassnhư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 .


Ngay cả với Swift 1.2 / XCode 6.3.1, nó có vẻ như không thể, hoặc bạn đã tìm ra giải pháp?
Stephan

2

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()

Điều này không tạo một thể hiện từ tên lớp.
Stephan

Câu trả lời của bạn không rõ ràng, có vẻ như nó không hoạt động trên các lớp Swift thuần túy, đúng không?
Stephan

2

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ừ NSClassFromStringphả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 initphương thức.


Nếu bạn có các lớp học Swift, hãy xem các câu hỏi khác của tôi: stackoverflow.com/questions/30111659
Stephan

2

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.


2

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);
}

2

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"

3
câu hỏi ban đầu là muốn lớp từ chuỗi, không phải chuỗi từ lớp.
Ryan

1

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


1

Thử đi.

let className: String = String(ControllerName.classForCoder())
print(className)

1

Tôi đã triển khai như thế này,

if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{
   ImplementationClass.init()
}

1

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
    

0

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)
}

0

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)
    }
}

Cảm ơn bạn về đoạn mã này, đoạn mã này có thể cung cấp một số trợ giúp hạn chế, tức thì. Một lời giải thích phù hợp sẽ cải thiện đáng kể giá trị lâu dài của nó bằng cách chỉ ra lý do tại sao đây là một giải pháp tốt cho vấn đề và sẽ khiến nó hữu ích hơn cho những người đọc trong tương lai với những câu hỏi tương tự khác. Vui lòng chỉnh sửa câu trả lời của bạn để thêm một số giải thích, bao gồm cả những giả định bạn đã đưa ra.
iBug 19/02/18

-6

Đâ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);

1
Điều này không làm những gì câu hỏi đang yêu cầu.
ThomasW
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.