Analysis (001) NSTimer

NSTimer

 

or a timer is periodically sends a message to a specified at a certain time from the target object.

Timer is a thread notifies specific pattern their own way to do something, timers and your RunLoop related. If the timer mode where the current is not RunLoop monitoring, the timer will not begin to run until the next RunLoop in the appropriate mode. If RunLoop not running, then the timer will never start.

Life cycle

NSTimer target will be the outside passed retain. If it is a one-time call (repeats: NO), will invalidate itself after this call, and NSTimer retain that target will do a release.

However, if it is repeated calls, we need to own manually invalidate, or else NSTimer persist.

accuracy

Not real mechanism

Timer duplication of work will vary based on the scheduled time rather than real time scheduling its own operation. For example, if the timer is set to start at a specific time and repeat five seconds, the timer will start after that particular time of 5 seconds, even in that particular trigger time delayed. If the timer is delayed so that it missed one or more trigger time, the timer will trigger the next nearest event to start, while the back will be executed in accordance with the normal trigger interval 

For the UIScrollView Timer

When using the method NSTimer of scheduledTimerWithTimeInterval. In fact at this time it will be added to Timer Run Loop current thread, and the mode is the default NSDefaultRunLoopMode. And if the current thread is the main thread, which is the UI thread, some UI events, such as UIScrollView drag operation, will switch to NSEventTrackingRunLoopMode Run Loop mode, in the process, the default mode NSDefaultRunLoopMode event is not registered It will be executed. In other words, this time using scheduledTimerWithTimeInterval added to RunLoop the Timer will not be executed.

 

NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(timer_callback) userInfo:nil repeats:YES];     
//使用NSRunLoopCommonModes模式,把timer加入到当前Run Loop中。  
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

 

 

NSTimer in learning, a number of problems finishing.

A push to B page by page, turn the page NSTimer in B, then pop back to the A page and found no execution dealloc. This time the timer is still running, causing memory leaks.

code show as below:

#import "LJLNSTimerViewController.h"
#import "DeviceHeader.h"
#import "LJLWeakProxy.h"

static int timerNum = 0;
@interface LJLNSTimerViewController ()

@property(nonatomic, strong) NSTimer * timer;
@property(nonatomic, strong) NSThread *thread;
@property(nonatomic, assign) BOOL stopTimer;

@end

@implementation LJLNSTimerViewController

- (void)viewDidLoad {

    [super viewDidLoad];

//一、这样写pop的时候不会调用dealloc,造成内存泄露
//    因为LJLNSTimerViewController 与 timer 互相强引用
    self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerRun) userInfo:nil repeats:YES];

//    NSDefaultRunLoopMode 默认运行循环模式
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
}

-(void)timerRun{
    timerNum++;
    NSLog(@"%s 触发计时器%d",__func__,timerNum);
}

- (void)dealloc{
    NSLog(@"%s",__func__);
    [self.timer invalidate];
}

B page is LJLNSTimerViewController

  1. First of all you know, NSTimer is running in NSRunLoop.
  2. No call dealloc, B description page there is no release.
  3. Below, the solid line represents a strong reference. As the timer and TimerVC strong references to each other, it created a circular reference, it can not be released properly.

A try:

First I think certainly the NSTimer properties into a weak reference, into the strong weak.

@property(nonatomic, weak) NSTimer * timer;

But they found and there is no effect, because this time, although there is no circular references, but RunLoop reference to this timer, and the timer has strong references this TimerViewController, pop when pointing TimerViewcontroller pointer destroyed, but the timer pointing TimerViewcontroller pointer is not destroyed, so memory leaks still exist, can not be performed dealloc.

Try two

Since I can not pass directly self, then try to pass weakSelf

__weak typeof(self) weakSelf = self;  
self.timer = [NSTimer scheduledTimerWithTimeInterval:1  target:weakSelf selector:@selector(timerAction:) userInfo:nil repeats:true]; 

