文章目录
  1. 1. 前言
  2. 2. 一.NSTimer
    1. 2.0.1. 1. NSTimer的基本用法
    2. 2.0.2. 2. 强引用-invalidate
    3. 2.0.3. 3. 引用桥接法来解决问题
      1. 2.0.3.1. 1. 写法一.XXTimer
      2. 2.0.3.2. 2. 写法二.BBTimer
      3. 2.0.3.3. 3. 写法三.NSTimer category
  • 3. 三.KVO
    1. 3.0.1. 1. KVO的基本用法
    2. 3.0.2. 2. 忘记释放的效果
    3. 3.0.3. 3. KVO的自释放
  • 4. Demo下载地址:
  • 5. 参考文档
  • 前言

    好久没写博客了,最近事情太多,不过终于要告一段落了,终于有了自己的一些时间。这个demo写了蛮久了,今天终于把文章写完了。

    一.NSTimer

    1. NSTimer的基本用法

    NSTimer有以下两个基本初始化方法,

    1
    2
    + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

    区别:

    NSTimer基本用法

    2. 强引用-invalidate

    NSTimerError

    我们来看测试代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     XXView *view = [[XXView alloc] initWithFrame:CGRectMake(50, 50, 100, 100)];
    view.tag = 998;
    [self.view addSubview:view];
    [view testStrongDeallocTimer];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    for(UIView *subview in self.view.subviews){
    if(subview.tag == 998){
    [subview removeFromSuperview];
    break;
    }
    }
    });
    -(void) testStrongDeallocTimer{
    self.strongTimer = [NSTimer scheduledTimerWithTimeInterval:1.f
    target:self
    selector:@selector(printTestStrongTimer)
    userInfo:nil
    repeats:YES];
    [self.strongTimer fire];
    }

    此时XXView持有了strongTimer, strongTimer又持有了XXView.当XXView被removeFromSuperview的时候, strongTimer还是会继续进行持有,所以此时NSTImer无法释放。我们来看看运行结果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    2017-03-22 15:30:57.095 XXTimer[3001:86086] strongTimer!
    2017-03-22 15:30:58.094 XXTimer[3001:86086] strongTimer!
    2017-03-22 15:30:59.093 XXTimer[3001:86086] strongTimer!
    2017-03-22 15:31:00.096 XXTimer[3001:86086] strongTimer!
    2017-03-22 15:31:01.095 XXTimer[3001:86086] strongTimer!
    2017-03-22 15:31:02.099 XXTimer[3001:86086] strongTimer!
    2017-03-22 15:31:03.097 XXTimer[3001:86086] strongTimer!
    2017-03-22 15:31:04.025 XXTimer[3001:86086] strongTimer!
    2017-03-22 15:31:05.025 XXTimer[3001:86086] strongTimer!

    此时要释放必须是结束的时候强行调用NSTimer的invalidate方法。否则是无法释放的。这里可能有人会问,weakSelf呢?weakSelf是保证当前持有对象被释放的时候,NSTimer的target会变成nil。这里NSTimer和XXView已经形成了循环引用,是无法释放的。

    3. 引用桥接法来解决问题

    这里提供了1种思路3种写法来解决这个问题。基本思路:

    桥接流程图

    如图所示,我们的思路是通过一个XXTimerMember成员变量去桥街引用关系,在target为空的时候去主动去调用invalidate。这里提供了@selector和block两种方法,大家可以参考。

    1. 写法一.XXTimer

    通过自己的XXTimer,但是只提供初始化方法,返回的还是NSTimer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    typedef void (^XXTimerBlock)(id _Nullable userInfo);

    @interface XXTimer : NSObject

    +(NSTimer* _Nonnull) scheduledTimerWithTimeInterval:(NSTimeInterval)ti
    target:(nullable id)aTarget
    selector:(nonnull SEL)aSelector
    userInfo:(nullable id)userInfo
    repeats:(BOOL)yesOrNo;

    +(NSTimer* _Nonnull) scheduledTimerWithTimeInterval:(NSTimeInterval)ti
    target:(nullable id)aTarget
    block:(nullable XXTimerBlock)aBlock
    userInfo:(nullable id)userInfo
    repeats:(BOOL)yesOrNo;


    @end

    我们的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    @interface XXTimerMember : NSObject
    @property (nonatomic,strong) NSTimer *timer;
    @property (nonatomic,weak) id target;
    @property (nonatomic,assign) SEL selector;


    @property (nonatomic,copy) XXTimerBlock timerBlock;
    @end

    @implementation XXTimerMember

    -(void)dealloc{
    }

    -(void) timerSelector:(NSTimer*) timer{
    if(self.target) {
    [self.target performSelector:self.selector withObject:timer.userInfo afterDelay:0.0f];
    }else {
    [self.timer invalidate];
    }
    }

    -(void) timerBlock:(NSTimer*) timer{
    if(self.timerBlock && self.target) {
    self.timerBlock(timer.userInfo);
    } else {
    [self.timer invalidate];
    }
    }
    @end

    @implementation XXTimer

    +(NSTimer* _Nonnull) scheduledTimerWithTimeInterval:(NSTimeInterval)ti
    target:(nullable id)aTarget
    selector:(nonnull SEL)aSelector
    userInfo:(nullable id)userInfo
    repeats:(BOOL)yesOrNo{
    XXTimerMember *timerMember = [[XXTimerMember alloc] init];
    timerMember.target = aTarget;
    timerMember.selector = aSelector;
    timerMember.timer = [NSTimer scheduledTimerWithTimeInterval:ti
    target:timerMember
    selector:@selector(timerSelector:)
    userInfo:userInfo
    repeats:yesOrNo];
    return timerMember.timer;
    }

    +(NSTimer* _Nonnull) scheduledTimerWithTimeInterval:(NSTimeInterval)ti
    target:(nullable id)aTarget
    block:(nullable XXTimerBlock)aBlock
    userInfo:(nullable id)userInfo
    repeats:(BOOL)yesOrNo{
    XXTimerMember *timerMember = [[XXTimerMember alloc] init];
    timerMember.timerBlock = aBlock;
    timerMember.target = aTarget;

    return [NSTimer scheduledTimerWithTimeInterval:ti
    target:timerMember
    selector:@selector(timerBlock:)
    userInfo:userInfo
    repeats:yesOrNo];
    }
    @end
    2. 写法二.BBTimer

    我们完全自定义一个BBTimer,将想要提供给外部的方法进行暴露

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    typedef void (^BBTimerBlock)(id _Nullable userInfo);

    @interface BBTimer : NSObject

    +(BBTimer* _Nonnull) xx_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
    block:(nullable BBTimerBlock)aBlock
    userInfo:(nullable id)userInfo
    repeats:(BOOL)yesOrNo;


    -(void) fire;

    @end

    我们的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    @interface BBTimerMember : NSObject
    @property (nonatomic,strong) NSTimer *timer;
    @property (nonatomic,copy) BBTimerBlock timerBlock;
    @end

    @implementation BBTimerMember

    -(void) xxTimerBlock:(NSTimer*) timer{
    if(self.timerBlock) {
    self.timerBlock(timer.userInfo);
    }
    }
    @end

    @interface BBTimer()
    @property(nonatomic,weak) BBTimerMember *memberXXTimer;
    @end

    @implementation BBTimer

    -(void)dealloc{
    if (_memberXXTimer) {
    [_memberXXTimer.timer invalidate];
    _memberXXTimer.timer = nil;
    }
    }

    +(BBTimer* _Nonnull) xx_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
    block:(nullable BBTimerBlock)aBlock
    userInfo:(nullable id)userInfo
    repeats:(BOOL)yesOrNo{
    BBTimerMember *timerMember = [[BBTimerMember alloc] init];
    timerMember.timerBlock = aBlock;

    timerMember.timer = [NSTimer scheduledTimerWithTimeInterval:ti
    target:timerMember
    selector:@selector(xxTimerBlock:)
    userInfo:userInfo
    repeats:yesOrNo];
    BBTimer *timer = [[BBTimer alloc] init];
    timer.memberXXTimer = timerMember;
    return timer;
    }

    -(void) fire{
    if(self.memberXXTimer.timer){
    [self.memberXXTimer.timer fire];
    }
    }

    @end
    3. 写法三.NSTimer category

    大家有兴趣可以参考KVO的demo自己去实现一个,因为和KVO极其类似,这里就不写了。

    三.KVO

    1. KVO的基本用法

    原理可参考iOS开发– KVO的实现原理与具体应用,简单来说,当观察某对象A时,KVO机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性keyPath的setter 方法。setter 方法随后负责通知观察对象属性的改变状况。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    -(void)dealloc{
    [self removeKVOModel];
    }
    #pragma mark- 增加观察者
    -(void) addKVOModel{
    [self addObserver:self forKeyPath:@"testValue" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];

    [self addObserver:self forKeyPath:@"bounds" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];

    }

    -(void) removeKVOModel{
    [self removeObserver:self forKeyPath:@"testValue"];

    [self removeObserver:self forKeyPath:@"bounds"];

    }

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{

    if ([keyPath isEqualToString:@"bounds"]) {
    NSValue *newValue = change[NSKeyValueChangeNewKey];
    NSValue *oldValue = change[NSKeyValueChangeOldKey];

    if(![newValue isEqual:[NSNull null]] && ![oldValue isEqual:[NSNull null]]){
    CGRect newFrame = [newValue CGRectValue];
    CGRect oldFrame = [oldValue CGRectValue];
    NSLog(@"newValue:%@,oldValue:%@",NSStringFromCGRect(newFrame),NSStringFromCGRect(oldFrame));
    }

    }else if([keyPath isEqualToString:@"testValue"]){
    NSInteger testIntValue = [change[NSKeyValueChangeNewKey] integerValue];;

    NSLog(@"testIntValue:%ld",testIntValue);
    }
    }

    -(void) addTestValue
    {
    self.testValue ++;
    }

    2. 忘记释放的效果

    XXKVOView

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #pragma mark- 增加观察者
    -(void) addKVOModel{
    [self addObserver:self forKeyPath:@"testValue" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];

    [self addObserver:self forKeyPath:@"bounds" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];

    }

    -(void) removeKVOModel{
    [self removeObserver:self forKeyPath:@"testValue"];

    //[self removeObserver:self forKeyPath:@"bounds"];

    }

    大家可以看一下, 按照如下代码去执行,此时我们remove掉XXKVOView之后,会导致以下结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    'NSInternalInconsistencyException', reason: 'An instance 0x7fa9b2d19d70 of class XXKVOView was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x61800003a6e0> (
    <NSKeyValueObservance 0x60800004d4d0: Observer: 0x7fa9b2d19d70, Key path: bounds, Options: <New: YES, Old: YES, Prior: NO> Context: 0x0, Property: 0x60800004d680>
    )'
    *** First throw call stack:
    (
    0 CoreFoundation 0x000000010bb2ad4b __exceptionPreprocess + 171
    1 libobjc.A.dylib 0x000000010b58c21e objc_exception_throw + 48
    2 CoreFoundation 0x000000010bb942b5 +[NSException raise:format:] + 197
    3 Foundation 0x000000010b0c0ba1 NSKVODeallocate + 293
    4 libobjc.A.dylib 0x000000010b5a11d1 _ZN12_GLOBAL__N_119AutoreleasePoolPage3popEPv + 715
    5 CoreFoundation 0x000000010ba77676 _CFAutoreleasePoolPop + 22
    6 UIKit 0x000000010bf48011 _prepareForCAFlush + 599
    7 UIKit 0x000000010bf47d01 _UIApplicationFlushRunLoopCATransactionIfTooLate + 52
    8 UIKit 0x000000010c75277c __handleEventQueue + 5672
    9 CoreFoundation 0x000000010bacf761 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    10 CoreFoundation 0x000000010bab498c __CFRunLoopDoSources0 + 556
    11 CoreFoundation 0x000000010bab3e76 __CFRunLoopRun + 918
    12 CoreFoundation 0x000000010bab3884 CFRunLoopRunSpecific + 420
    13 GraphicsServices 0x000000010f91ca6f GSEventRunModal + 161
    14 UIKit 0x000000010bf4ec68 UIApplicationMain + 159
    15 XXKVO 0x000000010afb237f main + 111
    16 libdyld.dylib 0x000000010e98c68d start + 1
    )
    libc++abi.dylib: terminating with uncaught exception of type NSException

    3. KVO的自释放

    XXCustomKVO

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @interface NSObject (XXCustomKVO)

    - (void)addCustomObserver:(nonnull NSObject *)observer
    forKeyPath:(nonnull NSString *)keyPath
    context:(nullable void *) context
    selector:(nonnull SEL)selector;

    - (void)addCustomObserver:(nonnull NSObject *)observer
    forKeyPath:(nonnull NSString *)keyPath
    context:(nullable void *) context
    block:(nonnull XXCustomKVOBlock) kvoBlock;


    - (void)addCustomObserver:(nonnull NSObject *)observer
    forKeyPath:(nonnull NSString *)keyPath
    block:(nonnull XXCustomSingleKVOBlock) kvoBlock;
    @end

    同样的,我这边是提供了selector和block两种模式给到外部。
    这里其实是借鉴的Fackbook的kvo代码,但是里面实现会复杂很多,并不会一个key就生成一个property,而是用一个nsarry统一管理。所有的地方都用一个统一的VC。个人来说不是很喜欢那种模式吧,感觉不是很好调试。观察者太多的话不是很方便。

    按照我们的思路实现代码后可以发现,XXCustomKVOView的操作如下:
    1.self.view add view
    2.changevalue
    3.remove掉

    1
    2
    3
    4
    5
    6
    7
    2017-03-22 17:44:51.378 XXKVO[4613:159204] oldValue:0,newValue:1
    2017-03-22 17:44:51.612 XXKVO[4613:159204] oldValue:1,newValue:2
    2017-03-22 17:44:51.804 XXKVO[4613:159204] oldValue:2,newValue:3
    2017-03-22 17:44:51.963 XXKVO[4613:159204] oldValue:3,newValue:4
    2017-03-22 17:44:52.121 XXKVO[4613:159204] oldValue:4,newValue:5
    2017-03-22 17:44:52.795 XXKVO[4613:159204] -[XXCustomKVOView dealloc]
    2017-03-22 17:44:52.796 XXKVO[4613:159204] -[XXCustomKVO dealloc]

    Demo下载地址:

    https://github.com/xcysuccess/XXAutoDealloc

    参考文档

    1.Weak Reference to NSTimer Target To Prevent Retain Cycle
    2.iOS 中的 NSTimer
    3.https://github.com/facebook/KVOController

    文章目录
    1. 1. 前言
    2. 2. 一.NSTimer
      1. 2.0.1. 1. NSTimer的基本用法
      2. 2.0.2. 2. 强引用-invalidate
      3. 2.0.3. 3. 引用桥接法来解决问题
        1. 2.0.3.1. 1. 写法一.XXTimer
        2. 2.0.3.2. 2. 写法二.BBTimer
        3. 2.0.3.3. 3. 写法三.NSTimer category
  • 3. 三.KVO
    1. 3.0.1. 1. KVO的基本用法
    2. 3.0.2. 2. 忘记释放的效果
    3. 3.0.3. 3. KVO的自释放
  • 4. Demo下载地址:
  • 5. 参考文档