iOS的消息传递方式-3.BLOCK

屏幕快照 2017-05-24 09.34.13.png

Block结构

block的代码是内联的,效率高于函数调用
block对于外部变量默认是只读属性,即在Block函数体里面不能改变Block之外的变量,只可以读取。否则会报错。
block被Objective-C看成是对象处理,其实真实的存储情况是一个结构体的形式
struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};
1.这里主要是研究下block的调用顺序
typedef void(^myBlockTest)(int);//声明一个block块(block 的重命名)
-(void)testBlock:(NSString *)str andBlock:(myBlockTest)block
{

   3. NSLog(@"str===%@",str);
   4. //block(100);  //将100传给参数intParameter,回调block的实现函数

//block块的回调,当这行代码不注释时,
控制台输出:NonAtomicTest[2841:113472] str===我是字符窜
          NonAtomicTest[2841:113472] 实现block
          NonAtomicTest[2898:116086] 大于10

//当这行代码注释时
控制台输出:NonAtomicTest[2841:113472] str===我是字符窜

}
-(void)test1
{
 1.     NSString *str = @"我是字符窜";
 2.   [self testBlock:str andBlock:^(int intParameter) {

       // str = @"1111”;
//这行代码是错误的,因为这个大括号里面是block块的实现,它只可以对block块的参数intParameter赋值,不可以对testBlock:andBlock:这个函数的参数str赋值。

     5.   NSLog(@"实现block");
     6.   if (intParameter>10) {
            NSLog(@"大于10");
        }
        else
        {
            NSLog(@"不大于10");
        }
    }];
   
}
这个例子已经标出调用顺序,这里将block块可以看作是内联函数
补充:如何在block中修改外部变量
__block int a = 0;
void  (^blockTest)(void) = ^{
    a = 1;
}
blockTest();

2.block中__strong和__weak的使用

问:为什么使用weakSelf

答:通过 clang -rewrite-objc 源代码文件名 将代码转为c++代码(实质是c代码),可以看到block是一个结构体,它会将全局变量保存为一个属性(是_ _strong的),而self强引用了block这会造成循环引用。所以需要使用__weak修饰的weakSelf。self 持有block,block持有self。

问:在block中weak 和strong的使用场景是什么?

答:(摘自http://www.jianshu.com/p/36342264d6dfApple )官方的建议是,传进 Block 之前,把 ‘self’ 转换成 weak automatic 的变量,这样在 Block 中就不会出现对 self 的强引用。如果在 Block 执行完成之前,self 被释放了,weakSelf 也会变为 nil。

__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [weakSelf doSomething];
});

clang 的文档表示,在 doSomething 内,weakSelf 不会被释放。但,下面的情况除外:

__weak __typeof__(self) weakSelf = self;  
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    [weakSelf doSomething];
    [weakSelf doOtherThing];

});

在 doSomething 中,weakSelf 不会变成 nil,不过在 doSomething 执行完成,调用第二个方法 doOtherThing 的时候,weakSelf 有可能被释放,于是,strongSelf 就派上用场了:

__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    __strong typeof(self) strongSelf = strongSelf;

    [strongSelf doSomething];
    [strongSelf doOtherThing];

});

如果仅仅使用__weak去修饰变量,当别处把变量释放后,block中该变量也会被释放掉。

__strong 确保在 Block 内,strongSelf 不会被释放。当加上修饰符strong时,当别处(别的block 内)把变量释放掉,但调用该变量的block如果仍然没有执行结束,那么系统就会等待block执行完成后再释放,对该变量在block中的使用起到了保护作用。当block执行结束后会自动释放掉。

总结

arc
>当在block内需要访问self相关属性的时候,添加 __weak 防止循环引用。

>当block内的变量不仅仅在block中使用的时候,为了防止变量提前释放,添加 __strong。

