diff --git a/DetoxSync/DetoxSync.xcodeproj/project.pbxproj b/DetoxSync/DetoxSync.xcodeproj/project.pbxproj index 2f9402d..4fa0b69 100644 --- a/DetoxSync/DetoxSync.xcodeproj/project.pbxproj +++ b/DetoxSync/DetoxSync.xcodeproj/project.pbxproj @@ -1224,7 +1224,6 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = NO; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1255,7 +1254,6 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = NO; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/DetoxSync/DetoxSync/Spies/NSTimer+DTXSpy.m b/DetoxSync/DetoxSync/Spies/NSTimer+DTXSpy.m index 8f5549c..019da0f 100644 --- a/DetoxSync/DetoxSync/Spies/NSTimer+DTXSpy.m +++ b/DetoxSync/DetoxSync/Spies/NSTimer+DTXSpy.m @@ -52,7 +52,7 @@ static void _DTXCFTimerTrampoline(CFRunLoopTimerRef timer, void *info) // NSLog(@"❤️ %p", timer); id tp = [DTXTimerSyncResource existingTimerProxyWithTimer:NS(timer)]; - [tp fire:(__bridge NSTimer*)timer]; + [tp fire]; } static CFRunLoopTimerRef (*__orig_CFRunLoopTimerCreate)(CFAllocatorRef allocator, CFAbsoluteTime fireDate, CFTimeInterval interval, CFOptionFlags flags, CFIndex order, CFRunLoopTimerCallBack callout, CFRunLoopTimerContext *context); @@ -72,6 +72,9 @@ CFRunLoopTimerRef __detox_sync_CFRunLoopTimerCreate(CFAllocatorRef allocator, CF static CFRunLoopTimerRef (*__orig_CFRunLoopTimerCreateWithHandler)(CFAllocatorRef allocator, CFAbsoluteTime fireDate, CFTimeInterval interval, CFOptionFlags flags, CFIndex order, void (^block) (CFRunLoopTimerRef timer)); CFRunLoopTimerRef __detox_sync_CFRunLoopTimerCreateWithHandler(CFAllocatorRef allocator, CFAbsoluteTime fireDate, CFTimeInterval interval, CFOptionFlags flags, CFIndex order, void (^block) (id timer)) { + NSLog(@"🤔 CFRunLoopTimerCreateWithHandler"); + + // What is this doing? We don't seem to be creating a trampoline here - I feel like we should be? return (__bridge_retained CFRunLoopTimerRef)[[NSTimer alloc] initWithFireDate:CFBridgingRelease(CFDateCreate(allocator, fireDate)) interval:interval repeats:interval > 0 block:block]; } @@ -94,9 +97,9 @@ void __detox_sync_CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, // NSLog(@"🤦‍♂️ removeTimer: %@", NS(timer)); id trampoline = [DTXTimerSyncResource existingTimerProxyWithTimer:NS(timer)]; - [trampoline untrack]; - __orig_CFRunLoopRemoveTimer(rl, timer, mode); + + [trampoline untrack]; } static void (*__orig_CFRunLoopTimerInvalidate)(CFRunLoopTimerRef timer); @@ -105,9 +108,9 @@ void __detox_sync_CFRunLoopTimerInvalidate(CFRunLoopTimerRef timer) // NSLog(@"🤦‍♂️ invalidate: %@", NS(timer)); id trampoline = [DTXTimerSyncResource existingTimerProxyWithTimer:NS(timer)]; - [trampoline untrack]; - __orig_CFRunLoopTimerInvalidate(timer); + + [trampoline untrack]; } static void (*__orig___NSCFTimer_invalidate)(NSTimer* timer); @@ -116,11 +119,20 @@ void __detox_sync___NSCFTimer_invalidate(NSTimer* timer) // NSLog(@"🤦‍♂️ invalidate: %@", timer); id trampoline = [DTXTimerSyncResource existingTimerProxyWithTimer:timer]; - [trampoline untrack]; - __orig___NSCFTimer_invalidate(timer); + + [trampoline untrack]; } +- (void)dealloc +{ + id trampoline = [DTXTimerSyncResource existingTimerProxyWithTimer:self]; + + if(trampoline) { + NSLog(@"🤦‍♂️ dealloc, but trampoline was still active: %@", self); + [trampoline untrack]; + } +} + (void)load { diff --git a/DetoxSync/DetoxSync/SyncResources/DTXTimerSyncResource.h b/DetoxSync/DetoxSync/SyncResources/DTXTimerSyncResource.h index a122819..cc794d1 100644 --- a/DetoxSync/DetoxSync/SyncResources/DTXTimerSyncResource.h +++ b/DetoxSync/DetoxSync/SyncResources/DTXTimerSyncResource.h @@ -22,7 +22,7 @@ NS_ASSUME_NONNULL_BEGIN //NSTimer @property (nonatomic, weak) NSTimer* timer; -- (void)fire:(NSTimer*)timer; +- (void)fire; @property (nonatomic) CFRunLoopRef runLoop; //CADisplayLink diff --git a/DetoxSync/DetoxSync/SyncResources/DTXTimerSyncResource.m b/DetoxSync/DetoxSync/SyncResources/DTXTimerSyncResource.m index d7ab95e..9347ffb 100644 --- a/DetoxSync/DetoxSync/SyncResources/DTXTimerSyncResource.m +++ b/DetoxSync/DetoxSync/SyncResources/DTXTimerSyncResource.m @@ -86,30 +86,16 @@ + (instancetype)sharedInstance shared = [DTXTimerSyncResource new]; [DTXSyncManager registerSyncResource:shared]; }); - - return shared; -} -/// Ugly hack for rare occasions where NSTimer gets released, but its associated objects are not released. -static NSUInteger _DTXCleanTimersAndReturnCount(NSMutableSet* _timers, NSMutableArray* eventIdentifiers) -{ - for (_DTXTimerTrampoline* trampoline in _timers.copy) { - if(trampoline.isDead) - { - [eventIdentifiers addObject:_DTXStringReturningBlock([NSString stringWithFormat:@"%p", trampoline])]; - [_timers removeObject:trampoline]; - } - } - - return _timers.count; + return shared; } - (void)clearTimerTrampolinesForCFRunLoop:(CFRunLoopRef)cfRunLoop { __block NSMutableArray* eventIdentifiers = [NSMutableArray new]; - + [self performMultipleUpdateBlock:^{ - return _DTXCleanTimersAndReturnCount(_timers, eventIdentifiers); + return _timers.count; } eventIdentifiers:_DTXObjectReturningBlock(eventIdentifiers) eventDescriptions:nil objectDescriptions:nil @@ -131,7 +117,7 @@ - (void)trackTimerTrampoline:(_DTXTimerTrampoline *)trampoline trampoline.jsonDescription])]; [_timers addObject:trampoline]; - return _DTXCleanTimersAndReturnCount(_timers, eventIdentifiers); + return _timers.count; } eventIdentifiers:_DTXObjectReturningBlock(eventIdentifiers) eventDescriptions:_DTXObjectReturningBlock(eventDescriptions) @@ -142,11 +128,11 @@ - (void)trackTimerTrampoline:(_DTXTimerTrampoline *)trampoline - (void)untrackTimerTrampoline:(_DTXTimerTrampoline *)trampoline { __block NSMutableArray* eventIdentifiers = [NSMutableArray new]; - + [self performMultipleUpdateBlock:^{ [eventIdentifiers addObject:_DTXStringReturningBlock([NSString stringWithFormat:@"%p", trampoline])]; [_timers removeObject:trampoline]; - return _DTXCleanTimersAndReturnCount(_timers, eventIdentifiers); + return _timers.count; } eventIdentifiers:_DTXObjectReturningBlock(eventIdentifiers) eventDescriptions:nil objectDescriptions:nil diff --git a/DetoxSync/DetoxSync/SyncResources/DTXUISyncResource.m b/DetoxSync/DetoxSync/SyncResources/DTXUISyncResource.m index b69d8d4..cc22f9e 100644 --- a/DetoxSync/DetoxSync/SyncResources/DTXUISyncResource.m +++ b/DetoxSync/DetoxSync/SyncResources/DTXUISyncResource.m @@ -113,45 +113,45 @@ - (void)_untrackForParam:(NSUInteger*)param eventIdentifier:(NSString*(NS_NOESCA - (void)trackViewNeedsLayout:(UIView *)view { NSString* identifier = [self _trackForParam:&_viewNeedsLayoutCount eventDescription:_DTXStringReturningBlock(@"View Layout") objectDescription:_DTXStringReturningBlock(view.__detox_sync_safeDescription)]; - + __detox_sync_orig_dispatch_async(dispatch_get_main_queue(), ^ { - [self _untrackForParam:&_viewNeedsLayoutCount eventIdentifier:_DTXStringReturningBlock(identifier)]; + [self _untrackForParam:&self->_viewNeedsLayoutCount eventIdentifier:_DTXStringReturningBlock(identifier)]; }); } - (void)trackViewNeedsDisplay:(UIView *)view { NSString* identifier = [self _trackForParam:&_viewNeedsDisplayCount eventDescription:_DTXStringReturningBlock(@"View Display") objectDescription:_DTXStringReturningBlock(view.__detox_sync_safeDescription)]; - + __detox_sync_orig_dispatch_async(dispatch_get_main_queue(), ^ { - [self _untrackForParam:&_viewNeedsDisplayCount eventIdentifier:_DTXStringReturningBlock(identifier)]; + [self _untrackForParam:&self->_viewNeedsDisplayCount eventIdentifier:_DTXStringReturningBlock(identifier)]; }); } - (void)trackLayerNeedsLayout:(CALayer *)layer { NSString* identifier = [self _trackForParam:&_layerNeedsLayoutCount eventDescription:_DTXStringReturningBlock(@"Layer Layout") objectDescription:_DTXStringReturningBlock(layer.description)]; - + __detox_sync_orig_dispatch_async(dispatch_get_main_queue(), ^ { - [self _untrackForParam:&_layerNeedsLayoutCount eventIdentifier:_DTXStringReturningBlock(identifier)]; + [self _untrackForParam:&self->_layerNeedsLayoutCount eventIdentifier:_DTXStringReturningBlock(identifier)]; }); } - (void)trackLayerNeedsDisplay:(CALayer *)layer { NSString* identifier = [self _trackForParam:&_layerNeedsDisplayCount eventDescription:_DTXStringReturningBlock(@"Layer Display") objectDescription:_DTXStringReturningBlock(layer.description)]; - + __detox_sync_orig_dispatch_async(dispatch_get_main_queue(), ^ { - [self _untrackForParam:&_layerNeedsDisplayCount eventIdentifier:_DTXStringReturningBlock(identifier)]; + [self _untrackForParam:&self->_layerNeedsDisplayCount eventIdentifier:_DTXStringReturningBlock(identifier)]; }); } - (void)trackLayerPendingAnimation:(CALayer*)layer { NSString* identifier = [self _trackForParam:&_layerPendingAnimationCount eventDescription:_DTXStringReturningBlock(@"Layer Pending Animation") objectDescription:_DTXStringReturningBlock(layer.description)]; - + __detox_sync_orig_dispatch_async(dispatch_get_main_queue(), ^ { - [self _untrackForParam:&_layerPendingAnimationCount eventIdentifier:_DTXStringReturningBlock(identifier)]; + [self _untrackForParam:&self->_layerPendingAnimationCount eventIdentifier:_DTXStringReturningBlock(identifier)]; }); } @@ -160,9 +160,9 @@ - (void)trackViewControllerWillAppear:(UIViewController *)vc if(vc.transitionCoordinator) { NSString* identifier = [self _trackForParam:&_viewControllerWillAppearCount eventDescription:_DTXStringReturningBlock(@"View Layout") objectDescription:_DTXStringReturningBlock(vc.description)]; - + [vc.transitionCoordinator animateAlongsideTransition:nil completion:^(id _Nonnull context) { - [self _untrackForParam:&_viewControllerWillAppearCount eventIdentifier:_DTXStringReturningBlock(identifier)]; + [self _untrackForParam:&self->_viewControllerWillAppearCount eventIdentifier:_DTXStringReturningBlock(identifier)]; }]; } } @@ -172,9 +172,9 @@ - (void)trackViewControllerWillDisappear:(UIViewController *)vc if(vc.transitionCoordinator) { NSString* identifier = [self _trackForParam:&_viewControllerWillDisappearCount eventDescription:_DTXStringReturningBlock(@"View Layout") objectDescription:_DTXStringReturningBlock(vc.description)]; - + [vc.transitionCoordinator animateAlongsideTransition:nil completion:^(id _Nonnull context) { - [self _untrackForParam:&_viewControllerWillDisappearCount eventIdentifier:_DTXStringReturningBlock(identifier)]; + [self _untrackForParam:&self->_viewControllerWillDisappearCount eventIdentifier:_DTXStringReturningBlock(identifier)]; }]; } } diff --git a/DetoxSync/DetoxSync/Utils/_DTXTimerTrampoline.m b/DetoxSync/DetoxSync/Utils/_DTXTimerTrampoline.m index 5235fdc..da70c69 100644 --- a/DetoxSync/DetoxSync/Utils/_DTXTimerTrampoline.m +++ b/DetoxSync/DetoxSync/Utils/_DTXTimerTrampoline.m @@ -16,19 +16,18 @@ @implementation _DTXTimerTrampoline { id _target; SEL _sel; - + //NSTimer __weak NSTimer* _timer; CFRunLoopTimerCallBack _callback; CFRunLoopRef _runLoop; - NSString* _timerDescription; NSTimeInterval _timeUntilFire; - + //CADisplayLink __weak CADisplayLink* _displayLink; - + BOOL _tracking; - + #if DEBUG NSString* _history; #endif @@ -53,7 +52,7 @@ - (instancetype)initWithTarget:(id)target selector:(SEL)selector fireDate:(NSDat _timeUntilFire = [fireDate timeIntervalSinceNow]; _ti = ti; _repeats = rep; - + #if DEBUG _history = [NSString stringWithFormat:@"%@\n%@", NSStringFromSelector(_cmd), NSThread.callStackSymbols]; #endif @@ -71,7 +70,7 @@ - (instancetype)initWithCallback:(CFRunLoopTimerCallBack)callback fireDate:(NSDa _timeUntilFire = [fireDate timeIntervalSinceNow]; _ti = ti; _repeats = rep; - + #if DEBUG _history = [NSString stringWithFormat:@"%@\n%@", NSStringFromSelector(_cmd), NSThread.callStackSymbols]; #endif @@ -86,17 +85,24 @@ - (BOOL)isDead - (void)dealloc { - [self untrack]; - - objc_setAssociatedObject(_timer, __DTXTimerTrampolineKey, nil, OBJC_ASSOCIATION_RETAIN); + NSLog(@"🤦‍♂️ trampoline dealloc: %@ (tracking: %d)", self, _tracking); + + if(_timer) + { + objc_setAssociatedObject(_timer, __DTXTimerTrampolineKey, nil, OBJC_ASSOCIATION_ASSIGN); + } + + if(_displayLink) + { + objc_setAssociatedObject(_displayLink, __DTXTimerTrampolineKey, nil, OBJC_ASSOCIATION_ASSIGN); + } } - (void)setTimer:(NSTimer*)timer { _timer = timer; - _timerDescription = [[timer debugDescription] copy]; objc_setAssociatedObject(timer, __DTXTimerTrampolineKey, self, OBJC_ASSOCIATION_RETAIN); - + #if DEBUG _history = [NSString stringWithFormat:@"%@\n%@", _history, [timer debugDescription]]; #endif @@ -105,54 +111,36 @@ - (void)setTimer:(NSTimer*)timer - (void)setDisplayLink:(CADisplayLink*)displayLink { _displayLink = displayLink; - objc_setAssociatedObject(_displayLink, __DTXTimerTrampolineKey, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(_displayLink, __DTXTimerTrampolineKey, self, OBJC_ASSOCIATION_RETAIN); #if DEBUG _history = [NSString stringWithFormat:@"%@\n%@", _history, [displayLink debugDescription]]; #endif } -- (void)fire:(id)timer +- (void)fire { - //This is to ensure the timer is still valid after fire. - CFRunLoopRef runloop = CFRunLoopGetCurrent(); - CFRunLoopMode mode = CFRunLoopCopyCurrentMode(runloop); - CFRunLoopPerformBlock(runloop, mode, ^{ - if(CFRunLoopTimerIsValid((__bridge CFRunLoopTimerRef)timer) == NO) - { - [self untrack]; - - CFRelease(mode); - - return; - } - - CFRunLoopPerformBlock(runloop, mode, ^{ - if(CFRunLoopTimerIsValid((__bridge CFRunLoopTimerRef)timer) == NO) - { - [self untrack]; - - CFRelease(mode); - - return; - } - - CFRelease(mode); - }); - }); - - if(_callback) + if(_timer && _callback) { CFRunLoopTimerContext ctx; - CFRunLoopTimerGetContext((__bridge CFRunLoopTimerRef)timer, &ctx); - _callback((__bridge CFRunLoopTimerRef)timer, ctx.info); - return; + CFRunLoopTimerGetContext((__bridge CFRunLoopTimerRef)_timer, &ctx); + _callback((__bridge CFRunLoopTimerRef)_timer, ctx.info); + } + + if(_timer && _target && _sel) { + IMP impl = [_target methodForSelector:_sel]; + + if(!impl) { + @throw @"☠️ attempted to call a selector but couldn't find the implementation!"; + } + + void (*func)(id, SEL, NSTimer*) = (void *)impl; + func(_target, _sel, _timer); + } + + if(!_repeats) { + [self untrack]; } - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - [_target performSelector:_sel withObject:timer]; -#pragma clang diagnostic pop } - (void)track @@ -161,7 +149,7 @@ - (void)track { return; } - + _tracking = YES; [DTXTimerSyncResource.sharedInstance trackTimerTrampoline:self]; } @@ -172,9 +160,9 @@ - (void)untrack { return; } - + // NSLog(@"🤦‍♂️ untrack: %@", _timer); - + [DTXTimerSyncResource.sharedInstance untrackTimerTrampoline:self]; _tracking = NO; } @@ -192,12 +180,12 @@ + (NSDateFormatter*)_descriptionDateFormatter } - (DTXBusyResource *)jsonDescription { - return @{ - @"fire_date": _fireDate ? [_DTXTimerTrampoline._descriptionDateFormatter stringFromDate:_fireDate] : @"none", - @"time_until_fire": @(_timeUntilFire), - @"is_recurring": @(_repeats), - @"repeat_interval": @(_ti) - }; + return @{ + @"fire_date": _fireDate ? [_DTXTimerTrampoline._descriptionDateFormatter stringFromDate:_fireDate] : @"none", + @"time_until_fire": @(_timeUntilFire), + @"is_recurring": @(_repeats), + @"repeat_interval": @(_ti) + }; } #if DEBUG