Swift func that takes a Metatype? - ios

As Apple says in the Metatype Type section in Swift's docs:
A metatype type refers to the type of any type, including class types, structure types, enumeration types, and protocol types.
Is there a base class to refer to any class, struct, enum, or protocol (eg MetaType)?
My understanding is that protocol types are limited to use as a generic constraint, because of Self or associated type requirements (well, this is what an Xcode error was telling me).
So, with that in mind, maybe there is a Class base class for identifying class references? Or a Type base class for all constructable types (class, struct, enum)? Other possibilities could be Protocol, Struct, Enum, and Closure.
See this example if you don't get what I mean yet.
func funcWithType (type: Type) {
// I could store this Type reference in an ivar,
// as an associated type on a per-instance level.
// (if this func was in a class, of course)
self.instanceType = type
}
funcWithType(String.self)
funcWithType(CGRect.self)
While generics work great with 1-2 constant associated types, I wouldn't mind being able to treat associated types as instance variables.
Thanks for any advice!

This works:
func funcWithType (type: Any.Type) {
}
funcWithType(String.self)
funcWithType(CGRect.self)

Given your example an implementation would be:
// protocol that requires an initializer so you can later call init from the type
protocol Initializeable {
init()
}
func funcWithType (type: Initializeable.Type) {
// make a new instance of the type
let instanceType = type()
// in Swift 2 you have to explicitly call the initializer:
let instanceType = type.init()
// in addition you can call any static method or variable of the type (in this case nothing because Initializeable doesn't declare any)
}
// make String and CGRect conform to the protocol
extension String: Initializeable {}
extension CGRect: Initializeable {}
funcWithType(String.self)
funcWithType(CGRect.self)

Related

Swift init from unknown class which conforms to protocol

