洋仔的博客 洋仔的博客
首页
  • 个人心法总结

    • 价值心法
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • iOS基础知识
  • 前端
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 投资体系
  • 毛选
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

洋仔

奋斗的小青年
首页
  • 个人心法总结

    • 价值心法
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • iOS基础知识
  • 前端
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 投资体系
  • 毛选
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术文档

  • GitHub技巧

  • Nodejs

  • 博客搭建

  • iOS基础知识

    • iOS底层相关

    • Runloop系列

    • Runtime系列

    • 内存管理系列

      • weak的实现原理
      • 内存管理系列
      • autoReleasePool
      • 循环引用
        • 内存管理自我总结
      • Block系列

      • 线程系列

      • KVC跟KVO系列以及通知中心

      • UI系列

      • 离屏渲染系列

      • 组件化系列跟架构

      • OC跟webview交互系列

      • 持久化系列

      • APP编译系列

      • APP性能优化系列

      • cocoapods系列

      • swift系列

      • Git系列

      • 网络相关

      • 三方库系列

      • 系统原理

      • 总结系列

      • 算法系列

      • 数据结构系列

    • 前端

    • 技术
    • iOS基础知识
    • 内存管理系列
    洋仔
    2024-09-05
    目录

    循环引用

    # 1.什么是循环引用问题?

    上篇文章说到循环引用的问题,其实引用计数这种管理内存 (opens new window)的方式虽然简单,但是有一个瑕疵,它不能很好的解决循环引用的问题。如图展示:

    这里写图片描述

    对象A和对象B,互相引用了对方作为自己的成员变量,只有当自己销毁的时候,才会将成员变量的引用计数减1。因为对象A的摧毁依赖于对象B的销毁,而对象B的销毁依赖与对象A的销毁,这样就造成了循环引用问题。即使在外界已经没有任何指针能访问它们了,它们这种互相依赖关系也无法被释放。

    不止两个对象可以产生循环引用问题,多个对象间依次持有,形成一个环路造成循环引用,这是最恶心的,因为在实际开发中,环大起来简直要命了,很难找出来。

    这里写图片描述

    # 2.解决循环引用

    解决办法有两个,第一个办法就是我明确知道这里会存在循环引用,在合理的位置主动的断开环中的一个引用,使得对象得以回收。但是这种方法并不是很好用,依赖于程序员对具体业务逻辑相当的熟悉。现在,更常见的是第二种方法:使用弱引用(weak reference)的办法。

    弱引用虽然持有对象,但是并不增加引用计数,这样就避免了循环引用的产生。在iOS开发中,弱引用通常在delegate模式中使用。这个之前的文章有说过的。传送门:iOS - Delegate代理为什么要用weak修饰(面试官钟爱)_ios delegate为什么weak-CSDN博客 (opens new window)

    # 二、循环引用类型(哪些场景会有循环引用问题)

    # 自循环引用

    假如有一个对象,内部强持有它的成员变量 obj,若此时我们 给 o bj 赋值为原对象时,就是自循环引用。

    # 相互循环引用

    对象 A 内部强 持有 obj , 对象 B 内部强持有 obj , 若 此时对 象 A 的 obj 指 向对 象 B ,同时对 象 B 中的 obj 指向对象 A,就是相互引用。

    # 多循环引用

    假如类中有对象 1...对象 N,每个对象中都强持有一 个 obj,若每个对 象的 obj 都指向下个对象,就产生了多循环引用。

    **总之,识别循环引用的核心就是:**两个或多个对象之间是否存在保留环。

    # 三、常见循环用及原因

    # 代理

    VC页面强持有UITableView,UITableView强持有Cell,在设置Cell的delegate为VC时,如果使用strong,这样就会形成循环引用。

    VC->UITableView->Cell->VC

    这种循环引用,比较简单,把Cell的delegate属性使用weak修饰,进行弱引用VC即可。

    @interface ViewController ()
    
    @property (nonatomic, copy) NSArray *array;
    @property (nonatomic, copy) void (^myBlock)(void);
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.myBlock = ^{
            NSLog(@"%@", self.array); // Capturing 'self' strongly in this block is likely to lead to a retain cycle
            NSLog(@"%@", _array); // Block implicitly retains 'self'; explicitly mention 'self' to indicate this is intended behavior
        };
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    1.因为self强引用了myblock,此时myblock在堆上,myblock捕获self并在其中强制持有,会导致保留环。

    出现以下情况时,ARC 会自动对 block 执行一次 copy 操作,将其从栈区移动到堆区:

    1. 当 block 作为函数返回值时。

    2. 当 block 被强指针引用时。

    3. 当 Cocoa API 方法名包含usingBlock,且 block 作为参数时,或 block 作为 GCD API 方法参数

    2.myblock中使用了self的实例变量 _array ,因此myblock会隐式的retain住self。(因为访问成员变量本质上是在调用self->instance,即仍然访问了self。)

    解决方案:

    在block外部使用__weak生成一个对self的弱引用weakSelf,block捕获weakSelf时,会连同他的修饰符进行捕获,在block对象内部产生一个指向真实内存区的__weak引用。从而解决了block对真实内存区的强引用问题

    __weak typeof(self) weakSelf = self;
    self.myBlock = ^{
       NSLog(@"%@", weakSelf.array);
       NSLog(@"%@", weakSelf->_array);
    };
    
    1
    2
    3
    4
    5

    那self和weakSelf一样么?

    NSLog(@"self === %p", &self);
    __weak typeof(self) weakSelf = self;
    self.myBlock = ^{
        NSLog(@"weakSelf === %p", &weakSelf);
        NSLog(@"%@", weakSelf.array);
    };
    self.myBlock();
    
    // 打印
    self === 0x16aff39b8
    weakSelf === 0x600003bac050
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    进入block外和block里面的weakSelf是不一样的。

    我们使用:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.0.0 ViewController.m 命令,将ViewController.m转成C++代码

    // @implementation ViewController
    
    struct __ViewController__viewDidLoad_block_impl_0 {
      struct __block_impl impl;
      struct __ViewController__viewDidLoad_block_desc_0* Desc;
      ViewController *const __weak weakSelf; // 对捕获的weakSelf进行弱引用。
      __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, ViewController *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    通过源码可以看出,block内部对捕获的weakSelf进行了弱引用,从而打破了block强引用self的这一层关系。

    为什么我们在项目中,block体里有时候又会使用strongSelf呢?

    __strong typeof(weakSelf) strongSelf = weakSelf;
    
    1

    假设有这样的场景:block里面有延迟操作self的持有的对象时,如下:

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.car = [[Car alloc] init];
        self.car.name = @"xiaomi su7";
    
        __weak typeof(self.car) weakCar = self.car;
        self.car.block = ^{
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"car.name2 === %@", weakCar.name);
            });
            NSLog(@"car.name1 === %@", weakCar.name);
        };
        self.car.block();
        self.car = nil;
    }
    
    // 打印结果
    car.name1 === xiaomi su7
    car.name2 === (null)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    可以看出self强持有car对象,在执行blcok之后,就把car置为nil。block里又有延迟函数,来读取self持有的car信息。

    通过打印结果来看:这通常不是我们想要的结果,故,我们需要在block体内进行一个“强引用”来延迟car的释放

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.car = [[Car alloc] init];
        self.car.name = @"xiaomi su7";
    
        __weak typeof(self.car) weakCar = self.car;
        self.car.block = ^{
             __strong typeof(weakCar) strongCar = weakCar;
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"car.name2 === %@", strongCar.name);
            });
            NSLog(@"car.name1 === %@", weakCar.name);
        };
        self.car.block();
        self.car = nil;
    }
    
    // 打印结果
    car.name1 === xiaomi su7
    car.name2 === xiaomi su7
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    原理:

    这就是我们要引入strongSelf的原因:用来稍微延长真实内存区的生存期,确保block体内操作对象不被释放。

    strongSelf是个局部变量,它的生存期与方法体同在,也就是说你在方法体开始时,保证了strongSelf不为空,那在方法体结束时,它依旧能提供这样的保证。当方法体结束时,strongSelf局部变量的生存期到期,strongSelf被释放,strongSelf对真实内存区的引用也被释放。

    使用案例:

    例如:在SDWebimage的下载回调中,就是用了strongSelf来延迟block体操作对象的释放

    早期的AFN请求完成完成回调

    - (void)setCompletionBlock:(void (^)(void))block {
        [self.lock lock];
        if (!block) {
            [super setCompletionBlock:nil];
        } else {
            __weak __typeof(self)weakSelf = self;
            [super setCompletionBlock:^ {
                __strong __typeof(weakSelf)strongSelf = weakSelf;
    
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wgnu"
                dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
                dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
    #pragma clang diagnostic pop
    
                dispatch_group_async(group, queue, ^{
                    block();
                });
    
                dispatch_group_notify(group, url_request_operation_completion_queue(), ^{
                    [strongSelf setCompletionBlock:nil];
                });
            }];
        }
        [self.lock unlock];
    }
    
    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
    # NSTimer
    @property (nonatomic, strong) NSTimer *timer;
    
    // Creates a timer and schedules it on the current run loop in the default mode.
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(doSomething) userInfo:nil repeats:YES];
    
    1
    2
    3
    4
    苹果官方文档对NSTimer的target参数解释:
    target
    The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated.
    当timer fire的时候会对`target`进行强持有,直到Timer是 invalidated
    
    1
    2
    3
    4

    如上,我们在创建NSTimer实例时,就会在默认的runloop中持有了一个timer,time在fire时,就会持有它的target(即self)

    如果把对timer的引用改成弱引用可以不?

    答案:不行,因为当NSTimer被分配之后,会被当前线程的Runloop进行强引用,如果对象以及NSTimer是在主线程创建的,就会被主线程的Runloop持有这个NSTimer,所以即使我们通过弱引用来指向NSTimer,但是由于主线程中Runloop常驻内存通过对NSTimer的强引用,再通过NSTimer对对象的强引用,仍然对这个对象产生了强引用,此时即使VC页面退出,去掉VC对对象的引用,当前VC仍然有被Runloop的间接强引用持有,这个对象也不会被释放,此时就产生了内存泄漏。

    左侧是Runloop对NSTimer的强引用,在NSTimer和VC对象中间添加一个中间对象,然后由NSTimer对中间对象进行强引用,同时中间对象分别对NSTimer和VC对象进行弱引用,当当前VC退出之后,VC就释放了,变为nil,当下次定时器的回调事件回来的时候,可以在中间对象当中,判断当前中间对象所持有的弱引用VC对象是否被释放了,实际上就是判断中间对象当中所持有的weak变量是否为nil,如果是nil,然后调用[NSTimer invalid]以及将NSTimer置为nil,就打破了Runloop对NSTimer的强引用以及NSTimer对中间对象的强引用。

    这个解决方案是利用了:当一个对象被释放后,它的weak指针会自动置为nil

    实现参考:内存管理-循环引用 - 简书 (opens new window)

    解决方案2:

    自定义一个作为消息传递中间者的Proxy(继承自NSProxy的类),并让这个Proxy弱持有真正的target,再把这个Proxy设为timer的target。Proxy会把消息转发给真正的target,而又因为是弱持有的,所以不出现循环引用(保留环)的问题。

    @interface YYTextWeakProxy : NSProxy
    
    /// The proxy target.
    @property (nullable, nonatomic, weak, readonly) id target;
    
    /// Creates a new weak proxy for target.
    - (instancetype)initWithTarget:(id)target;
    
    /// Creates a new weak proxy for target.
    + (instancetype)proxyWithTarget:(id)target;
    
    @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)];
    }
    
    - (BOOL)respondsToSelector:(SEL)aSelector {
        return [_target respondsToSelector:aSelector];
    }
    
    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

    这是 Apple 官方文档给 NSProxy 的定义,NSProxy 和 NSObject 一样都是根类,它是一个抽象类,你可以通过继承它,并重写 -forwardInvocation: 和 -methodSignatureForSelector: 方法以实现消息转发到另一个实例。综上,NSProxy 的目的就是负责将消息转发到真正的 target 的代理类。

    动态方法解析

    对象在收到无法解读的消息后,首先将调用其所属类的下列类方法:

    • (BOOL)resolveInstanceMethod:(SEL)selector

    使用这种方法的前提是:相关方法的实现代码已经写好了,只等着运行的时候动态插入在类里面就可以了。

    备援接受者

    当前接受者还有第二次机会能处理未知的选择子,在这一步中,运行期的系统会问它,能不能把这条消息转给其他接受者来处理。

    • (id)forwardingTargetForSelector:(SEL)selector

    在一个对象内部,可能还有一些列其他对象,该对象可经由此方法将能够处理某选择子的相关内部对象返回。如果没有则返回nil。

    完整的消息转发机制

    如果forwardingTargetForSelector:方法没有处理,会来到methodSignatureForSelector:方法,该方法可以返回一个方法签名,返回后,程序会继续调用forwardInvocation:方法。如果forwardInvocation:方法也没处理,程序就抛出异常

    • (NSMethodSignature *)methodSignatureForSelector:(SEL)selector

    • (void)forwardInvocation:(NSInvocation *)invocation

    问题:

    当不能识别方法的时候,就会调用forwardingTargetForSelector这个方法,在这个方法中,我们可以将不能识别的传递给其它对象处理,由于这里对所有的不能处理方法的都传递给_target了,所以methodSignatureForSelector和forwardInvocation不可能被执行的,所以不用再重载了吧?

    其实还是需要重载methodSignatureForSelector和forwardInvocation的,为什么呢?因为_target是弱引用的,所以当_target可能释放了,当它被释放了的情况下,那么forwardingTargetForSelector就是返回nil了。然后methodSignatureForSelector和forwardInvocation没实现的话,就直接crash了!!! 这也是为什么这两个方法中随便写的!!!

    参考链接:

    NSTimer和实现弱引用的timer的方式_vc弱引用nstimer-CSDN博客 (opens new window)

    [iOS进阶]iOS消息机制-CSDN博客 (opens new window)

    使用案例:

    self.tipViewTimer = [NSTimer scheduledTimerWithTimeInterval:time
                                                         target:[YYTextWeakProxy proxyWithTarget:self]
                                                       selector:@selector(tipViewTimerAction)
                                                       userInfo:nil
                                                        repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.tipViewTimer forMode:NSRunLoopCommonModes];
    
    1
    2
    3
    4
    5
    6

    # 四、不会产生循环引用的场景

    # AFN

    我们使用[AFHTTPSessionManager manager]发起网络请求,在函数中,AFHTTPSessionManager * manager是一个局部变量,随着函数栈的调用结束,这个局部变量也就被回收了,故self并没有持

    // AFN GET请求
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    [manager GET:url parameters:params headers:header progress:^(NSProgress * _Nonnull downloadProgress) {
    
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    
    }];
    
    + (instancetype)manager {
        return [[[self class] alloc] initWithBaseURL:nil];
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    在GET的调用过程中,对success,failure并没有做什么操作。

    // GET请求源码1
    - (NSURLSessionDataTask *)GET:(NSString *)URLString
                       parameters:(nullable id)parameters
                          headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                         progress:(nullable void (^)(NSProgress * _Nonnull))downloadProgress
                          success:(nullable void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                          failure:(nullable void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
    {
        NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                            URLString:URLString
                                                           parameters:parameters
                                                              headers:headers
                                                       uploadProgress:nil
                                                     downloadProgress:downloadProgress
                                                              success:success
                                                              failure:failure];
    
        [dataTask resume];
        return dataTask;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    来到dataTaskWithHTTPMethod里,发现31-40行,发现dataTask也没有持有success,failure,只是当成一个普通的block,然后调用了这个block而已。

    // 源码2
    - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                           URLString:(NSString *)URLString
                                          parameters:(nullable id)parameters
                                             headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                                      uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                    downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                             success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                             failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure
    {
        NSError *serializationError = nil;
        NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
        for (NSString *headerField in headers.keyEnumerator) {
            [request setValue:headers[headerField] forHTTPHeaderField:headerField];
        }
        if (serializationError) {
            if (failure) {
                dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                    failure(nil, serializationError);
                });
            }
    
            return nil;
        }
    
        __block NSURLSessionDataTask *dataTask = nil;
        dataTask = [self dataTaskWithRequest:request
                              uploadProgress:uploadProgress
                            downloadProgress:downloadProgress
                           completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
            if (error) {
                if (failure) {
                    failure(dataTask, error);
                }
            } else {
                if (success) {
                    success(dataTask, responseObject);
                }
            }
        }];
    
        return dataTask;
    }
    
    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
    # Masonry

    由源码可以看出:Masonry中设置布局的方法中的block对象并没有被View所引用,而是直接在方法内部同步执行,执行完以后block将释放,其中捕捉的外部变量的引用计数也将还原到之前。

    /// 调用
    [self.containerView mas_makeConstraints:^(MASConstraintMaker *make) {
       make.bottom.mas_equalTo(0);
       make.top.mas_equalTo(TBCPadding(M_H_X002));
       make.left.mas_equalTo(TBCPadding(M_W_X004));
       make.right.mas_equalTo(-TBCPadding(M_W_X004));
    }];
    
    /// Masonry源码
    #import "View+MASAdditions.h"
    #import <objc/runtime.h>
    
    @implementation MAS_VIEW (MASAdditions)
    
    - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
        self.translatesAutoresizingMaskIntoConstraints = NO;
        MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
        block(constraintMaker);
        return [constraintMaker install];
    }
    
    - (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *))block {
        self.translatesAutoresizingMaskIntoConstraints = NO;
        MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
        constraintMaker.updateExisting = YES;
        block(constraintMaker);
        return [constraintMaker install];
    }
    
    - (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block {
        self.translatesAutoresizingMaskIntoConstraints = NO;
        MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
        constraintMaker.removeExisting = YES;
        block(constraintMaker);
        return [constraintMaker install];
    }
    
    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
    # 系统UIView类动画

    UIView动画block不会造成循环引用是因为这是类方法,不可能强引用一个类,所以不会造成循环引用。

    UIView中的block持有当前控制器,但是当前控制器中是没有持有UIView类的,没有形成循环。当动画结束时,UIView会结束持有这个block,如果没有别的对象持有block的话,block对象就会被释放掉,从而block会释放掉对self的持有,整个内存引用关系被解除。

    [UIView animateWithDuration:0.25 animations:^{
        [self.refreshHeaderView setPullToRefreshState:TBCPullToRefreshHeaderViewStateLoading];
        self.contentComponent.top = self.segmentComponent.height + kTBCHybridFrsRefreshHeaderViewHeight;
    }];
    
    1
    2
    3
    4
    # GCD相关的一些代码块

    没有对象持有dispatch_after,所以在dispatch_after里面使用self是不构成循环引用。

    
    
    1
    编辑 (opens new window)
    上次更新: 2024/10/23, 23:26:17
    autoReleasePool
    内存管理自我总结

    ← autoReleasePool 内存管理自我总结→

    最近更新
    01
    数组
    10-25
    02
    数组双指针系列之对撞指针
    10-25
    03
    数组双指针系列之快慢指针
    10-25
    更多文章>
    Theme by Vdoing | Copyright © 2019-2024 Evan Xu | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式