Closed lan83 closed 6 years ago
Hey @lan83, could you please assist me in this bug reproduction? I've tried, but no luck.. I did this code:
- (NSArray *)dummyArray
{
return [@[@1, @2, @3, @4, @5, @6, @7] copy];
}
- (void)test_array_enumerator
{
XCTestExpectation *expectation = [self expectationWithDescription:@"Waiting for manual iteration"];
[[self dummyArray] typhoon_enumerateObjectsWithManualIteration:^(id object, id<TyphoonIterator> iterator) {
[(NSObject *)iterator performSelector:@selector(next) withObject:nil afterDelay:0.5];
} completion:^{
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:10 handler:^(NSError * _Nullable error) {
}];
}
But it passed with weak/strong self inside for iOS9, 10 and 11.
Hi @alexgarbarev thanks for your reply. The problem only occurs with arrays containing a single element.
I wrote a test that uses a similar setup as
- (void)createInvocationWithContext:(TyphoonInjectionContext *)context completion:(void(^)(NSInvocation *invocation))result
This test fails in iOS 11 while it passes on iOS 10:
@interface EnumTestTests : XCTestCase {
NSMutableArray *_dummyArray;
}
@end
@implementation EnumTestTests
- (void)setUp {
[super setUp];
_dummyArray = [NSMutableArray arrayWithObjects:@"foo", nil];
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
- (NSArray *)dummyArray
{
return [_dummyArray copy];
}
- (void)test_array_enumerator
{
XCTestExpectation *expectation = [self expectationWithDescription:@"Waiting for manual iteration"];
[[self dummyArray] typhoon_enumerateObjectsWithManualIteration:^(id object, id<TyphoonIterator> iterator) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[iterator next];
});
} completion:^{
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:1 handler:^(NSError * _Nullable error) {
}];
}
@end
The main difference is that objectEnumerator
returns a NSSingleObjectEnumerator
in iOS 11 while it returns a NSFastEnumerationEnumerator
in iOS 10.
iOS 11:
iOS 10:
Hey @lan83. Thanks for that detailed response. I guess the problem in different versions of iOS11. I have one of first beta releases (Version 9.0 beta (9M136h)), so I had NSFastEnumerationEnumerator
on both iOS 10 and iOS 11.
Anyway you did amazing work on catching this, thanks!
Proposed changes merged to master and release as 4.0.3
When calling
objectEnumerator
of an NSArray instance containing a single object in iOS 11, the returnedNSEnumerator
instance doesn't retain the array anymore. As a consequence when iterating over theinjectedParameters
array increateInvocationWithContext:completion:
the array of parameters gets deallocated to early and the invocation never gets invoked:TyphoonMethod+InstanceBuilder.m
NSArray+TyphoonManualEnumeration.m
To fix this issue, self should be captured strongly inside the nextBlock:
When having methods with multiple parameters, the enumerator retains the array and everything is working as expected.