Swift Quick framework memory leak - swift

I'm using Quick to test my Swift code.
However, I think it doesn't release objects defined in describe scope:
class MyClass {
deinit {
print(self, #function)
}
}
final class MyClassSpec: QuickSpec {
override func spec() {
describe("") {
let foo = MyClass()
it("") {
print(foo)
expect(true).to(beTrue())
}
}
}
}
I don't see any output from print inside deinit, and a debug breakpoint inside the deinit does not get catched.
If I move foo inside it, the deinit is called.
Is this a bug in Quick, or is it normal for deinit not to be called in a test suite?

Apparently the code I wrote was not only retaining the object but was also an anti-pattern.
Even a plain old XCTestCase retains an object:
class MyClass {
deinit {
print(self, #function)
}
}
final class MyClassTest: XCTestCase {
let foo = MyClass()
func testMyClass() {
print(foo)
XCTAssert(true)
}
}
deinit is not called for foo.
This is due to a nature of XCTestCase—it never really gets deinited.
So one should always use setUp & tearDown to manage everything (or more accurately, objects with reference semantics).
I believe this directly translates to QuickSpec as well, so I should always use beforeEach & afterEach in order to manage the objects.
To "fix" the problem, I should test like:
final class MyClassSpec: QuickSpec {
override func spec() {
describe("") {
let foo: MyClass!
beforeEach { foo = MyClass() }
afterEach { foo = nil }
it("") {
print(foo)
expect(true).to(beTrue())
}
}
}
}

Related

Swift scope of ignored return value?

I have a function similar to the following
class Bar {
deinit {
print("Do some cleanup")
}
}
func foo() -> Bar {
return Bar()
}
The scope of Bar is clear when calling it like this:
func useFoo() {
let bar = foo()
runFunctionA()
// bar goes out of scope: "Do some cleanup" is printed
}
However, what happens when the return value is ignored, will it go immediately out of scope?
func useFoo() {
let _ = foo()
// "Do some cleanup" is printed here?
runFunctionA()
// "Do some cleanup" is printed here?
}
Also, does it make a difference if let _ = foo() is used or only _ = foo()?
In second case, there is no one taking the ownership of returned object and hence ARC releases the object immediately. So, you must see Do some cleanup, before runFunctionA() call.
Also, let _ = foo() is similar to _ = foo
Now, you must be thinking what crap I am writing when you must be looking at different results in your playground.
The thing is, playgrounds are not meant for checking memory related code. Refer this.
If you don't trust me, just check your code in an actual project. I sure did.
Bar:
class Bar {
deinit {
print("Do some cleanup")
}
class func foo() -> Bar {
return Bar()
}
}
ViewController:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
useFoo()
useFooAgain()
}
func useFoo() {
let bar = Bar.foo()
print("inside useFoo")
}
func useFooAgain() {
let _ = Bar.foo()
print("inside useFooAgain")
}
}
Output:
inside useFoo
Do some cleanup
Do some cleanup
inside useFooAgain

Testing Delegation in Playground giving 'nil'

I have the following code in Playground -I'm learning delegation-...
import UIKit
protocol FollowThisProtocol {
func passingTheValue(aValue: String)
}
class IPassTheValues{
var aDelegate: FollowThisProtocol!
func runThisFunc(){
aDelegate.passingTheValue(aValue: "I like this game")
}
}
class IReceiveTheValues: FollowThisProtocol{
var localString: String!
var instanceOfClass: IPassTheValues!
func runReceivefunc(){
instanceOfClass.aDelegate = self
}
func passingTheValue(aValue: String) {
localString = aValue
}
}
When I attempt to
print(IReceiveTheValues().localString)
it's giving me nil
It also gives me nil if I run the following lines before attempting to print(IReceiveTheValues().localString)...
IPassTheValues()
IReceiveTheValues()
could you please help me understand why the value is not being passed from the 1st class to the 2nd..?
Or if you can spot something in my code that is contradicting itself, could you please point it out..?
Appreciate your time and help.
You need to create the IPassTheValues object before assigning yourself as the delegate, and then call runThisFunc() on the instance:
func runReceivefunc(){
instanceOfClass = IPassTheValues()
instanceOfClass.aDelegate = self
instanceOfClass.runThisFunc()
}
Then test:
// Create the `IReceiveTheValues` object
let irtv = IReceiveTheValues()
// Run the method
irtv.runReceivefunc()
// Get the resulting string
print(irtv.localString)
I suggest 2 other changes. Make your delegate weak so that you don't get a retain cycle which makes it impossible to delete either object. In order to do that, you will need to add : class to your protocol declaration because only reference objects (instances of a class) can be weak.
Here's the modified code. Try it and see what happens when you delete weak.
protocol FollowThisProtocol: class {
func passingTheValue(aValue: String)
}
class IPassTheValues{
weak var aDelegate: FollowThisProtocol!
func runThisFunc(){
print("Calling delegate...")
aDelegate.passingTheValue(aValue: "I like this game")
}
deinit {
print("IPassTheValues deinitialized")
}
}
class IReceiveTheValues: FollowThisProtocol{
var localString: String!
var instanceOfClass: IPassTheValues!
func runReceivefunc(){
instanceOfClass = IPassTheValues()
instanceOfClass.aDelegate = self
instanceOfClass.runThisFunc()
}
func passingTheValue(aValue: String) {
print("Receiving value from helper object...")
localString = aValue
}
deinit {
print("IReceiveTheValues deinitialized")
}
}
func test() {
let irtv = IReceiveTheValues()
irtv.runReceivefunc()
print(irtv.localString)
}
test()

