OC--Block总结

参考

Block编译代码解读:
block没那么难(一、二、三)
iOS进阶——iOS(Objective-C) 内存管理&Block

Block源码解读:
漫谈Block
苹果爹爹Block实现源码

支持weak的clang命令

clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations test.m

一、Block代码

(1) 没有返回值、没有参数的block

     void (^voidBlock)() = ^{};// 定义
     voidBlock();// 使用

(2) 没有返回值、有一个参数的block

    void (^parameterBlock)(NSInteger parameter) = ^(NSInteger parameter){
        NSLog(@"=====parameterBlock");
    };
    parameterBlock(1);

(3)没有返回值、有两个参数的block

    void (^parametersBlock)(int par1, int par2) = ^(int par1, int par2){
        NSLog(@"=====parameterBlock");
    };
    parametersBlock(2,3);

(4) 有返回值、有参数的block

    NSString *(^returnBlock)(NSInteger a) = ^(NSInteger a){
        return @"returnBlock";
    };
    NSLog(@"=====%@",returnBlock);

(5) block的typedef定义、使用

    typedef void(^TypedefBlock)(NSInteger type);
    TypedefBlock typedefBlock = ^(NSInteger type){
        NSLog(@"TypedefBlock");
    };
    typedefBlock(1);

二、Block的类型,什么是NSGlobalBlock、NSStackBlock、NSMallocBlock?

1、NSGlobalBlock

(1)没有捕获外部变量

    void(^blockA)(void) = ^() {
        NSLog(@"=====%@",@"asdasd");
    };
    NSLog(@"=====%@",blockA); // <__NSGlobalBlock__: 0x10debd190>

(2)只捕获全局变量,全局静态变量,局部静态变量。

int b = 20; // 全局变量
static int c = 30;// 全局静态变量
- (void)viewDidLoad {
    [super viewDidLoad];

    static int a = 10; // 局部静态变量
    void(^blockA)(void) = ^() {
        a++;
        b++;
        c++;
    };
    NSLog(@"=====%@",blockA); // <__NSGlobalBlock__: 0x10af19190>
}

另外:GlobalBlock的copy与retain还是GlobalBlock(后面有代码解析)

2、NSStackBlock:使用了外部变量的block,将 Block 赋值给附有 __weak 修饰符变量。栈区
    int a = 10;
    typedef void (^BBBlock)();
    __weak BBBlock aBlock = ^(){
         NSLog(@"a = %i",a);
    };
    NSLog(@"=====%@",^{NSLog(@"a = %i",a);}); // ====<__NSStackBlock__: 0x7fff50af3ce0>
    NSLog(@"=====%@",aBlock); // =====<__NSStackBlock__: 0x7fff50af3d08>
3、NSMallocBlock:有以下几种情况

(1)[stackBlock copy];stackBlock的copy方法;
(2)将stackBlock赋值给附有 __strong 修饰符的成员变量,(Block的retain其实就是copy,这就是为什么ARC的block属性使用copy、strong修饰都一样的原因)
(3)return stackBlock;stackBlock作为返回值;
(4)在方法名中含有 usingBlock 的 Cocoa 框架方法或 GCD 的 API 中传递stackBlock,方法里面先Block block = [stackBlock copy],然后使用block,(其实就是copy)

代码例子
(1)[stackBlock copy]
    int a = 10;
    typedef void (^BBBlock)();
    __weak BBBlock stackBlock = ^(){
        NSLog(@"a = %i",a);
    };
    
    NSLog(@"=====%@",[stackBlock copy]);// <__NSMallocBlock__: 0x60000045d8b0>
(2)stackBlock做返回值
//返回值是stackBlock的,变成NSMallocBlock
- (void(^)(void))getBlock {
    int a = 0;
    return ^{NSLog(@"=====%d",a);};
}
NSLog(@"%@",[self getBlock]);// <__NSMallocBlock__: 0x60000045d8b0>

其实也不能简单说做返回值,就是stackBlock,要看使用方法的环境。
严格的原理是:
(1)当block做为方法的返回值,方法执行时候,触发了autorelease的优化操作(objc_retainAutoreleasedReturnValue),导致是block被retain(copy)
(2)如果定义一个 __weak BBBlock aBlock = [self getBlock];这个aBlock是nil(因为触发autorelease的优化知道这个aBlock定义了__weak,被定义无意义的操作)

