0

I have tried to do this so many ways but the swift compiler complains whatever I do. SourceKit and the compiler also crashes non-stop, so I can't even experiment anymore. Not even to insert some printlns. I'm tearing my hair.

I'm trying to construct a simple array for table view content. The "rows" are Presentable objects, which is just a collection of protocols.

import Foundation

// The protocols are all @objc
typealias Presentable = protocol<Utterable, Displayable, Departure>
typealias TableSection = (sectionTitle: String, rows: [Presentable])

1. This doesn't work:

(buses, metros etc. are all [Bus]?, [Metro]? etc. and those classes conform to all the protocols that are Presentable)

private func asContent5() -> [TableSection]
{
    var result: Array<TableSection> = []

    var deptsCollections: [[Presentable]?] = [ buses, metros, trains, trams, ships ]
    for var i = 0; i<deptsCollections.count ; i++ {
        if let departures = deptsCollections[i]? {
            var newDeparturesArray: [Presentable] = []
            for dep in departures
            {
                newDeparturesArray.append(dep) // EXC_BAD_INSTRUCTION
            }
            let tuple: TableSection = (sectionTitle: "test", rows: newDeparturesArray)
            result.append(tuple)
        }
    }
    return result
}

Console output:

fatal error: NSArray element failed to match the Swift Array Element type

