1

Okay, I looked at this excellent answer, but I don't think it applies to my issue (but maybe it does, and I'm just being obtuse).

The issue is that I want to set up an Array of dispatcher objects, based on a protocol (not a base class), so the Array would be an Array of protocol, like so:

protocol ProtoOne { func someFunc() }
protocol ProtoTwo: ProtoOne { }

class ClassBasedOnOne: ProtoOne { func someFunc() { /* NOP */} }
class AnotherClassBasedOnOne: ProtoOne { func someFunc() { /* NOP */} }
class ClassBasedOnTwo: ProtoTwo { func someFunc() { /* NOP */} }
class AnotherClassBasedOnTwo: ProtoTwo { func someFunc() { /* NOP */} }

let arrayOfInstances: [ProtoOne] = [ClassBasedOnOne(), AnotherClassBasedOnOne(), ClassBasedOnTwo(), AnotherClassBasedOnTwo()]

Simple enough, eh?

But then, I want to filter for only certain instances, based on their protocol, not their class. With a function signature like this:

func getInstancesOfProtoTwo(from: [Any]) -> [ProtoTwo] { return [] }

or maybe a more generic type, like so:

func filterForInstances<T>(of: T.Type, from: [Any]) -> [T] { return [] }

I'm kind of at a loss as to how to do this. Is it even possible?

I have a nasty suspicion that it's actually incredibly simple, and I'm missing the forest for the trees.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Chris Marshall
  • 4,910
  • 8
  • 47
  • 72
  • 1
    I would be vary of this pattern, however. Checking for specific type conformances is an indicator of poor design. It's usually better to just ask the instance conforming to the protocol to do something (by calling a function), and have the instance decide for itself whether it should act or disregard the call. – Alexander Apr 22 '19 at 15:18
  • 1
    Possible duplicate of https://stackoverflow.com/questions/37857733/in-swift-how-can-i-filter-an-array-of-objects-conforming-to-a-protocol-by-their (just note that `flatMap` has to be replaced by `compactMap` in current Swift). – Martin R Apr 22 '19 at 15:19
  • Good point about filtering for type. Thanks! – Chris Marshall Apr 22 '19 at 15:21

1 Answers1

1

You just need to use compactMap along with the conditional coercion operation, as?.

compactMap

Returns an array containing the non-nil results of calling the given transformation with each element of this sequence.

protocol ProtoOne { func someFunc() }
protocol ProtoTwo: ProtoOne { }

class ClassBasedOnOne: ProtoOne { func someFunc() { /* NOP */} }
class AnotherClassBasedOnOne: ProtoOne { func someFunc() { /* NOP */} }
class ClassBasedOnTwo: ProtoTwo { func someFunc() { /* NOP */} }
class AnotherClassBasedOnTwo: ProtoTwo { func someFunc() { /* NOP */} }

let arrayOfInstances: [ProtoOne] = [
    ClassBasedOnOne(),
    AnotherClassBasedOnOne(),
    ClassBasedOnTwo(),
    AnotherClassBasedOnTwo()
]

let protoTwos = arrayOfInstances.compactMap { $0 as? ProtoTwo }

compactMap is really simple, you can see its implementation here

This is a simple line that I honestly wouldn't bother extracting to a function, but if you insist:

func filterForInstances<T>(of: T.Type, from instances: [Any]) -> [T] {
    return instances.compactMap { $0 as? T }
}

But it would be even better as an extension:

extension Sequence {
    func keepingOnlyInstances<T>(of: T.Type) -> [T] {
        return self.compactMap { $0 as? T }
    }
}
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • Damn that was fast. Cool. Yeah, I'd probably just use the compact map. Thanks! – Chris Marshall Apr 22 '19 at 15:16
  • 1
    And also, you should learn how the sausage it made: `compactMap` is really simple, you can see its implementation [here](https://github.com/apple/swift/blob/95a15d12bdc01ec392d01fdd6126f68cdd16aa42/stdlib/public/core/SequenceAlgorithms.swift#L765-L813) – Alexander Apr 22 '19 at 15:22
  • I really appreciate the answers. I'll probably still have to sort through the protocols, as I am using a set of "sibling" protocols to key specialized handlers (I search for instances of classes that implement one protocol, then cast those over to another protocol that I define. It's annoying, but I am writing an API test harness, and don't have control over the API protocols). – Chris Marshall Apr 22 '19 at 16:20
  • Without knowing much about the specifics, I see three approaches: 1) make your protocols extend the API ones, and add what you need, 2) make extensions on the API's protocols directly, or 3) as a last resort, extend each of the API's protocols with a function like "asMyProtocol" (idk a better name) which asks the conforming objects to cast themselves as they see fit – Alexander Apr 22 '19 at 16:44
  • It's a common pattern: switch cases (or other branching statements) can oftrn be better expressed as a method call, where one of several possible objects responds in the way that's appropriate to the context (that's that whole polymorphism thing that OO guys are always drooling over :p) – Alexander Apr 22 '19 at 16:46
  • That's a great idea! I would have no problem posting what I'm doing, but I think it would derail the thread. I get the whole "polymorphism" thing (this ain't my first rodeo), but I've learned to rely on it a whole lot less than I used to. The way Swift does protocols allows a fairly "safe" mixin process (no "diamond of death" -I came from C++). – Chris Marshall Apr 22 '19 at 16:54
  • Actually, I just figured out how I'll do it. The word "mixin" keyed it for me. – Chris Marshall Apr 22 '19 at 16:57
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/192229/discussion-between-alexander-and-rift-valley-software). – Alexander Apr 22 '19 at 17:05