(3)将stackBlock赋值给附有 __strong 修饰符的成员变量时,(变量默认修饰符是__strong)
   int a = 10;
    typedef void (^BBBlock)();
    __weak BBBlock stackBlock = ^(){
        NSLog(@"a = %i",a);
    };
    
    BBBlock mBlock = stackBlock;
    NSLog(@"=====%@", mBlock);// <__NSMallocBlock__: 0x60000045d8b0>

三、block捕捉变量,有__block修饰、无__block修饰到底发生了什么?

无__block 修饰:

block捕获变量,相当于在block结构体中开一个同名的变量(如变量是对象,则也同strong、weak修饰)

有__block 修饰:

__block局部变量,是包装成一个__Block_byref_XXX_n结构体的对象,当这个__Block_byref对象被block使用且block_copy上堆时候,__Block_byref对象也会被复制到堆上(block源码:_Block_object_assign中执行_Block_byref_copy如下)。

block源码流程(libclosure-73)

block->flags 这个值主要用来告知系统Block在copy时应该执行什么操作,意思如下:

BLOCK_REFCOUNT_MASK = (0xfffe), // 一般参与判断引用计数,是一个可选用参数
BLOCK_NEEDS_FREE = (1 << 24), // NSConcreteMallocBlock
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // NSConcreteStackBlock
BLOCK_IS_GLOBAL = (1 << 28), // NSGlobalBlock

1、block的copy方法流程 (强引用也是copy)

id objc_retainBlock(id x) {
    return (id)_Block_copy(x);
}
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;
    
    if (!arg) return NULL;

    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // 堆上NSConcreteMallocBlock,增加block的引用计数
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        // 全局NSConcreteGlobalBlock,直接返回
        return aBlock;
    }
    else {
        // 栈上NSConcreteStackBlock才支持copy
        // 堆上new一个aBlock的大小--aBlock->descriptor->size
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        
        // 拷贝信息
        memmove(result, aBlock, aBlock->descriptor->size);
        
        // 把堆上的Block的flags改为(BLOCK_HAS_COPY_DISPOSE|BLOCK_NEEDS_FREE|2)
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);
        result->flags |= BLOCK_NEEDS_FREE | 2;
        
        _Block_call_copy_helper(result, aBlock); //--> 最终执行:_Block_object_assign(处理Block截获的对象,包括正常对象、__block修饰的对象)
        // _Block_call_copy_helper过程理解:编译代码中的__main_block_desc_0中的copy方法 >>> __main_block_copy_0 >>> _Block_object_assign
        
        // 把isa指向_NSConcreteMallocBlock
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}
_Block_object_assign (处理block捕获的对象)
void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
        case BLOCK_FIELD_IS_OBJECT:
            /*******
             id object = ...;
             [^{ object; } copy];
             ********/
            
            // 截获变量是对象:MRC是retain,ARC是空
            _Block_retain_object(object);
            *dest = object;
            break;
            
        case BLOCK_FIELD_IS_BLOCK:
            /*******
             void (^object)(void) = ...;
             [^{ object; } copy];
             ********/
            // 截获变量是block:Block_copy
            *dest = _Block_copy(object);
            break;
            
        case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
        case BLOCK_FIELD_IS_BYREF:
            /*******
             // copy the onstack __block container to the heap
             // Note this __weak is old GC-weak/MRC-unretained.
             // ARC-style __weak is handled by the copy helper directly.
             __block ... x;
             __weak __block ... x;
             [^{ x; } copy];
             ********/
            
            // 处理Block截获的__block修饰的变量(对象或者常量,_Block_byref结构体栈上复制到堆上,如果是对象要_Block_object_assign一下,进入下面的 case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT)
            *dest = _Block_byref_copy(object);
            break;

        case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
        case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
            /*******
             // copy the actual field held in the __block container
             // Note this is MRC unretained __block only.
             // ARC retained __block is handled by the copy helper directly.
             __block id object;
             __block void (^object)(void);
             [^{ object; } copy];
             ********/
            // 处理Block_byref结构体中截获的对象
            *dest = object;
            break;
            
        case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
        case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
            /*******
             // copy the actual field held in the __block container
             // Note this __weak is old GC-weak/MRC-unretained.
             // ARC-style __weak is handled by the copy helper directly.
             __weak __block id object;
             __weak __block void (^object)(void);
             [^{ object; } copy];
             ********/
            
            *dest = object;
            break;
            
        default:
            break;
    }
}

