Neat method signature to find subclasses of a given type - swift

I have a method that I have implemented, but am having trouble getting the method signature to be elegant.
The method returns all classes which are a subclass of a specified class. My current method signature looks like :
public func allSubclassesOf(baseClass: AnyClass) -> [AnyClass]
which is fine, apart from the return type being AnyClass which means I always end up with a messy cast like this:
allSubClassesOf(UIView).forEach { (subclass:AnyClass) in
UIView *sigh = subclass as! UIView //!< Gross and unnecessary
...
}
This seems like something that generics should be able to solve :)
Things I've tried :
public func allSubclassesOf<T>() -> [T]
Nope, you're not allowed to add generics to a function like that.
extension NSObject {
class func allSubclasses() -> [Self]
}
Nope, Self isn't available here.
Does anyone know how I can pass a type into this method and have the compiler know what type the returning array would hold; removing the need for a cast?

I am not sure of the implementation of your method but you would need to do something like the following.
What I am doing here is applying a generic type to the method function then saying that I would expect the object type as an argument and return instances of the type in an array.
You could add it as an extension, however without more examples for your code I can't help any further
func subclasses<T>(type: T.Type) -> [T] {
....
}
subclasses(UIView).forEach { (view: UIView) in
print(view)
}

Related

How to get inheritance to work for parameters inside a closure?

