5

Related question: Generic completion handler in Swift

In a Swift app I'm writing, I'm downloading JSON and I want to convert it into model objects. Right now, I'm doing that like this:

func convertJSONData<T: Entity>(jsonData: NSData?, jsonKey: JSONKey, _: T.Type) -> [T]? {
        var entities = [T]()
        if let data = jsonData {

            // Left out error checking for brevity

            var json = JSON(data: data, options: nil, error: nil)
            var entitiesJSON = json[jsonKey.rawValue]

            for (index: String, subJson: JSON) in entitiesJSON {

                // Error: EXC_BAD_ACCESS(code=EXC_I386_GPFLT)

                let entity = T(json: subJson)
                entities.append(entity)
            }
        }
        return entities
    }

Each object conforming to Entity implements init(json: JSON). JSON is a type defined in the SwiftyJSON library. That's also the reason the enumeration looks a bit weird.

I call convertJSONData() in this method:

public func performJSONRequest<T where T: Entity>(jsonRequest: JSONRequest<T>) {
        var urlString = ...
        Alamofire.request(.GET, urlString, parameters: nil, encoding: .JSON).response { (request, response, data, error) -> Void in
                var books = self.convertJSONData(data as? NSData, jsonKey: jsonRequest.jsonKey, T.self)
                jsonRequest.completionHandler(books, error)
        }
    }

I get a runtime EXC_BAD_ACCESS(code=EXC_I386_GPFLT) error calling T(json: subJSON). There are no compiler warnings or errors. Although I left out error checking in the above code, there is error checking in the actual code and error is nil.

I'm not sure whether this is a compiler bug or my fault and any help figuring that out is much appreciated.

Community
  • 1
  • 1
wander
  • 711
  • 7
  • 23
  • Is the Entity class and the `init` public or local to the project? – sean woodward Dec 07 '14 at 14:55
  • It's actually a protocol, marked as `public`. Protocol methods can't be marked as `public`, but since `Entity` itself is, I don't see how that could cause the problem – wander Dec 07 '14 at 14:58
  • what type does the debugger report for `entitiesJSON`? – sean woodward Dec 07 '14 at 15:18
  • It's an instance of `JSON`, but inspecting it shows that it's actually an `__NSCFArray` – wander Dec 07 '14 at 15:43
  • have you tried casting it? – sean woodward Dec 07 '14 at 15:56
  • do you have control of the base classes implementing the Entity protocol? – sean woodward Dec 07 '14 at 16:00
  • Casting doesn't really make sense since `init(json: JSON)` takes a JSON parameter as you can see. It also doesn't work: `JSON is not convertible to NSArray`. I do have control over the model classes implementing `Entity` but what I'm trying to figure out is whether this is my fault or a compiler bug and whether I can fix it (preferably without changing everything by switching away from SwiftyJSON) – wander Dec 07 '14 at 16:30
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/66359/discussion-between-sean-woodward-and-wander). – sean woodward Dec 07 '14 at 17:16
  • Two new questions: 1. What is `entitiesJSON`'s type (`entitiesJSON.type == .Number, .String, ... .Unknown`)?; and 2. Is `entitiesJSON.error != nil`? – sean woodward Dec 11 '14 at 18:31
  • Of note, even if `jsonData` is nil, `convertJSONData` should always return a value, so the optional, `[T]?` is not required. Either change the return type to `[T]` or move the `var entities = [T]()` inside the `if let data = jsonData ...` block and return `.None` if `jsonData` is nil. – sean woodward Dec 11 '14 at 20:10
  • Also, in `performJSONRequest` the `Alamofire.request` response handler references `self.convertJSONData`, to what does the `self` refer? – sean woodward Dec 11 '14 at 20:52
  • And finally, I think the `init` method may be the culprit. How is it implemented in the classes implementing the `Entity` protocol? Are any of those classes sub-classes? – sean woodward Dec 11 '14 at 20:58
  • 1
    The GPFLT exception indicates some addressing error. See also here: http://stackoverflow.com/questions/19651788/whats-the-meaning-of-exception-code-exc-i386-gpflt – qwerty_so Dec 13 '14 at 22:02

1 Answers1

3

Several things are going on here, and I suspect the problem lies somewhere in the initializer of the class implementing the Entity protocol.

Assuming the code resembles the following:

protocol Entity {
    init(json: JSON)
}

class EntityBase: Entity {
    var name: String = ""
    required init(json: JSON) { // required keyword is vital for correct type inference
        if let nameFromJson = json["name"].string {
            self.name = nameFromJson
        }
    }

    func getName() -> String { return "Base with \(name)" }
}

class EntitySub: EntityBase {
    convenience required init(json: JSON) {
        self.init(json: json)  // the offending line
    }

    override func getName() -> String { return "Sub with \(name)" }
}  

The code compiles with self.init(json: json) in the sub-class, but actually trying to initialize the instance using the convenience method results in an EXC_BAD_ACCESS.

Either remove the initializer on the sub-class or simply implement required init and call super.

class EntitySub: EntityBase {
    required init(json: JSON) {
        super.init(json: json)
    }

    override func getName() -> String { return "Sub with \(name)" }
}  


The method to convert the jsonData to an Entity (modified slightly to specifically return .None when jsonData is nil):

func convertJSONData<T:Entity>(jsonData: NSData?, jsonKey: JSONKey, type _:T.Type) -> [T]? {
    if let jsonData = jsonData {
        var entities = [T]()

        let json = JSON(data: jsonData, options:nil, error:nil)
        let entitiesJSON = json[jsonKey.rawValue]

        for (index:String, subJson:JSON) in entitiesJSON {

            let entity:T = T(json: subJson)

            entities.append(entity)

        }

        return entities
    }

    return .None
}
sean woodward
  • 1,750
  • 1
  • 24
  • 36
  • I'm not subclassing, and I'm using `struct`s. I made a small gist showing the code I have. It's really that simple. https://gist.github.com/wandersiemers/cb3d486c7d370d35eeab – wander Dec 13 '14 at 00:20
  • in the `init(json: JSON)` before calling `init(id: Int, name: String)` pull the values into the properties and skip the call to `self.init`. Should give you the ability to identify an issue with the JSON data if there is one. – sean woodward Dec 13 '14 at 01:40
  • 1
    It crashes before even hitting the first line of the actual implementation of `init(json: JSON)`. I'd think there's something wrong with what the compiler thinks is the actual type that T represents but I can't figure out *why*. – wander Dec 13 '14 at 01:49
  • What version of Xcode and latest SwiftyJSON? I get both classes (base and sub-classes) and now structs. I am simulating Alamofire though. Can you reproduce the issue in a test without running the call through Alamofire? – sean woodward Dec 13 '14 at 01:58