_Block_byref_copy (__block修饰的对象copy,栈上复制到堆上)

static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;
    
    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        
        // 该方法先在堆上生成同样大小的Block_byref赋值给堆上的Block,
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // 并把flags设置为src->flags | BLOCK_BYREF_NEEDS_FREE | 4
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        copy->forwarding = copy; // 堆上的forwarding指向自己
        src->forwarding = copy;  // 栈上的forwarding指向堆
        copy->size = src->size;
        
        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
            // __block修饰对象,这里的意思就是Block_byref要处理这个对象,
            // 执行__Block_byref结构体里面的__Block_byref_id_object_copy_xxx >>> _Block_object_assign,

            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;
            
            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }
            
            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // 这个Block_byref对象已经在堆上了,引用计数++
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}

__block NSObject *obj = [NSObject new];
Block里面使用obj的地方编译为:obj.__forwarding->obj

/******* block 修饰对象转换后的代码 *******/
/* struct for __block variable */
struct __Block_byref_obj_0 
{
    void *__isa;
    __Block_byref_obj_0 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*); 
    __strong id obj;
};
static void __Block_byref_id_object_copy_131(void *dst, void *src) 
{
    _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) 
{
    _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
/* __block variable declaration */
__Block_byref_obj_0 obj = { 0,
                            &obj,
                            0x2000000, 
                            sizeof(__Block_byref_obj_0), 
                            __Block_byref_id_object_copy_131, 
                            __Block_byref_id_object_dispose_131,
                            [[NSObject alloc] init]
                           };
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_obj_0 *obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
Block_byref对象:栈-->堆
首先,了解一下内存分配 iOS开发中的内存分配(堆和栈)

(1)栈(stack):由编译器自动分配释放,存放方法的参数值,局部变量等。栈是向低地址括展的数据结构,是连续的区域。栈顶的地址和栈的最大容量是系统预先规定好的。
优点:快速高效,
缺点:容量有限制
静态分配:静态分配是编译器完成的,比如局部变量的分配。
动态分配:动态分配由alloca函数进行分配,由编译器进行释放,无需我们手工实现。
(2)堆(heap):程序员分配释放,向高地址括展的数据结构,是不连续的区域。
(3)全局区(静态区)(static):全局变量和静态变量的存储,分初始化和未初始化bss(data、bss 两块区域),程序结束系统释放。
(4)常量区:字符串常量,const常量,程序结束系统释放。
(5)代码区:存放函数体的二进制代码。也就是说是它是可执行程序在内存种的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作——它是不可写的。

看代码

int age = 24;//全局初始化区(数据区)
NSString *name;//全局未初始化区(BSS区)
static NSString *sName = @"Dely";//指针sName的地址在全局(静态初始化)区,"Dely"本身在常量去,字符串常量

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    int tmpAge;//栈
    NSString *tmpName = @"Dely";  //指针tmpName的地址在栈区,"Dely"本身在常量去,字符串常量
    NSString *number = @"123456"; //指针number的地址在栈上,123456 在常量区,
    NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];//分配而来的8字节的区域就在堆中,指针array在栈中,指向堆区的地址
    NSInteger total = [self getTotalNumber:1 number2:1];

}

- (NSInteger)getTotalNumber:(NSInteger)number1 number2:(NSInteger)number2{
    return number1 + number2;//number1和number2 栈区
}
1、局部变量
(1)无_block修饰:

基本类型

    int a = 10;
    NSLog(@"block定义前a地址=%p", &a);
    void (^aBlock)() = ^(){
        NSLog(@"block定义内部a地址=%p", &a);
    };
    NSLog(@"block定义后a地址=%p", &a);
    aBlock();
    
    /*
     结果:
     block定义前a地址=0x7fff5bdcea8c
     block定义后a地址=0x7fff5bdcea8c
     block定义内部a地址=0x7fa87150b850
     */
    
    /*
     流程:
     1. block定义前:a在栈区
     2. block定义内部:里面的a是根据外面的a拷贝到堆中的,已经不是同一个a了,
     3. block定义后:a在栈区
     */

