3

I have a service that I'm currently writing a unit test for. The code works as expected, but I'm getting a strange retain cycle warning.

[self.myService doSomethingCoolWithCompletionBlock:^(MyResponseObject *obj) {
    XCTAssertNil(obj, @"obj should be nil");
}];

The XCTAssertNil(obj, @"obj should be nil"); line shows a warning in Xcode Capturing 'self' strongly in this block is likely to lead to a retain cycle.

If I change the code to the following, the warning is removed:

__weak MyService *weakService = self.myService;
[weakService doSomethingCoolWithCompletionBlock:^(MyResponseObject *obj) {
    XCTAssertNil(obj, @"obj should be nil");
}];

I am using self.someService in other unit tests, and never had this issue. Anyone experienced this before?

EDIT

I have another test that has the following:

[self.myService doSomethingElseCoolWithCompletionBlock:(NSArray *results) {
    XCTestAssertNotNil(results, @"results should not be nil");
}];

This doesn't give me a warning. The only difference I see is that this is checking an array, and the other is checking an object of a specific type.

Taz
  • 1,203
  • 1
  • 11
  • 21

2 Answers2

7

assert it is macros and used self inside. so you need create local variable with name self.

__weak id weakSelf = self;
self.fooBlock = ^{
    id self = weakSelf;
    XCTAssert(YES);
};
ajjnix
  • 462
  • 3
  • 17
  • 1
    Thanks, this seems to have resolved the warning. Still doesn't explain why the warning isn't being shown for other NSAssert's in other tests. Can you elaborate further? – Taz Jan 27 '16 at 17:17
  • I try, but my English in this time don't allow me speak my full thought (sorry). In this case you use the block (myService retain block, self retain myService) and use assert inside. Assert it is a macros (#define) and this the macros will paste in code on compilation time. So your block have self inside. And will be relationship = self -> myService -> block -> self. May be other test haven't this relationship? – ajjnix Jan 27 '16 at 18:44
  • Thanks. That makes sense. I have added an example of another test that I have which does not throw a warning. Any ideas? – Taz Jan 28 '16 at 11:48
  • If you haven't warning it doesn't mean what your code is clear (( I don't know why you haven't warning (maybe it not need?), but you should know what all assert use variable self inside. But don't each self inside block mean retain cycle. For example this code haven't retain cycle because block is in stack memory. – ajjnix Jan 28 '16 at 14:11
  • Ok, thanks. What you are saying is making sense, and coincides with my understanding of retain cycles. I'm not entirely sure why only this test is throwing a warning yet the others are not, but since I have not got a better answer, I'll make this as accepted. – Taz Jan 29 '16 at 13:37
  • 1
    You can set habit use weak self use macros @weakify(self) outside block and @strongify(self) inside. It don't remove ability retain cycle but reduce it. – ajjnix Jan 29 '16 at 13:45
0

Don't do this:

@interface MyCoolTests : XCTestCase

@property (retain) id myService;

@end

@implementation MyCoolTests

-(void)testCoolness{
  self.myService = [MyService new];

  self.myService.callback = ^{
    XCTAssert(YES);
  };
  // ...
}

@end

Do this:

@interface MyCoolTests : XCTestCase


@end

@implementation MyCoolTests

-(void)testCoolness{
  id myService = [MyService new];

  myService.callback = ^{
    XCTAssert(YES);
  };
  // ...
}

@end

It's a limitation of XCTTestCase and it probably catches people when using the setup method.

Saltymule
  • 2,907
  • 2
  • 22
  • 30