Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift - generics issue cause memory leak

Tags:

swift

I tried to implement CQRS pattern in swift ios app and I found weird behavior.

Core classes:

class Query<T> {} 

class QueryHandler<TResult, TQuery:Query<TResult>> {

    func execute(query: TQuery, callback: (result: TResult) -> Void ) {
    }
}

Implementing classes:

class GetRandomStringsQuery: Query<[String]> {
internal var prefix:String

init(prefix: String) {
    self.prefix = prefix
    }
}

class GetRandomStringsQueryHandler: QueryHandler<[String], GetRandomStringsQuery> {

    override func execute(query: GetRandomStringsQuery, callback: (result: [String]) -> Void) {
        var result = [String]()

        for var i = 0; i < 100; i++ {
            result.append("[\(i)]: \(query.prefix)")
        }

        callback(result: result)
    }
}

Example usage:

@IBAction func generateMemoryLeak(sender: AnyObject) {
        let query = GetRandomStringsQuery(prefix: "leak leak leak :(")

        let queryHandler = GetRandomStringsQueryHandler()

        queryHandler.execute(query) { (result) -> Void in
            print("total records: \(result.count)")
        }
}

In callback we should get 100 elements in array. At runtime it seems like the reference is lost and value is unknown. iOS Developer Instruments detect memory leak.

Weid behvaior is that when we remove super class from GetRandomStringsQueryHandler and remove "override" modifier from execute function there will be no memory leak and app will work fine!

Could somebody explains this behavior? It's my mistake or swift issue?

I'm Using final verion of Xcode with swift 2.

like image 878
razor118 Avatar asked Sep 18 '15 11:09

razor118


1 Answers

Interesting question! I pasted your code into the playground and managed to reproduced the error you encountered. I tried to tweak the code but failed to get the generics to work. As an alternative, I end up rewriting the code using protocols to express the constraints.

Core protocols:

protocol QueryType {
    typealias ResultType
}

protocol QueryHandler {
    typealias Query: QueryType
    func execute(query: Self.Query, callback: (result: Self.Query.ResultType) -> Void)
}

Conforming classes:

class GetRandomStringsQuery: QueryType {
    typealias ResultType = [String]

    internal var prefix:String

    init(prefix: String) {
        self.prefix = prefix
    }
}


class GetRandomStringsQueryHandler: QueryHandler {

    func execute(query: GetRandomStringsQuery, callback: (result: [String]) -> Void) {
        var result = [String]()

        for var i = 0; i < 100; i++ {
            result.append("[\(i)]: \(query.prefix)")
        }

        callback(result: result)
    }

}

Calling code:

let query = GetRandomStringsQuery(prefix: "leak leak leak :(")

let queryHandler = GetRandomStringsQueryHandler()

queryHandler.execute(query) { (result) -> Void in
     print("total records: \(result.count)")
}
like image 58
Hong Wei Avatar answered Nov 15 '22 04:11

Hong Wei