Self.Type Cannot Be Directly Converted to Anyclass in Extension to Objective-C Class in Swift

Self.Type cannot be directly converted to AnyClass in extension to objective-c class in swift

This looks like a bug or an (unnecessary) restriction, so you might
consider to file a bug report at Apple. It happens only
for type methods with return type Self. As a workaround, you can write

let clsName = NSStringFromClass(self as! AnyClass).componentsSeparatedByString(".").last!

which compiles and seems to work as expected.

But there is a simpler way to get the same result:

// Swift 2:
let clsName = String(self)
// Swift 3:
let clsName = String(describing: self)

Swift init from unknown class which conforms to protocol

You will likely want to rethink this code in the future, to follow more Swift-like patterns, but it's not that complicated to convert, and I'm sure you have a lot of existing code that relies on behaving the same way.

The most important thing is that all the objects must be @objc classes. They can't be structs, and they must subclass from NSObject. This is the major reason you'd want to change this to a more Swifty solution based on Codable.

You also need to explicitly name you types. Swift adds the module name to its type names, which tends to break this kind of dynamic system. If you had a type Person, you would want to declare it:

@objc(Person)  // <=== This is the important part
class Person: NSObject {
required init(json: NSDictionary) { ... }
}

extension Person: JsonProtocol {
func convertToJSON() -> NSDictionary { ... }
}

This makes sure the name of the class is Person (like it would be in ObjC) and not MyGreatApp.Person (which is what it normally would be in Swift).

With that, in Swift, this code would be written this way:

if let className = obj[JSON_CLASS] as? String,
let jsonClass = NSClassFromString(className) as? JsonProtocol.Type {
arr.add(jsonClass.init(json: obj))
}

The key piece you were missing is as? JsonProtocol.Type. That's serving a similar function to +conformsToProtocol: plus the cast. The .Type indicates that this is a metatype check on Person.self rather than a normal type check on Person. For more on that see Metatype Type in the Swift Language Reference.

Note that the original ObjC code is a bit dangerous. The -initWithJSON must return an object. It cannot return nil, or this code will crash at the addObject call. That means that implementing JsonProtocol requires that the object construct something even if the JSON it is passed is invalid. Swift will enforce this, but ObjC does not, so you should think carefully about what should happen if the input is corrupted. I would be very tempted to change the init to an failable or throwing initializer if you can make that work with your current code.

I also suggest replacing NSDictionary and NSArray with Dictionary and Array. That should be fairly straightforward without redesigning your code.

Check if a AnyClass variable is an extension or implementation of a class

You can use isSubclassOfClass if the receiving class inherits from NSObject.

let c = UIButton.self
print(c.isSubclassOfClass(UIButton.self)) // true
print(c.isSubclassOfClass(UIView.self)) // true
print(c.isSubclassOfClass(UILabel.self)) // false

Update: Although isSubclassOfClass is defined in NSObject, it seems to be usable for Swift classes that don't explicitly derive from NSObject as well. The below works as expected in Xcode 7 beta 6.

class A {
}

class B: A {
}

class C {
}

let c: AnyClass = B.self
print(c.isSubclassOfClass(A.self)) // true
print(c.isSubclassOfClass(B.self)) // true
print(c.isSubclassOfClass(C.self)) // false

I believe that Swift classes that don't inherit from NSObject still share some of its methods for compatibility reasons. I couldn't find any official documentation on this, however, and Xcode won't even let me navigate to the declaration of isSubclassOfClass from the snippet above.

Update 2: An alternative that works regardless of whether the classes inherit from NSObject or not is to use is together with the Type property.

let c: AnyClass = UIButton.self        
print(c is UIButton.Type) // true
print(c is UIView.Type) // true
print(c is UILabel.Type) // false

let c: AnyClass = B.self
print(c is A.Type) // true
print(c is B.Type) // true
print(c is C.Type) // false

Swift language NSClassFromString

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 !

Swift 3.1 deprecates initialize(). How can I achieve the same thing?

Easy/Simple Solution

A common app entry point is an application delegate's applicationDidFinishLaunching. We could simply add a static function to each class that we want to notify on initialization, and call it from here.