The test results happened circular reference, B is not released, timer of this variable is strongly weakSelf reference, timer -> weakSelf -> TimerViewcontroller -> timer, is formed between the three circular reference.

Try three

LJProxy analysis may be processed by adding an intermediate proxy object. Let TimerViewcontroller hold LJProxy example, let LJProxy examples to weak references TimerViewcontroller. timer strong reference LJProxy instance.

@interface LJProxy : NSObject
+ (instancetype) proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation LJProxy
+ (instancetype) proxyWithTarget:(id)target
{
    LJProxy *proxy = [[LJProxy alloc] init];
    proxy.target = target;
    return proxy;
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return self.target;
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    // 这里的target发生了变化
    self.timer = [NSTimer timerWithTimeInterval:1.0 target:[LJProxy proxyWithTarget:self] selector:@selector(timerRun) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
  1. When the first point to pop the No. 1 pointer is destroyed, and now there is no strong reference point TimerViewcontroller, TimerViewcontroller can be properly destroyed.
  2. TimerViewcontroller destruction will go dealloc method is called [self.timer invalidate] in dealloc years; RunLoop removed from the timer, No. 3 pointer Destruction
  3. When TimerViewcontroller, destroying its strength corresponding to the pointer will be destroyed by reference, then the pointer 2 will be destroyed.
  4. Executing the above, timer has not been strong reference other objects, timer will be destroyed, LJProxy will be destroyed

 

Try four

The investigation data NSProxy know there is a class, which is designed to make the message forwarded class.

@interface LJLWeakProxy : NSProxy
@property(nonatomic, weak)id target;
+(instancetype)proxyWithTarget:(id)target;
@end


@implementation LJLWeakProxy

+ (instancetype)proxyWithTarget:(id)target{

    LJLWeakProxy * proxy = [LJLWeakProxy alloc];
    proxy.target = target;
    return proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{

    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
    return [invocation invokeWithTarget:self.target];
}
@end

Here is Apple's documentation NSProxy, it simply provides several information

  • NSProxy is designed to make a message forwarding class
  • NSProxy is an abstract class, and is required to write a subclass inherits from NSProxy own
  • NSProxy subclasses need to implement two methods, that is, the following two
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
     return [self.target methodSignatureForSelector:sel];
} 

- (void)forwardInvocation:(NSInvocation *)invocation {
     [invocation invokeWithTarget:self.target];
}

 

Extended: OC message lookup mechanism     OC message forwarding mechanism

  1. Send a message:
    1. Start with the method cache to find ways, not how you look go to the list of methods of the current class, find a method of adding cache (the beginning of the cache size is 4-1, more than three-quarters empty on expansion, expansion into the original 2 4 * 2-1 times, then cache the current method, and then emptied before the time in the cache, and so are the two times has been the expansion).
    2. If no parent class instance method to repeat the preceding steps to find. Find by category Find method of isa chain. If you look in the end (or base class NSObject root metaclass) did not find that method, then you come into the next step 2
  2. Dynamic method resolution:
    1. To see if the class implements resolveInstanceMethod: resolveClassMethod :, and if it is implemented on analytical methods added dynamically, call the method, if not achieved proceed to step 3
  3. Message forwarding two steps
    1. Call forwardingTargetForSelector :, objects to see if the return is nil, if not nil calling objc_msgSend incoming object and SEL;
    2. If the return is nil, then it calls the method signature call methodSignatureForSelector :, return, if the method signature is not nil, call forwardInvocation: to perform the method.
  4. If the message is forwarded can not handle, it throws an exception.

Second attempt, inherited from NSObject object LJProxy. When implemented method does not find back through the above steps 1,2,3 to throw an error, if in the process we do remedial measures, then it will not be thrown doesNotRecognizeSelector :, the program can perform normal, but if inherited from the NSProxy LJLWeakProxy, will skip all the previous steps, the second small step directly to step 3, directly to the object execution method for improved performance. (NSProxy only methodSignatureForSelector: and forwardInvocation: These two methods, so only the first small step 3 step 2)

 

Method five

Borrowing:

NSTimer is known to be a strong reference parameter target: self is, if forget to turn off the timer, then pass in what will be a strong reference. timer function is a timing adjustment method, and call time NSTimer is inaccurate! It hung on by a thread switch runloop, the impact of the implementation time of an event.

Using dispatch_asyn () performs a timing function. Look at the following code.

- (void)loop {
    [self doSomething];
    ......
    // 休息 time 秒,再调 loop,实现定时调用
    [NSThread sleepForTimeInterval:time];
    dispatch_async(self.runQueue, ^{
        [weakSelf loop];
    });    
}

dispatch_async in tune loop does not produce a recursive call
dispatch_async is to add a task in the queue, the GCD to callback [weakSelf loop]

This solution can not release the timer, hang in question runloop can not be removed.

So that you can write yourself a timer. controller release, timer automatically stops the release of   GitHub Download
or open in viewWillAppear, closed viewWillDisappear. NSTimer strong reference to solve the problem of VC is not released. Many ways to find one.

 

Child thread using NSTimer

One,

//四、子线程添加NSTimer 如果直接pop会内存泄露,需要再pop之前处理停止NSTimer
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        __strong typeof(weakSelf) strongWeakSelf = weakSelf;
        if (strongWeakSelf) {
            strongWeakSelf.thread = [NSThread currentThread];
            [strongWeakSelf.thread setName:@"线程1"];
            strongWeakSelf.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:strongWeakSelf selector:@selector(timerRun) userInfo:nil repeats:YES];
            NSRunLoop * runloop = [NSRunLoop currentRunLoop];
            [runloop addTimer:strongWeakSelf.timer forMode:NSDefaultRunLoopMode];
            [runloop run];
        }
    });