指针类型:(block捕获变量,相当于在block结构体中开一个同名的变量,指向局部变量的对象,被block持有,引用数+1)
(如果block里面重新赋值,也是block.a改变了,与block外面的变量a没有关系,赋值没有任何意义,所以苹果禁止赋值,xcode提示不允许。这就是为什么没有__block修饰的变量不能赋值原因。)

    NSObject *obj = [[NSObject alloc] init]; // 局部变量--指针变量,多提一下:“=”赋值右边的(alloc init)分配的地址是在堆上,左边obj指针地址是在栈上
    NSLog(@"block定义前:[obj本身的地址=%p,obj指向的地址=%p]",&obj,obj);
    void (^aBlock)() = ^(){
        NSLog(@"block定义内部:[obj本身的地址=%p,obj指向的地址=%p]",&obj,obj);
    };
    NSLog(@"block定义后:[obj本身的地址=%p,obj指向的地址=%p]",&obj,obj);
    aBlock();

    /*
     NSLog输出:
     block定义前:[obj本身的地址=0x7fff5bebed30,obj指向的地址=0x60000001f750]
     block定义后:[obj本身的地址=0x7fff5bebed30,obj指向的地址=0x60000001f750]
     block定义内部:[obj本身的地址=0x600000059910,obj指向的地址=0x60000001f750]
     */
    
    /*
     解析:
     1. block定义前:指针obj本身的地址在栈区,指针obj指向的对象是在堆上
     2. block定义内部:在block结构体中(block在堆上)开一个同名的变量(堆上),指向obj指向的对象,被block持有,引用数+1
     3. block定义后:obj没有变化。
     */
(2)有_block修饰

基本数据类型

    __block int b = 10;
    NSLog(@"block定义前b地址=%p", &b);
    void (^bBlock)() = ^(){
        b = 20;
        NSLog(@"block定义内b地址=%p", &b);
    };
    NSLog(@"block定义后b地址=%p", &b);
    bBlock();
    
    /*
     结果:
    block定义前b地址=0x7fff5c446c28
    block定义后b地址=0x60400042b318
    block定义内b地址=0x60400042b318
     */

指针类型:

    __block NSString *obj = [[NSString alloc] initWithFormat:@"111"]; // 局部变量--指针变量,多提一下:“=”赋值右边的(alloc init)分配的地址是在堆上,左边obj指针地址是在栈上
    NSLog(@"block定义前:[obj本身的地址=%p,obj指向的地址=%p]",&obj,obj);
    void (^aBlock)() = ^(){
        NSLog(@"block定义内:[obj本身的地址=%p,obj指向的地址=%p]",&obj,obj);
    };
    NSLog(@"block定义后:[obj本身的地址=%p,obj指向的地址=%p]",&obj,obj);
    aBlock();
    
    /*
     结果:
     block定义前:[obj本身的地址=0x7fff5d32fd38,obj指向的地址=0xa000000003131313]
     block定义后:[obj本身的地址=0x600000052298,obj指向的地址=0xa000000003131313]
     block定义内:[obj本身的地址=0x600000052298,obj指向的地址=0xa000000003131313]
     */

2、全局变量或者全局静态变量:因为都是在全局区,在程序结束前不会被销毁

block内部不需要处理全局变量或者全局静态变,直接使用

    //所以block直接访问了对应的变量(没有对变量处理)
    int global_var = 100;
    static int static_global_var = 200;
    void(^globalVarBlock)() = ^{
        
        NSInteger var = global_var; // 可以使用
        global_var = 1000; // 可以直接访问或者赋值
        static_global_var = 10000; // 可以直接访问或者赋值
        
    };
3、局部静态变量:将静态变量的指针传递给block,block通过静态局部变量的地址来进行访问

(__block(__Block_byref_XXX_n)结构体也是通过指针传递给block内部,所以MRC使用__block可以预防循环引用,且可以重新赋值修改__block修饰的变量)

{
    static NSInteger staticLocalVar = 10; 
    void(^staticLocalVarBlock)() = ^{
        
        NSInteger bvar = staticLocalVar;//可以使用
        staticLocalVar = 1000;//可以使用
        
    };
}

block的@property