This first solution is simple and easy to understand. For most cases, this is what I'd recommend. Although the next solution provides results that are more similar to the original initialize() function, it also results in slightly longer app start up times. I no longer think
it is worth the effort, performance degradation, or code complexity in most cases. Simple code is good code.

Read on for another option. You may have reason to need it (or perhaps parts of it).


Not So Simple Solution

The first solution doesn't necessarily scale so well. And what if you are building a framework, where you'd like your code to run without anyone needing to call it from the application delegate?

Step One

Define the following Swift code. The purpose is to provide a simple entry point for any class that you would like to imbue with behavior akin to initialize() - this can now be done simply by conforming to SelfAware. It also provides a single function to run this behavior for every conforming class.

protocol SelfAware: class {
static func awake()
}

class NothingToSeeHere {

static func harmlessFunction() {

let typeCount = Int(objc_getClassList(nil, 0))
let types = UnsafeMutablePointer<AnyClass>.allocate(capacity: typeCount)
let autoreleasingTypes = AutoreleasingUnsafeMutablePointer<AnyClass>(types)
objc_getClassList(autoreleasingTypes, Int32(typeCount))
for index in 0 ..< typeCount { (types[index] as? SelfAware.Type)?.awake() }
types.deallocate(capacity: typeCount)

}

}

Step Two

That's all good and well, but we still need a way to actually run the function we defined, i.e. NothingToSeeHere.harmlessFunction(), on application startup. Previously, this answer suggested using the Objective-C code to do this. However, it seems that we can do what we need using only Swift. For macOS or other platforms where UIApplication is not available, a variation of the following will be needed.

extension UIApplication {

private static let runOnce: Void = {
NothingToSeeHere.harmlessFunction()
}()

override open var next: UIResponder? {
// Called before applicationDidFinishLaunching
UIApplication.runOnce
return super.next
}

}

Step Three

We now have an entry point at application startup, and a way to hook into this from classes of your choice. All that is left to do: instead of implementing initialize(), conform to SelfAware and implement the defined method, awake().

How to get a Swift type name as a string with its namespace (or framework name)

Use String(reflecting:):

struct Bar { }

let barName = String(reflecting: Bar.self)
print(barName) // <Module>.Bar

From the Xcode 7 Release Notes:

Type names and enum cases now print and convert to String without
qualification by default. debugPrint or String(reflecting:) can still
be used to get fully qualified names.

UIView extension in Swift

You're making an extension for UIView. Not UIImageView

extension UIImageView {

func addShadow() {
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 0)
layer.shadowOpacity = 0.5
layer.shadowRadius = 5
clipsToBounds = false
}
}

Also please use the UIImageVIew directly, not the UIImage

EDIT

Assuming by your comments, i think you're trying to modify an MKAnnotationView. If so here's a question that will help you.

How to create rounded image with border and shadow as MKAnnotationView in Swift?

How to test whether generic variable is of type AnyObject

In Swift 3, everything is bridgeable to AnyObject due to the introduction of _SwiftValue (see this Q&A for more info), that can wrap anything that isn't directly bridgeable to Objective-C in an opaque Objective-C compatible box.

Therefore is AnyObject will always be true, as anything can be represented as an AnyObject via wrapping in a _SwiftValue.

One way to check whether a value is a reference type (as shown in this Q&A) is to type-check the type of the value against the metatype of AnyObject, AnyClass (aka AnyObject.Type).

For generics, if you want to check whether the static type of T is a reference type, you can do:

isObject = T.self is AnyClass

If you want to check whether the dynamic type of a value typed as T is a reference type (such as val in your example), you can use the type(of:) function on the unwrapped value, as the aforementioned Q&A suggests:

if let val = val {
isObject = type(of: val) is AnyClass

// ...
}

The difference between these two approaches is that when T is of type Any (or a non AnyObject abstract type), T.self is AnyClass will return false (which could be useful if you want a box where the value could be a reference or value type) – type(of: val) is AnyClass however, will return whether val itself is a reference type.



Related Topics



Leave a reply



Submit