Deinit not called in special circumstance that involves NSHashTable [duplicate]

This question already has an answer here:
How to call deinit in Swift [duplicate]
(1 answer)
Closed 5 years ago.
I came across something that's peculiar and interesting and would love to get inputs from anyone. So to start off with lets take this definition of the class:
class TestClass:NSObject {
var s1 = NSHashTable<TestClass>(options: .weakMemory)
func doit(bla:TestClass) {
s1.add(bla)
bla.s1.add(self)
}
deinit {
print("Deinit")
}
}
Now lets consider the following:
var t1:TestClass? = TestClass()
var t2:TestClass? = TestClass()
If we did the following deinit gets called:
t1?.s1.add(t2!)
t2?.s1.add(t1!)
t1 = nil // This will result in deinit being called
Now lets do the same thing but by calling doit() method
t1?.doit(bla:t2!)
t1 = nil // Deinit doesn't get called for some reason
The question here is why isn't deinit being called in this situation? What is so different about this since it essentially has the same reference assignment as the first method?
I would love to get input from anyone on this.
As usual, the problem is that you are trying to test this in a playground. Don't. Playgrounds are the work of the devil.
Test in an actual app project and you will see that deinit is called.
Example (iOS, but the equivalent in macOS would do fine):
import UIKit
class TestClass:NSObject {
var s1 = NSHashTable<TestClass>(options: .weakMemory)
func doit(bla:TestClass) {
s1.add(bla)
bla.s1.add(self)
}
deinit {
print("Deinit")
}
}
class ViewController: UIViewController {
var t1:TestClass? = TestClass()
var t2:TestClass? = TestClass()
override func viewDidLoad() {
super.viewDidLoad()
t1?.doit(bla:t2!)
t1 = nil // --> "Deinit"
print(t2?.s1) // --> it's empty
}
}
deinit is not called because you've created reference cycle.
First you're creating sctrong reference from self to bla: s1.add(bla)
Second you create strong reference from bla to self: bla.s1.add(self)
And now they both have references to each other, so they won't deinit if you just nullify one of them.
I've modified your TestClass to remove reference cycle:
class TestClass:NSObject {
weak var s1 = NSHashTable<TestClass>(options: .weakMemory)
func doit(bla:TestClass) {
s1?.add(bla)
bla.s1?.add(self)
}
deinit {
print("Deinit")
}
}
Now your second call will trigger deinit properly.

Shared XCTest unit tests for different implementations of interface