protocol Proto {
associatedtype Entity: NSManagedObject
}
extension Proto {
func subscribe(completionBlock: #escaping (Entity) -> ()) {
var helper = Helper()
helper.subscribe(completionBlock) //error out
}
}
class Helper {
func subscribe(completionBlock: #escaping (NSManagedObject) -> ()){}
}
Even though entity will always be of type NSManagedObject, but it won't let me pass that closure into another function that accepts NSmanagedObject. How to fix?
I can make the code work by doing this:
helper.subscribe(completionBlock as! (NSManagedObject) -> ())
But why do I need to force cast when Entity is of type NSManagedObject? Also are there any risk of it crashing at runtime if I cast like this? Is there ever a scenario if the cast will fail?
Okay, so after some back and forth I believe the question amounts to this. Given this function:
func foo(v:UIView, f:(UIView)->Void) {
f(v)
}
...why is it illegal to pass a (UIButton)->Void into foo as its f parameter?
Well, let's imagine that we could do that. (This is called a reductio ad absurdum proof.) Then we could write this:
let bar : ((UIButton)->Void) = {button in print(button.currentTitle)}
foo(v:UISwitch(), f:bar)
And what would happen? foo would pass a UISwitch into f - legally, because f is typed as taking a UIView, and a UISwitch is indeed a UIView. And we would crash, because a UIView has no currentTitle property.
Do you see the problem? Characterising a function as a (UIButton)->Void gives that function the right, in the eyes of the compiler, to talk to the parameter as if it were a UIButton. But on the outside, f is typed as a function that takes a UIView. So it is legal to pass any UIView into it. Therefore the compiler needs to know that the function being held by f does not think that its parameter is a UIButton, lest it do that very sort of thing.
So the compiler very rightly stops you right at the door and doesn't let you pass bar into foo in the first place. The rule is that function types are contravariant on their parameter types, for passing.

Constraining associated types in Swift protocol

I'm writing a little code snippet to learn about how associated types work, but I've come across an error I'm not sure how to interpret. The code I've written is posted below for reference.
// A basic protocol
protocol Doable {
func doSomething() -> Bool
}
// An extension that adds a method to arrays containing Doables
extension Array where Element: Doable {
func modify(using function:(Array<Doable>)->Array<Doable>) -> Array<Doable> {
return function(self)
}
}
// Another protocol with an associated type constrained to be Doable
protocol MyProtocol {
associatedtype MyType: Doable
func doers() -> Array<MyType>
func change(_:Array<MyType>) -> Array<MyType>
}
// An simple extension
extension MyProtocol {
func modifyDoers() -> Array<MyType> {
return doers().modify(using: change)
}
}
I've constrained MyType to be Doable, but the compiler complains that it cannot convert (Array<Self.MyType>) -> Array<Self.MyType> to expected argument type (Array<Doable>) -> Array<Doable>. Can anybody explain what's going on here and how I can make the compiler happy?
As the error message says, the modify function expects arguments with the type Array<Doable> and you're passing arguments with the type Array<MyType>.
The issue stems from the definition of modify, where you're explicitly using Doable in the parameters, which excludes all other types but Doable – and as associated types are not typealiases, MyType can't be converted to Doable.
The fix is to change all occurrences of Doable in the modify function to Element, as is portrayed in the Swift documentation: Extensions with a Generic Where Clause.

Array of generics with metatypes (using AlamofireObjectMapper)

I suspect I may be making the same mistake as described by Rob in this post here in that I should be doing this whole thing another way, but with that in mind:
I'm trying to use AlamofireObjectMapper in a generic way. It has a protocol
public protocol Mappable
I then have various model classes that adopt it
class Dog: Mappable
class Cat: Mappable
class Bird: Mappable
I have this method
func loadEntityArray<T: Mappable>(type: T.Type)
and the reason this is generic is because it calls a function load() that needs a completion block that uses this generic param. The 'type' argument is never actually used, but you can't make a func generic without the generic type being in the func's parameter list.
func load(completion:(Response<T, NSError> -> Void))
loadEntityArray is called from another method
func letsgo() { loadEntityArray(Dog.self); loadEntityArray(Cat.self) }
So far so good, this all works. But I want to pass an array of which models to load to letsgo() and I can't work out how to do this. If I change letsgo() to
func letsgo<T:Mappable>(models: [T.Type]) {
for mod in models {
loadEntityArray(mod)
}
}
and then call letsgo() with 1 param like
letsgo([Dog.self])
it works, but as soon as I have an array of 2 or more, I get a compiler error 'cannot convert value of type NSArray to expected argument type [_.Type]' I don't now how I would explicitly type this array either.
letsgo([Dog.self, Cat.self])
I've tried various permutations and nothing seems to work. Am I doing something impossible here? It seems to me the compiler has enough information at compile time for this to work, so I'm not sure if this is a syntax thing or I'm doing something wrong here with generics.
Looking at your function :
func letsgo<T:Mappable>(models: [T.Type])
Its model parameter should be an Array of all the same Type. So an array of only Dog Types for example. That's why
letsgo([Dog.self])
works but
letsgo([Dog.self, Cat.self])
won't as it has multiple Types.
The solution :
Use Mappable.Type directly :
func loadEntityArray(type: Mappable.Type) {}
func letsgo(models: [Mappable.Type]) {
for mod in models {
loadEntityArray(mod.self)
}
}
And cast your array of Types as an array of Mappable Types :
letsgo([Dog.self, Cat.self] as [Mappable.Type])
Hope this achieves what you're looking for !
So in the end I came to the conclusion that this is not possible. I changed the code such that I pass in an enum to letsgo() and in a switch statement on that enum I call loadEntityArray(Dog.self) etc. etc. with an explicitly coded call, for each of my possible types. Then the compiler can see all the possible types and is happy.

Can I specialize a generic function on a type passed as a parameter?

I want to be able to specialize a generic function by passing a type as an argument, instead of having to declare a variable having the desired type and writing an assignment to that variable.
In my use case, I'm walking up the responder chain looking for an object that conforms to a certain protocol. If found, I'd like to call a method on that object.
I'm trying to do it in a "swift-y" (i.e. type safe) way.
The code I'm currently using looks like this:
if let r:UndoManager = self.findResponder() {
r.undo(...)
}
but that makes it hard to chain in a statement.
I want to do something more succinct, like the following, passing the desired protocol as an argument to my function.
self.findResponder( UndoManager )?.undo(...)
In this example, say I have protocol UndoManager, defined as
protocol UndoManager {
func undo(...)
}
Also, my declaration of findResponder() currently looks like
public extension NSReponder {
public func findResponder<T>() -> T? {
...
}
}
If you want to do self.findResponder( UndoManager )?.undo(...) instead of (findResponder() as Undo?)?.undo(...), the method signature would be:
public func findResponder<T>(_: T.Type) -> T? {
// ...
}

How do I declare a function that takes *any* block/closure as parameter?

I want to pass any block around as a parameter, without wanting to know the precise block parameter/return type.
func myFunc(block:(xxx) -> yyy) -> AnyObject {
...
}
So xxx in my code should match any number of parameters (including none) of any type. And yyy could be anything from void to AnyObject to an NSObject.
You can make a generic function:
func myFunc<A,B>(block:A -> B) -> AnyObject {
...
}
Unfortunately, you can't do that in Swift. Function Types are defined by their parameters and return types and there is no general function type. Functions also don't conform to Any or AnyObject, so you also can't use those either.
In Swift it's still possible to use the Selector. Maybe you can achieve what you want using that. See sample below:
func myFunc(selector:Selector) -> AnyObject {
self.performSelector(selector)
}
func someSelector() -> String {
return "test"
}
var result: AnyObject = myFunc(Selector("someSelector"))