Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

日常开发中有哪些可能会出现的循环引用?又该怎样解决这些问题 #24

Open
tbfungeek opened this issue Jan 5, 2020 · 0 comments

Comments

@tbfungeek
Copy link
Owner

tbfungeek commented Jan 5, 2020

  1. delegate
//ClassA:
@protocol ClssADelegate 
- (void)fuck;
@end
@interface ClassA : UIViewController
@property (nonatomic, strong) id  delegate;
@end
//ClassB:
@interface ClassB ()
@property (nonatomic, strong) ClassA *classA;
@end
@implementation ClassB
- (void)viewDidLoad {
    [super viewDidLoad]; 
    self.classA = [[ClassA alloc] init];  
    self.classA.delegate = self;
}
ClassB --> classA(ClassA) --> delegate --> self (ClassB)

解决方案将delegate声明为weak类型,这个是最常见的。

  1. block
@interface ClassA ()
@property (nonatomic, copy) dispatch_block_t block;
@property (nonatomic, assign) NSInteger tem;
@end
@implementation ClassA
- (void)viewDidLoad {
    [super viewDidLoad];
    self.block = ^{
        self.tem = 1;
    };  
}

解决方案,用weak指针指向self。然后在block中使用weak指针访问self对象。

@interface ClassA ()
@property (nonatomic, copy) dispatch_block_t block;
@property (nonatomic, assign) NSInteger tem;
@end
@implementation ClassA
- (void)viewDidLoad {
    [super viewDidLoad];
    __weak typeof(self) weakSelf = self
    self.block = ^{
        weakSelf.tem = 1;
    };  
}

方案2. 通过将对象在block中设置为nil,但是这种需要注意的是block一定要被执行。

使用Strong-Weak Dance 来避免执行block过程中self被释放。

__weak MyViewController *wself = self;  
self.completionHandler = ^(NSInteger result) {  
    [wself.property removeObserver: wself forKeyPath:@"pathName"];
};

假设 block 被放在子线程中执行,而且执行过程中 self 在主线程被释放了。由于 wself 是一个弱引用,因此会自动变为 nil。而在 KVO 中,这会导致崩溃。 如果在 block 内部不使用强引用,而是通过 if 判空貌似可以规避这个问题,但是在多线程的情况下有可能会在判断非空的时候self还没释放,但是在判断语句过后被置为空,同样也会崩溃。而Strong-Weak Dance的本质是在执行block的时候将self给hold在Block 局部变量中,所以在这个Block中会强持有self,保证在执行block的时候self不会被释放掉。但是它又回在Block结束的时候释放掉,所以不会导致循环引用。如果在 block 执行以前,self 就释放了,那么 block 的引用计数降为 0,所以自己就会被释放。这样它根本就不会被执行。

  1. NSTimmer
self.timmer = [NSTimer timerWithTimeInterval:3 target:self selector:@selector(tick) userInfo:nil repeats:YES];
[self.timmer fire];
- (void)dealloc {
    [self.timer invalidate];
    self.timer = nil;
}

运行上面代码你会发现,由于上面发生了死循环所以dealloc不会执行,导致定时器不会停止。从而导致内存泄漏。

截屏2020-01-0518 41 26

这里明显有循环引用的存在,Object 持有timmer,timmer持有Object,(Object 作为timmer的target)。
如果将timmer作为弱引用可以解决吗?
截屏2020-01-0518 41 42

答案是否定的,因为在定时器分派之后,当前线程所在的Runloop会持有timmer,进而间接持有Object。

这种情况有哪些方式可以解除循环引用?

如果是单次的定时器比较简单,只要在block中将定时器停止就可以了,下面是针对循环定时器而言的

  • 将timmer停止及释放方法暴露出去,通过外部适当时机释放timmer
- (void)stopTimmer {
    [self.timer invalidate];
    self.timer = nil;
}
  • 将target转移到timmer内部

这也是BlocksKit中所采用的一种方法。

@interface NSTimer (BlocksKitPrivate)

+ (void)bk_executeBlockFromTimer:(NSTimer *)aTimer;

@end

@implementation NSTimer (BlocksKit)

+ (id)bk_scheduledTimerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void (^)(NSTimer *timer))block repeats:(BOOL)inRepeats
{
	NSParameterAssert(block != nil);
	return [self scheduledTimerWithTimeInterval:inTimeInterval target:self selector:@selector(bk_executeBlockFromTimer:) userInfo:[block copy] repeats:inRepeats];
}

+ (id)bk_timerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void (^)(NSTimer *timer))block repeats:(BOOL)inRepeats
{
	NSParameterAssert(block != nil);
	return [self timerWithTimeInterval:inTimeInterval target:self selector:@selector(bk_executeBlockFromTimer:) userInfo:[block copy] repeats:inRepeats];
}

+ (void)bk_executeBlockFromTimer:(NSTimer *)aTimer {
	void (^block)(NSTimer *) = [aTimer userInfo];
	if (block) block(aTimer);
}

这种就可以在Object的dealloc中调用如下方法释放timmer了:

- (void)dealloc {
    [self.timer invalidate];
    self.timer = nil;
}
  • 引入中间对象

截屏2020-01-0520 00 50

@interface IDLWeakTimmerObject : NSObject
@property(nonatomic, weak, readwrite) id target;
@property(nonatomic, weak, readwrite) NSTimer *timmer;
@property(nonatomic, assign, readwrite) SEL selector;
- (void)fire;
@end

@implementation IDLWeakTimmerObject
- (void)fire {
    if(self.target) {
        if([self.target respondsToSelector:self.selector]) {
            [self.target performSelector:self.selector];
        }
    } else {
        [self.timmer invalidate];
    }
}
@end

@implementation NSTimer (IDLAddion)
+ (NSTimer *)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)ti
                                         target:(id)aTarget
                                       selector:(SEL)aSelector
                                       userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo {
    IDLWeakTimmerObject *weakTimmerObject = [IDLWeakTimmerObject new];
    weakTimmerObject.target = aTarget;
    weakTimmerObject.selector = aSelector;
    weakTimmerObject.timmer = [NSTimer scheduledTimerWithTimeInterval:ti
                                                               target:weakTimmerObject
                                                             selector:@selector(fire)
                                                             userInfo:userInfo
                                                              repeats:YES];
    return weakTimmerObject.timmer;
}
@end

这种方式就是通过一个中间对象持有Object和NSTimer的弱引用,当Object被释放,并且下一个定时循环的时候,中间对象负责检测Object是否为空,如果为空则停止定时器,定时器一旦停止,RunLoop就会释放Timer。

YYText中也包含了一个YYTextWeakProxy类,它是使用YYTextWeakProxy作为代理,弱引用target,然后所有的方法都转发给target。 具体的代码这里也贴出来:

@implementation MyView {
        NSTimer *_timer;
}

- (void)initTimer {
        YYTextWeakProxy *proxy = [YYTextWeakProxy proxyWithTarget:self];
        _timer = [NSTimer timerWithTimeInterval:0.1 target:proxy selector:@selector(tick:) userInfo:nil repeats:YES];
}

- (void)tick:(NSTimer *)timer {...}
 @end
@implementation YYTextWeakProxy

- (instancetype)initWithTarget:(id)target {
    _target = target;
    return self;
}

+ (instancetype)proxyWithTarget:(id)target {
    return [[YYTextWeakProxy alloc] initWithTarget:target];
}

- (id)forwardingTargetForSelector:(SEL)selector {
    return _target;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    void *null = NULL;
    [invocation setReturnValue:&null];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
//........
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant