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
orString(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
How to Enable Only Numeric Digit Keyboard iOS Swift
How to Get All the Sundays in Array of Date iOS
Xcode 6 & Swift: Black Bars Appear Above and Below the Viewcontroller on iOS 7 iPhone 5 Device
Swift Continuous Rotation Animation Not So Continuous
How to Read Heart Rate from iOS Healthkit App Using Swift
Start/Stop Image View Rotation Animations
How to Fill a Circle Color by Percentage Value
Convenience Initialization of Uinavigationcontroller Subclass Makes Subclass Constant Init Twice
Swipe to Delete on a Tableview That Is Inside a Pageviewcontroller
Segue Out of Navigation Controller
Swiftui: How to Change the Tint Color (Background Color) of a Navigationview
Launch Watch App into Middle View
iOS Swift Nsmutabledata Has No Member Appendstring
Swiftui Transition from Modal Sheet to Regular View with Navigation Link