ARC会自动帮strong类型且捕获外部变量的block进行copy,所以在定义block类型的属性时也可以使用strong,不一定使用copy

    // 假如有栈block赋给以下两个属性
    // ARC,当栈block中会捕获外部变量时, 这个block会被copy进堆中
    // 如果没有捕获外部变量,这个block会变为全局类型
    // 不管怎么样,它都脱离了栈生命周期的约束
    @property (nonatomic,strong) Block *strongBlock;
    @property (nonatomic,copy) Block *copyBlock;

四、block使用中出现retain cycle的问题、解决办法

最简单的循环问题代码

self.myBlock = ^{
        NSLog(@"=====%@",self);//这种明显的循环引用,xcode会提示。
    };

解决办法:(常用)

    __weak __typeof(self)weakSelf = self;
    self.myBlock = ^{
        __strong __typeof(weakSelf)strongSelf = weakSelf;;//这个作用是防止block执行过程中self释放导致block执行了一半就GG。
        NSLog(@"=====%@", strongSelf);
    };

解决办法:(不常用)
Block 多开一个参数,传入self,然后使用self的一些属性方法,这样子不会retain cycle

介绍做法:
为 block 多加一个参数,也就是 self 所属类型的参数,那么在 block 内部,该参数就会和 strongSelf 的效果一致。同时你也可以不写 weakSelf,直接使用使用该参数(作用等同于直接使用 strongSelf )。这样就达到了:“多加一个参数,省掉两行代码”的效果。原理就是利用了“参数”的特性:参数是存放在栈中的(或寄存器中),系统负责回收,开发者无需关心。因为解决问题的思路是:将 block 会捕获变量到堆上的问题,化解为了:变量会被分配到栈(或寄存器中)上,所以我把种做法起名叫 Heap-Stack Dance 。
引用来自--iOS程序猿 使用 Heap-Stack Dance 替代 Weak-Strong Dance,优雅避开循环引用。

#import "Foo.h"
typedef void (^Completion)(Foo *foo);
@interface Foo ()
@property (nonatomic, copy) Completion completion1;
@end

@implementation Foo
- (instancetype)init {
    if (!(self = [super init])) {
        return nil;
    }
    self.completion2 = ^(Foo *foo) {
        //使用foo的各种属性、方法
    };
    self.completion2(self);
    return self;
}
- (void)dealloc {
    NSLog(@"dealloc");//这能执行
}
@end

五、block的思考与实际使用的疑问,为什么有些系统SDK或者第三方库API中有block的使用中会引发循环引用,有些不会?

(1) GCD不会 (内部是copy这个block)
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    // something
    NSLog(@"=====%@",self.view);
});
(2) presentViewController不会 (这个block没有被任何对象持有,只是传进去执行)
UIViewController *vc = [[UIViewController alloc] init];
[self presentViewController:vc animated:YES completion:^{
    NSLog(@"=====%@",self.view);
}];
(3) AFNetWork
//self -> manager(AFHTTPSessionManager)->mutableTaskDelegatesKeyedByTaskIdentifier->delegate->block->self
//AFNetwork 在请求结束后会自动释放掉 delegate,打破那个环,所以循环引用也就不存在了.
manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.hao123.com"]];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
    NSLog(@"=====%@",self.view);
}];
[dataTask resume];
(4) SDWebImage
//slef -> view- -> imageView -> operationDictionary -> SDWebImageOperation -> OperationCompletedBlock  -> imageViewCompletedBlock -> self
//跟AFNetwork一样,等待operation请求完成自动remove,断开连接
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
[self.view addSubview:imageView];//self.view会retain imageView。
[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://img17.3lian.com/d/file/201702/18/3e8536054dd699a2134f86c200f7c079.jpg"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
    NSLog(@"=====%@",self.view);
}];

AFNetwork、SDWebImage都是要等请求结束才能断开retain cycle,self得不到快速释放。假设请求时间100年,这个self就100年得不到释放。只是假设,哈哈

(5) 其他方式出现的retain cycle,NSNotification、NSTimer、自己写的delegate都需要好好注意一下!!!!
[[NSNotificationCenter defaultCenter] addObserverForName:@"SecondViewController" object:nil queue:NULL usingBlock:^(NSNotification *note) {
}];
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {}];
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容