>当block内需要引用外部变量的时需要添加 __block  ,因为局部变量出了范围就会释放,但是block调用的时机是不确定的。所以使用 __ block 将block复制到堆区,引用的变量也在堆区,以后的操作也都在这里进行。

>但是__block 本身无法避免循环引用的问题,所以我们可以通过在 block 内部手动把 blockObj 赋值为 nil 的方式来避免循环引用的问题。另外一点就是 __block 修饰的变量在 block 内外都是唯一的,要注意这个特性可能带来的隐患。

注:__block有一点:这只是限制在ARC环境下。在非arc下,__block是可以避免引用循环的。

block中各种情况的出现及解决办法

#define BLog(prefix,obj) {NSLog(@"位置和指针变量名:%@ ,指针内存地址:%p, 指针值:%p ,指向的对象:%@ ",prefix,&obj,obj,obj);} 

// 强引用
- (void)blockVariableStrongReferenceTest
{
    NSLog(@"\n");
    NSObject *obj = [[NSObject alloc] init];//+1
    BLog(@"StrongRef obj",obj);
    void(^testBlock)()= ^(){
        BLog(@"StrongRef in block",obj);//+1Block中obj指针已经不是外部的obj指针了,它是外部变量obj的拷贝,内存引用计数加一
    };
    testBlock();
    // Block外部尝试将obj置为nil
    obj = nil;//-1
    testBlock();  // 第二次调用block
}

解释:block内部的obj 指针是外部obj指针的拷贝,有2个指针指向同一个NSObject对象,(没有重新分配空间)但只将外部的obj指针置为nil,NSObject对象的引用计数不为0,无法回收。

// 弱引用
- (void)blockVariableWeakReferenceTest
{
    NSObject *obj = [[NSObject alloc] init];//+1
    __weak NSObject *weakObj = obj;//+0   weakObj和obj是2个不同的指针,指向同一块内存地址,但是因为是弱引用,所以引用计数不改变
    BLog(@"WeakRef weakObj", weakObj);
    void(^testBlock)()= ^(){
        BLog(@"weakObj in block",weakObj);
    };
    testBlock();
    obj = nil; //    -1=0
    testBlock();//
}

解释:在block中__weak声明的指针去引用对象 可以避免循环引用的问题,但是当外部对象被释放了,block 内部会访问不到这个对象.

//多线程时Block生命周期内对象安全
- (void)blockVariableMutiThreadTest
{
    NSObject *obj = [[NSObject alloc]init]; //obj强引用,<NSObject: 0x7f9413c1c040>对象引用计数+1,=1
    BLog(@"obj", obj);
    __weak NSObject *weakObj = obj;//weakObj弱引用,<NSObject: 0x7f9413c1c040>对象引用计数不变,=1
    BLog(@"weakObj-0", weakObj);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        __strong NSObject *strongObj = weakObj; //strongObj强引用,<NSObject: 0x7f9413c1c040>对象引用计数+1,=2
        sleep(3);
        BLog(@"weakObj - block", weakObj);
        BLog(@"strongObj - block", strongObj);
    });
    sleep(1);
    obj = nil; //obj被置为nil,<NSObject: 0x7f9413c1c040>对象引用计数-1,=1
    BLog(@"weakObj-1", weakObj);  //没被释放
    sleep(4); //block在异步线程中执行完毕(在另一块内存中执行),block内存被释放,<NSObject: 0x7f9413c1c040>对象引用计数-1,=0;ARC开始把0x7f9413c1c040对象内存回收,把弱引用weakObj置为nil
    BLog(@"weakObj-2", weakObj);
}