I have two or more implementations of some interface (protocol):
protocol Interface {
func methodOne()
func methodTwo()
}
I want to test each implementation and I don't want to duplicate code. I have couple of options, but none of them satisfies me.
First one is to create test case for ImplementationA and subclass it to get test case for ImplementationB:
class ImplementationATests: XCTestCase {
var implToTest: Interface!
override func setUp() {
super.setUp()
implToTest = ImplementationA()
}
func testMethodOne() {
...
}
func testMethodTwo() {
...
}
}
class ImplementationBTests: ImplementationATests {
override func setUp() {
super.setUp()
implToTest = ImplementationB()
}
}
One of the drawbacks of this method is that I can't have tests which apply only for ImplementationA. (e.g. to test some helper method specific to that implementation)
Second option I came up with is creating shared subclass for test cases:
class InterfaceTests: XCTestCase {
var implToTest: Interface!
func testMethodOne() {
...
}
func testMethodTwo() {
...
}
}
But here those tests will be also executed, and they will fail, because no implementation is assigned to implToTest. Of course I can assign some implementation to it, but then I will end with two test cases for the same implementation. The best option would be to somehow disable InterfaceTests test case and run only its subclasses. Is it possible?
Third idea I got may seem tricky, but it would satisfy all my needs. Unfortunately it doesn't work.
I decided to create InterfaceTestable protocol:
protocol InterfaceTestable {
var implToTest: Interface! { get set }
}
and make extension to it with all shared tests:
extension InterfaceTestable {
func testMethodOne() {
...
}
func testMethodTwo() {
...
}
}
and then create test cases for each implementation:
class ImplementationATests: XCTestCase, InterfaceTestable {
var implToTest: Interface!
override func setUp() {
super.setUp()
implToTest = ImplementationA()
}
// some tests which only apply to ImplementationA
}
class ImplementationBTests: XCTestCase, InterfaceTestable {
var implToTest: Interface!
override func setUp() {
super.setUp()
implToTest = ImplementationB()
}
// some tests which only apply to ImplementationB
}
Those test cases compile but Xcode doesn't see tests declared in InterfaceTestable extension.
Is there any other way to have shared tests for different implementations?
I came across the same problem and solved it using your second option.
However, I found a way to prevent the test cases from the base class from running:
Override the defaultTestSuite() class method in your base class to return an empty XCTestSuite:
class InterfaceTests: XCTestCase {
var implToTest: Interface!
override class func defaultTestSuite() -> XCTestSuite {
return XCTestSuite(name: "InterfaceTests Excluded")
}
}
With this no tests from InterfaceTests are run. Unfortunately also no tests of ImplementationATests either. By overriding defaultTestSuite() in ImplementationATests this can be solved:
class ImplementationATests : XCTestCase {
override func setUp() {
super.setUp()
implToTest = ImplementationA()
}
override class func defaultTestSuite() -> XCTestSuite {
return XCTestSuite(forTestCaseClass: ImplementationATests.self)
}
}
Now the test suite of ImplementationATests will run all test from InterfaceTests, but no tests from InterfaceTests are run directly, without setting implToTest.
The way I've done this before is with a shared base class. Make implToTest nillable. In the base class, if an implementation is not provided, simply return out of the test in a guard clause.
It's a little annoying that the test run includes reports of the base class tests when it's not doing anything. But that's a small annoyance. The test subclasses will provide useful feedback.
Building on top of ithron's solution, if you carefully craft your defaultTestSuite, you can remove the need for each subclass to re-override it.
class InterfaceTests: XCTestCase {
override class var defaultTestSuite: XCTestSuite {
// When subclasses inherit this property, they'll fail this check, and hit the `else`.
// At which point, they'll inherit the full test suite that generated for them.
if self == AbstractTest.self {
return XCTestSuite(name: "Empty suite for AbstractSpec")
} else {
return super.defaultTestSuite
}
}
}
Of course, the same limitation applies: this won't hide the empty test suite from the Xcode test navigator.
Generalizing this into a AbstractTestCase class
I would go a step further an make a base class AbstractTestCase: XCTestCase to store this defaultTestSuite trick, from which all your other abstract classes can inherit.
For completeness, to make it truly abstract you'd also want to override all the XCTestCase initializers to make them error out if an attempt is made to instantiate your abstract classes. On Apple's platforms, there's 3 initializers to override:
-[XCTestCase init]
-[XCTestCase initWithSelector:]
-[XCTestCase initWithInvocation:]
Unfortunately, this can't be done from Swift because of that last initializer, which uses NSInvocation. NSInvocation isn't available with Swift (it isn't compatible with Swift's ARC). So you need to implement this in Objective C. Here's my stab at it:
AbstractTestCase.h
#import <XCTest/XCTest.h>
#interface AbstractTestCase : XCTestCase
#end
AbstractTestCase.m
#import "AbstractTestCase.h"
#implementation AbstractTestCase
+ (XCTestSuite *)defaultTestSuite {
if (self == [AbstractTestCase class]) {
return [[XCTestSuite alloc] initWithName: #"Empty suite for AbstractTestCase"];
} else {
return [super defaultTestSuite];
}
}
- (instancetype)init {
self = [super init];
NSAssert(![self isMemberOfClass:[AbstractTestCase class]], #"Do not instantiate this abstract class!");
return self;
}
- (instancetype)initWithSelector:(SEL)selector {
self = [super initWithSelector:selector];
NSAssert(![self isMemberOfClass:[AbstractTestCase class]], #"Do not instantiate this abstract class!");
return self;
}
- (instancetype)initWithInvocation:(NSInvocation *)invocation {
self = [super initWithInvocation:invocation];
NSAssert(![self isMemberOfClass:[AbstractTestCase class]], #"Do not instantiate this abstract class!");
return self;
}
#end
Usage
You can then just use this as the superclass of your abstract test, e.g.
class InterfaceTests: AbstractTestCase {
var implToTest: Interface!
func testSharedTest() {
}
}

swift class declared inside method can not reach method?

I am using XCTest and declaring classes inside the TestMethod as a means of Mocking a class. However, It seems that the class is unable to call a method outside of its declaration. how do I fix this?
class MyTestCase : XCTestCase {
func testExample() {
class Bar { // <-- the class declared inside a function
func Barkly() {
foo(); // <-- apparently this produces a compile error.
}
}
}
func foo() { //<-- this is the method I am trying to call
println("hello world")
}
}