- (void)cancelTimer{
    if (self.timer && self.thread) {
        [self performSelector:@selector(cancel) onThread:self.thread withObject:nil waitUntilDone:YES];
    }
}

- (void)cancel{
    if (self.timer) {
        [self.timer invalidate];
        self.timer = nil;
    }
}

Not directly to the dealloc process. Need to manually add pop event, and then pop in the implementation of destruction to go NSTimer.

two,

//五、子线程添加 NSTimer 解决内存泄露问题
    self.timer = [NSTimer timerWithTimeInterval:1.0 target:[LJLWeakProxy proxyWithTarget:self] selector:@selector(timerRun) userInfo:nil repeats:YES] ;

    __weak typeof(self) weakSelf = self;
    self.thread = [[NSThread alloc] initWithBlock:^{
        [[NSRunLoop currentRunLoop] addTimer:weakSelf.timer forMode:NSDefaultRunLoopMode];
        // 这里需要注意不要使用[[NSRunLoop currentRunLoop] run]
        while (weakSelf && !weakSelf.stopTimer) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
    }];
    [self.thread start];

- (void)dealloc
{
    NSLog(@"%s",__func__);
    [self stop];    
}

-(void)stop
{
    if (self.timer && self.thread) {
        [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
    }
}

-(void)stopThread
{
//    设置标记为YES
    self.stopTimer = YES;
//    停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
//    清空线程
    self.thread = nil;
}

Here do not use [[NSRunLoop currenRunLoop] run] to open the current thread RunLoop, but the use of runMode: beforeDate :.

Because the opening by the run method runloop no corresponding method of stopping, and so the while loop runMode: beforeDate: run RunLoop.

 

 

 

reference:

https://www.jianshu.com/p/d4589134358a

https://www.jianshu.com/p/bb691938fb2f

Published 83 original articles · won praise 12 · views 180 000 +

Guess you like

Origin blog.csdn.net/shengdaVolleyball/article/details/104656411
001