I'm currently working on updating a large project from Objective-C to Swift and I'm stumped on how to mimic some logic. Basically we have a class with a protocol which defines a couple functions to turn any class into a JSON representation of itself.
That protocol looks like this:
#define kJsonSupport #"_jsonCompatible"
#define kJsonClass #"_jsonClass"
#protocol JsonProtocol <NSObject>
- (NSDictionary*)convertToJSON;
- (id)initWithJSON:(NSDictionary* json);
#end
I've adapted that to Swift like this
let JSON_SUPPORT = "_jsonCompatible"
let JSON_CLASS = "_jsonClass"
protocol JsonProtocol
{
func convertToJSON() -> NSDictionary
init(json: NSDictionary)
}
One of the functions in the ObjC class runs the convertToJSON function for each object in an NSDictionary which conforms to the protocol, and another does the reverse, creating an instance of the object with the init function. The output dictionary also contains two keys, one denoting that the dictionary in question supports this protocol (kJsonSupport: BOOL), and another containing the NSString representation of the class the object was converted from (kJsonClass: NSString). The reverse function then uses both of these to determine what class the object was converted from to init a new instance from the given dictionary.
All of the classes are anonymous to the function itself. All we know is each class conforms to the protocol, so we can call our custom init function on it.
Here's what it looks like in ObjC:
Class rootClass = NSClassFromString(obj[kJsonClass]);
if([rootClass conformsToProtocol:#protocol(JsonProtocol)])
{
Class<JsonProtocol> jsonableClass = (Class<JsonProtocol>)rootClass;
[arr addObject:[[((Class)jsonableClass) alloc] initWithJSON:obj]];
}
However, I'm not sure how to make this behavior in Swift.
Here's my best attempt. I used Swiftify to try and help me get there, but the compiler isn't happy with it either:
let rootClass : AnyClass? = NSClassFromString(obj[JSON_CLASS] as! String)
if let _rootJsonClass = rootClass as? JsonProtocol
{
weak var jsonClass = _rootJsonClass as? AnyClass & JsonProtocol
arr.add(jsonClass.init(json: obj))
}
I get several errors on both the weak var line and the arr.add line, such as:
Non-protocol, non-class type 'AnyClass' (aka 'AnyObject.Type') cannot be used within a protocol-constrained type
'init' is a member of the type; use 'type(of: ...)' to initialize a new object of the same dynamic type
Argument type 'NSDictionary' does not conform to expected type 'JsonProtocol'
Extraneous argument label 'json:' in call
Is there any way for me to instantiate from an unknown class which conforms to a protocol using a custom protocol init function?
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.

Swift cast object to type and protocol at the same time

How can I cast a given object to a type and a protocol in order to call some methods that are defined as an extension
For Example:
extension Identifiable where Self: NSManagedObject, Self: JsonParseDescriptor {
func someMethod() { }
}
Now I have an object that I retrieved from Core data and I would like to cast it to the above protocols in order to call someMethod on it. I could cast to the protocols using protocol<Identifiable, JsonParseDescriptor> , but how can I include the NSManagedObejct type in it also?
Thanks
As of Swift 4, it is now possible to make mentioned cast directly without tricky workarounds. The task is accomplished similarly as we do protocol composition:
var myVar = otherVar as! (Type & Protocol)
No more need for extensions and bridge protocols.
What you're looking for it called a concrete same-type requirement. Unfortunately, it's not yet possible in Swift.
See ticket SR-1009 and SR-1447 for details. You should also checkout this answer.
In the mean-while, you can extend NSManagedObject with a dummy protocol with the methods you need:
protocol _NSManagedObject {
//the methods you want
}
extension NSManagedObject: _NSManagedObject {}
extension Identifiable where Self: _NSManagedObject, Self: JsonParseDescriptor {
func someMethod() { }
}

Swift: static property in protocol extension CAN be overridden, but why?

I watched "Protocol-Oriented Programming in Swift" and read the related docs, but I still think there is a conflict in the following sample code (try it in a Playground).
protocol X {
// The important part is "static" keyword
static var x: String { get }
}
extension X {
// Here "static" again
static var x: String {
get {
return "xxx"
}
}
}
// Now I'm going to use the protocol in a class, BUT
// in classes "static" is like "final class",
// i.e. CAN'T BE OVERRIDDEN, right?
// But I'd prefer to have the ability to override that property,
// so I'll try to use "class" keyword.
// Will it break the program? (spoiler: no!)
class Y: X {
// Here we are allowed to use "class" keyword (but why?).
class var x: String {
get {
return "yyy"
}
}
}
class Z: Y {
override class var x: String {
get {
return "zzz"
}
}
}
class Test<T: X> {
func test() -> String {
return T.x
}
}
// And finally the property is successfully overridden (but why?).
print(Test<Z>().test()) // "zzz\n"
Does this actually mean that static keyword from protocol (and possible default implementation) can be legitimately replaced with class keyword when the protocol used in classes? Do you know any references confirming that?
From Language Reference / Declarations we know the following.
Function Declaration
...
Special Kinds of Methods
...
Methods associated with a type rather than an instance of a type must be marked with the static declaration modifier for enumerations and structures or the class declaration modifier for classes.
I.e. the static keyword is (mainly) for enumerations and structures and the class keyword is for classes.
There is also such a note:
Type Variable Properties
...
NOTE
In a class declaration, the keyword static has the same effect as marking the declaration with both the class and final declaration modifiers.
I.e. the static keyword actually can be used in class declaration and will mean final class.
So what about protocols?
Protocol Method Declaration
...
To declare a class or static method requirement in a protocol declaration, mark the method declaration with the static declaration modifier. Classes that implement this method declare the method with the class modifier. Structures that implement it must declare the method with the static declaration modifier instead. If you’re implementing the method in an extension, use the class modifier if you’re extending a class and the static modifier if you’re extending a structure.
Here the docs state that we should replace the static keyword from protocol declaration with the class keyword when implementing the protocol in a class or a class extension (and this is the exact answer to the original question).
Bonus
There are two cases in which protocol adoption will be restricted to classes only. The first (and the least explicit) is when a protocol includes optional members:
Protocol Declaration
...
By default, types that conform to a protocol must implement all properties, methods, and subscripts declared in the protocol. That said, you can mark these protocol member declarations with the optional declaration modifier to specify that their implementation by a conforming type is optional. The optional modifier can be applied only to protocols that are marked with the objc attribute. As a result, only class types can adopt and conform to a protocol that contains optional member requirements. ...
And the second (explicit; the next paragraph):
To restrict the adoption of a protocol to class types only, mark the protocol with the class requirement by writing the class keyword as the first item in the inherited protocols list after the colon. ...
But neither of them changes the rules considering the static and the class keywords applicability.

Swift dynamic variable can't be of type Printable

I have a Swift project that contains two UITableViewControllers. The second UITableViewController is linked to a MVC model called Model. According to the UITableViewCell I select in the first UITableViewController, I want to initialize some properties of Model with Ints or Strings. Therefore, I've decided to define those properties with Printable protocol type. In the same time, I want to perform Key Value Observing on one of these properties.
Right now, Model looks like this:
class Model: NSObject {
let title: String
let array: [Printable]
dynamic var selectedValue: Printable //error message
init(title: String, array: [Printable], selectedValue: Printable) {
self.title = title
self.array = array
self.selectedValue = selectedValue
}
}
The problem here is that the following error message appears on the selectedValue declaration line:
Property cannot be marked dynamic because its type cannot be
represented in Objective-C
If I go to the Xcode Issue Navigator, I can also read the following line:
Protocol 'Printable' is not '#objc'
Is there any workaround?
There is no way to do what you want. Non-#objc protocols cannot be represented in Objective-C. One reason is that Non-#objc protocols can represent non-class types (and indeed, you said that you wanted to use it for Int and String, both non-class types), and protocols in Objective-C are only for objects.
KVO is a feature designed for Objective-C, so you must think about what you expect it to see from the perspective of Objective-C. If you were doing this in Objective-C, you would not want to have a property that could either be an object like id or a non-object like int -- you can't even declare that. Instead, as you said in your comment, you probably want it to be just objects. And you want to be able to use Foundation's bridging to turn Int into NSNumber * and String into NSString *. These are regular Cocoa classes that inherit from NSObject, which implements Printable.
So it seems to me you should just use NSObject or NSObjectProtocol.
Unfortunately ObjC does not treat protocols as types, they are just a convenient way of grouping members. Under the covers they are of type Any, so regretfully you will have to make the property Any and cast to Printable.
The best I can thing of is:
dynamic var selectedValue: Any
var printableValue : Printable {
get {
return (Printable)selectedValue
}
set {
selectedValue = newValue
}
}

How to define an array of objects conforming to a protocol?

Given:
protocol MyProtocol {
typealias T
var abc: T { get }
}
And a class that implements MyProtocol:
class XYZ: MyProtocol {
typealias T = SomeObject
var abc: T { /* Implementation */ }
}
How can I define an array of objects conforming to MyProtocol?
var list = [MyProtocol]()
Gives (together with a ton of SourceKit crashes) the following error:
Protocol 'MyProtocol' can only be used as a generic constraint because it has Self or associated type requirements
Even though the typealias is in fact defined in MyProtocol.
Is there a way to have a list of object conforming to a protocol AND having a generic constraint?
The problem is about using the generics counterpart for protocols, type aliases.
It sounds weird, but if you define a type alias, you cannot use the protocol as a type, which means you cannot declare a variable of that protocol type, a function parameter, etc. And you cannot use it as the generic object of an array.
As the error say, the only usage you can make of it is as a generic constraint (like in class Test<T:ProtocolWithAlias>).
To prove that, just remove the typealias from your protocol (note, this is just to prove, it's not a solution):
protocol MyProtocol {
var abc: Int { get }
}
and modify the rest of your sample code accordingly:
class XYZ: MyProtocol {
var abc: Int { return 32 }
}
var list = [MyProtocol]()
You'll notice that it works.
You are probably more interested in how to solve this problem. I can't think of any elegant solution, just the following 2:
remove the typealias from the protocol and replace T with AnyObject (ugly solution!!)
turn the protocol into a class (but that's not a solution that works in all cases)
but as you may argue, I don't like any of them. The only suggestion I can provide is to rethink of your design and figure out if you can use a different way (i.e. not using typealiased protocol) to achieve the same result.

Resources