8

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.

razor118
  • 473
  • 4
  • 16
  • How are you determining there is a memory leak? Are you running on a real device that is running out of memory and having a crash? Or only confirming with instruments? And when you confirm in instruments, are you using real device or simulator? And either way, what memory is being reported as leaking? – nhgrif Sep 18 '15 at 11:24
  • Right now when I run the app I get exception: EXC_BAD_ACCESS in libswiftCore.dylib`_swift_release_(swift::HeapObject*): In connection to device I'm using simulator. – razor118 Sep 18 '15 at 11:27
  • That's a bit different from a memory leak and should probably be added to your question. – nhgrif Sep 18 '15 at 11:28
  • There's probably something non-obvious going on related to this http://stackoverflow.com/a/27922252/2792531 – nhgrif Sep 18 '15 at 11:31
  • Yeah it maybe not be memory leak directly. Wierd thing is that in method generateMemoryLeak in result closure the "result" will be wrong. It should has 100 values and it doesn't. – razor118 Sep 18 '15 at 11:47
  • I think it's a mess with templates. The `override` there probably doesn't work correctly. However, this is obviously also a compiler bug - either it should generate an error or compile differently. – Sulthan Sep 18 '15 at 13:36
  • @razor118 try to rewrite you code in non generic form. I agree with Sulthan. – user3441734 Nov 15 '15 at 11:09
  • I've many times suffered from similar problems with a combination of virtual (with `override`) functions and generic classes.. – findall Nov 25 '15 at 05:03
  • @findall unfortunatelly even swift 2.1 has this problem and I can't do it in correct way... – razor118 Nov 25 '15 at 06:43

1 Answers1

2

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)")
}
Hong Wei
  • 1,397
  • 1
  • 11
  • 16
  • +1 for refactoring with protocols and linking associated query and handler types. only thing is I think you forgot to define your typealias on GetRandomStringsQueryHandler – Patrick Goley Nov 28 '15 at 07:22
  • I am guessing the compiler can infer that based on what I passed as an argument to the execute method. Otherwise it would have raise an issue with protocol conformance. – Hong Wei Nov 28 '15 at 07:36
  • Hi, thanks for creating protocol alternative for that problem. I hope that generic will be fixed asap. – razor118 Nov 30 '15 at 11:47