2. This "works" (i.e. doesn't crash at runtime) but I seem to get no objects in my new array:

private func asContent4() -> [TableSection]
{
    var result: Array<TableSection> = []

    var deptsCollections: [AnyObject?] = [ buses, metros, trains, trams, ships ]
    for var i = 0; i<deptsCollections.count ; i++ {
        if let departures: [Presentable] = deptsCollections[i] as? [Presentable] {
            var newDeparturesArray: [Presentable] = []
            for dep in departures
            {
                newDeparturesArray.append(dep as Presentable)
            }
            let tuple: TableSection = (sectionTitle: "test", rows: newDeparturesArray)
            result.append(tuple)
        }
    }
    return result
}

3. This works fully:

private func asContent3() -> [TableSection]
{
    var result: Array<TableSection> = []

    if let departures = buses {
        var newDeparturesArray: [Presentable] = []
        for dep in departures { newDeparturesArray.append(dep as Presentable) }
        let tuple: TableSection = (sectionTitle: "bus", rows: newDeparturesArray)
        result.append(tuple)
    }

    if let departures = metros {
        var newDeparturesArray: [Presentable] = []
        for dep in departures { newDeparturesArray.append(dep as Presentable) }
        let tuple: TableSection = (sectionTitle: "metro", rows: newDeparturesArray)
        result.append(tuple)
    }

    if let departures = trains {
        var newDeparturesArray: [Presentable] = []
        for dep in departures { newDeparturesArray.append(dep as Presentable) }
        let tuple: TableSection = (sectionTitle: "trains", rows: newDeparturesArray)
        result.append(tuple)
    }

    if let departures = trams {
        var newDeparturesArray: [Presentable] = []
        for dep in departures { newDeparturesArray.append(dep as Presentable) }
        let tuple: TableSection = (sectionTitle: "trams", rows: newDeparturesArray)
        result.append(tuple)
    }

    if let departures = ships {
        var newDeparturesArray: [Presentable] = []
        for dep in departures { newDeparturesArray.append(dep as Presentable) }
        let tuple: TableSection = (sectionTitle: "ships", rows: newDeparturesArray)
        result.append(tuple)
    }

    return result
}

All I want is to take my buses, metros, trains, trams, ships and put them in a [Presentable] each, without a wall of code. I'm starting to believe it's impossible in Swift, because it feels like I've rewritten these loops in every conceivable way.

What am I missing? Why can't I seem to iterate successfully instead of repeating all this code?


Update

This is what happens with Davids code:

Same console output as above, but this time it crashes when trying to access the TableSection::rows (that has happened to me before as well). This makes it crash:

println("index path s: \(indexPath.section) r: \(indexPath.row)")
let section = tableContent[indexPath.section]
println("row count: \(section.rows.count)")
let departure: Presentable = section.rows[indexPath.row] // crash

Console (I printed the rows array from the Variables View):

index path s: 0 r: 0
row count: 8
fatal error: NSArray element failed to match the Swift Array Element type
Printing description of section.rows:
([protocol<Departure, Displayable, Utterable>]) rows = {}

Is it just me or don't these numbers add up?

Andreas
  • 2,665
  • 2
  • 29
  • 38
  • The cause might be explained here: http://stackoverflow.com/questions/24113354/array-element-cannot-be-bridged-to-objective-c Your array-bridging in `var deptsCollections: [[Presentable]?] = [ buses ]` won't work – qwerty_so Jan 26 '15 at 14:44
  • Could be. I have been able to assign `var myPresentables: [Presentable]? = buses` though. I know it's a dubious assignment because the array is of type [Bus] and not [Presentable], but I am unsure about if that matters. It breaks type safety, but it compiled and it worked as long as it was an [@objc protocol]... – Andreas Jan 26 '15 at 15:15

1 Answers1

1

After generating a bunch of code that's missing here, I come up with the following that seems to work as you expect:

import Foundation

@objc protocol Utterable {}
@objc protocol Displayable {}
@objc protocol Departure {}

typealias Presentable = protocol<Utterable, Displayable, Departure>
typealias TableSection = (sectionTitle: String, rows: [Presentable])

class Bus : Presentable {}
class Metro : Presentable {}
class Train : Presentable {}
class Tram : Presentable {}
class Ship : Presentable {}

let buses : [Bus]? = nil
let metros : [Metro]? = [ Metro() ]
let trains : [Train]? = [ Train() ]
let trams : [Tram]? = nil
let ships : [Ship]? = [Ship()]

let departments : [[Presentable]?] = [ buses, metros, trains, trams, ships]

// filter out the non-nil departments that actually have elements
let acceptable = departments.filter { $0?.count > 0 }

// map the acceptable departments into sections, note that we force-unwrap
//  dept because we already verified in the step above that it must be
//  valid
let sections : [TableSection] = acceptable.map { (sectionTitle:"test", rows: $0!) }

Note that this uses a couple of very important builtin functions filter and map I'd suggest really digging into them as they, plus reduce are incredibly powerful built-ins that almost eliminate the need to ever manually do your own array iteration.

Or, for compactness, you can use:

// or for compactness...
let sections2 : [TableSection] = departments.filter({ $0?.count > 0 })
                                            .map({ (sectionTitle:"test", rows: $0!) })
David Berry
  • 40,941
  • 12
  • 84
  • 95
  • I like the approach and I tried your code. It doesn't seem to work fully :/ Starting to suspect it's because of swift's broken extension system (see my other question: http://stackoverflow.com/q/28131121/220820 ) – Andreas Jan 26 '15 at 18:06
  • Sorry about the missing code. It was impractical to add it as there's a hierarchy with base classes, and at least one extension per protocol to keep things tidy. Sometimes I can avoid the runtime errors just by moving the adoption of a protocol from the extension to the class (i.e. `class A : B` instead of `extension A : B`. It's weird. – Andreas Jan 26 '15 at 18:19
  • Sounds like you need to add more information to your question since there's no mention of extension there :) – David Berry Jan 26 '15 at 20:05
  • Hmm... when I put the protocol compliance in an extension I get an crash "Method crash corrupted" That sounds like a genuine Apple bug to me, as it really looks like an internal error that should never be seen. I'd recommend moving the compliance from the extension into the class and submitting a bug to http://bugreporter.apple.com. – David Berry Jan 26 '15 at 20:08
  • I started making a minimal outline of the hierarchy but had to throw in the towel when playground crashed on every single letter I typed, and SourceKit crashed all the time in my real project, and the compiler exited with segmentation fault on every build ;) Maybe I can make another try tomorrow. The bug is reported – Andreas Jan 26 '15 at 20:27
  • 1
    Well, if you take what's above and split the compliance into separate extensions (that still do nothing) the problem you report in the other bug reproduces. If doing that fixes the problem you're reporting here, I'd close this one as a duplicate. – David Berry Jan 26 '15 at 20:31