总结 :
多线程的时候,在 block 外部用__weak声明的变量指向一个对象, 通过把weak声明的变量值赋值给block内部的```strong变量,实现在block内对该对象进行强引用,这样可以在block生命周期内保留该对象不被释放,在block生命周期结束后,对象内存被释放。

 (void)blockVariable

{

    NSObject *obj = [[NSObject alloc]init]; //指向的对象:<NSObject: 0x7fb4039063b0>

    BLog(@"obj",obj);

    __block NSObject *blockObj = obj;  //blockObj指向对象:0x7fb4039063b0

    obj = nil;

    BLog(@"blockObj -1",blockObj);  //blockObj,0x7fff52365c90

    void(^testBlock)() = ^(){

        BLog(@"blockObj - block",blockObj);   //blockObj,0x7fb401c7d7f8,指向的对象:<NSObject: 0x7fb4039063b0>

        NSObject *obj2 = [[NSObject alloc]init]; // obj2 ,0x7fff52365bc8,指向的对象:<NSObject: 0x7fb401c83c40>

        BLog(@"obj2",obj2);

        blockObj = obj2;

        BLog(@"blockObj - block",blockObj); //blockObj,0x7fb401c7d7f8,指向对象:<NSObject: 0x7fb401c83c40>

    };

    NSLog(@"%@",testBlock);         //block 的地址  <__NSMallocBlock__: 0x7fb401c07d20>

    BLog(@"blockObj -2",blockObj);  //blockObj地址发生变化,0x7fb401c7d7f8,/指向的对象:<NSObject: 0x7fb4039063b0>

    testBlock();

    BLog(@"blockObj -3",blockObj);  //blockObj,0x7fb401c7d7f8,指向对象:<NSObject: 0x7fb401c83c40>

 

}

分析 :
第3处日志打印了一个testBlock对象,blockObj的地址发生变化。此时,block对象 从栈拷贝到堆上,__block变量blockObj,也被拷贝到堆上。block对象拥有blockObj指针指向的对象。注意:这是个强引用哦。
关注4到8 处日志,用__block关键字声明blockObj指针后,block内外的变量blockObj都是0x7fa084905838,也就是block内外的blockObj指针是同一个指针。
block内部改变 blockObj指针指向的对象,改动在 block外部可见。

常用的出现循环使用:(只要你在block里用到了self所拥有的东西!)

block在copy时都会对block内部用到的对象进行强引用(ARC)或者retainCount增1(非ARC)。在ARC与非ARC环境下对block使用不当都会引起循环引用问题,一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,简单说就是

self.someBlock = ^(Type var){
       [self dosomething];
      或者self.otherVar = XXX;
      或者_otherVar = ...XXX;
};

//block的这种循环引用会被编译器捕捉到并及时提醒。

  • 即使在你的block代码中没有显式地出现"self",也会出现循环引用!只要你在block里用到了self所拥有的东西!
  • 但对于这种情况,我们无法通过加__weak声明或者__block声明去禁止block对self进行强引用或者强制增加引用计数。但我们可以通过其他指针来避免循环引用,具体是这么做的:
__weak typeof(self) weakSelf = self;
self.blkA = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;//加一下强引用,避免weakSelf被释放掉
NSLog(@"%@", strongSelf->_xxView); //不会导致循环引用.
};

self.arr

1)ARC环境下:
ARC环境下可以通过使用_weak声明一个代替self的新变量代替原先的self,我们可以命名为weakSelf。通过这种方式告诉block,不要在block内部对self进行强制strong引用:(如果要兼容ios4.3,则用__unsafe_unretained代替__weak,不过目前基本不需考虑这么low的版本)

 self.arr = @[@111, @222, @333]; 
 __weak typeof(self) weakSelf=self; 
 self.block = ^(NSString *name){ 
  NSLog(@"arr:%@", weakSelf.arr); 
   };

2)MRC环境下:解决方式与上述基本一致,只不过将__weak关键字换成__block即可,这样的意思是告诉block:小子,不要在内部对self进行retain了!

委托delegate

一字诀:声明delegate时请用assign(MRC)或者weak(ARC),

对block想要深入研究的 :http://www.jianshu.com/p/ee9756f3d5f6 http://www.cocoachina.com/ios/20170527/19308.html
https://yq.aliyun.com/articles/62662

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

推荐阅读更多精彩内容