Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EXC_BAD_ACCESS using Generics in Swift

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.

like image 348
wander Avatar asked Oct 19 '22 21:10

wander


1 Answers

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
}
like image 66
sean woodward Avatar answered Oct 22 '22 12:10

sean woodward