iOS Principle:Block(下)

简介: iOS Principle:Block(下)

6.三种 Block 本体


到这个阶段,我们用C的结构编译的代码以及源码能看到Block结构体内部的isa指针是指向_NSConcreteStackBlock的,其实这只是其中的一种,分别还有_NSContreteGlobalBlock 和 _NSContreteMallocBlock,可以根据命名的后缀看出来StackBlock是设置在栈上的,GlobalBlock就类似全局变量,设置在程序的数据区域(.data区域),那么最重要的也是我们写OC代码的时候根本不关注的一种类型NSContreteMallocBlock,没错,他就是和对象一样分类内存块(堆)中。


{
    NSConcreteGlobalBlock; // 在全局中定义的(数据区域)
    NSConcreteStackBlock; // 在局部定义的(栈)
    NSConcreteMallocBlock; // 分配在堆中
}


NSConcreteGlobalBlock 全局定义


上面我们说的都是 NSConcreteStackBlock 在局部定义的(栈),下面我们来看看 NSConcreteGlobalBlock

你可以把它理解为全局变量,反正存储在.data区域的,最直接得写法是这样的


void (^block)(void) = ^{};  
int main(){
}

Clang 转换过后的源码指针impl.isa = &_NSContreteGlobalBlock类型的

由于在使用全局变量的地方不能使用局部变量,这么说来就根本不存在对局部变量的捕获。那么这个Block的结构体实例的内容压根不会再进行追加成员变量,所以不会依赖于执行状态,所以整个程序运行只有一个实例。因此将Block使用的结构体实例设置在与全局变量相同的数据区域即可。(把它理解为单纯的全局变量就好了,而且不会有任何值的捕获)


存在的两种案例

  • 全局变量的地方,用这种Block语法时,如上面所示


  • Block语法中表达式不截获任何局部变量时,这个稍后Demo介绍,也很简单


NSContreteMallocBlock 堆定义


配置在全局的GlobalBlock可以出了作用域还是能继续访问,但是在栈上的StackBlock就废弃了,因此为了出了作用域能继续使用,Blocks提供了把Block和__block这两个东西从栈上复制到堆上的方法来解决这个问题。而_forwarding其实既可以指向自己,也可以指向复制后的自己,也就是说有了这个指针的存在,无论__block变量配置在堆上还是栈上都能够正确的访问__block变量


一种作为返回值返回的情况


typedef void(^block)(void);  
block func (int a) {  
    return ^{};  
}


转换后


block func (int a) {  
    block tmp = ((void (*)())&__func_block_impl_0((void *)__func_block_func_0, &__func_block_desc_0_DATA));  
    tmp = objc_retainBlock(tmp);  
    return objc_autoreleaseReturnValue(tmp);  
}


这里先是通过Block语法生成Block,即生成配置在栈上的Block结构体实例,然后赋值给Block类型的变量tmp中,然后执行objc_retainBlock(tmp)这句其实就是_Block_copy(tmp),将栈上的的Block复制到堆上,赋值后将堆上的指针赋值给tmp,然后堆上的所有都是对象,需要注册陶autoreleasepool中进行管理,然后返回其对象

其实这种内部调用方式,MallocBlock就是对象,而且这种写法是否很似曾相识,就是类方法实例化


- (NSArray *) myTestArray {  
    NSArray *array = [[NSArray alloc] initWithObjects: @"a", @"b", @"c", nil nil];  
    return [array autorelease];  
}



简单来说就是第一步copyBlock到堆上,然后和OC对象一样,返回的对象需要进行autorelease防治内存泄露


ARC下面很多都已经自动帮我们Copy成了MallocBlock了,请看一下几种情况

由于Block是默认建立在栈上,所以如果离开方法作用域,Block就会被丢弃,在非ARC情况下,我们要返回一个Block,需要


[Block copy];


在ARC下,以下几种情况, Block会自动被从栈复制到堆:


  • 1.被执行copy方法
  • 2.作为方法返回值
  • 3.将Block赋值给附有__strong修饰符的id类型的类或者Blcok类型成员变量时
  • 4.在方法名中含有usingBlock的Cocoa框架方法或者GDC的API中传递的时候


7.来猜一猜



int a = 1;  
// 这里的^{}初始化的block赋值给block变量,在OC中没有具体写明的情况下应该就是strong类型的,这就是上面第三点的例子  
// 打印出来 first <__NSMallocBlock__: 0x60800004d290>  
void (^block)(void) = ^{  
   NSLog(@"%d",a);  
};  
NSLog(@"first %@",block);  
// 没有截获变量的时候就是globalBlock  
// second<__NSGlobalBlock__: 0x102f80100>  
NSLog(@"second%@",^{NSLog(@"呵呵");});  
// 截获了变量就是stackBlock  
// third<__NSStackBlock__: 0x7fff5cc7faa0>  
NSLog(@"third%@",^{NSLog(@"%d",a);});  
__block int val = 10;  
__strong blk strongPointerBlock = ^{NSLog(@"val1 = %d", ++val);};  
// strongPointerBlock: <__NSMallocBlock__: 0x600000044e90>  
NSLog(@"strongPointerBlock: %@", strongPointerBlock); //1  
__weak blk weakPointerBlock = ^{NSLog(@"val2 = %d", ++val);};  
// weakPointerBlock: <__NSStackBlock__: 0x7fff5282ea70>  
NSLog(@"weakPointerBlock: %@", weakPointerBlock); //2  
// mallocBlock3: <__NSMallocBlock__: 0x608000046930>  
NSLog(@"mallocBlock3: %@", [weakPointerBlock copy]); //3  
// 截获了test <__NSStackBlock__: 0x7fff5282ea48>  
NSLog(@"test4 %@", ^{NSLog(@"val4 = %d", ++val);}); //4  
// test5 <__NSMallocBlock__: 0x60800005c0b0>  
NSLog(@"test5 %@", [^{NSLog(@"val4 = %d", ++val);} copy]); //5  
// stackBlock经过传参 打印  
NSLog(@"传参后 %@",[self getBlock]);  
}  
- (blk)getBlock {  
int val = 11;  
// 上面已经介绍了,这种直接打印传参前的block,应该是__NSStackBlock__  
NSLog(@"传参前:%@",^{NSLog(@"%d",val);});  
// 那么现在我们直接传出去  
return ^{NSLog(@"%d",val);};  
}


这里把几种情况都打印了一下看看到底是哪个类型


1.第一个和第二个打印的区别在于,第一个生成的Block默认赋值给了block变量,第二个直接打印,由于OC里面没有修饰符默认就是strong,所以这么看来遵循第三条规则之后,第二条条打印就是_NSGlobalBlock_(没有截获变量),第一条打印就是_NSMallocBlock_(自动复制到堆上了)


2.后面用strong修饰符和weak修饰符分别打印的是malloc的和stack的,但是无论哪种,只要copy就是变成malloc类型了


3.最后一种就是上面介绍的,stackBlock经过传参,自动变成了mallocBlock


8.属性修饰符 copy 修饰 Block


来看一张表,看看三种类型copy之后有什么区别

image.png


@property (nonatomic,copy) blk blk;


当我们这么声明属性的时候,其setter方法就是用了copy方法


- (void)setBlk:(blk)blk {  
    if (_blk != blk) {  
        [_blk release]; // MRC  
        _blk = [blk copy];  
    }  
}
  • 1.超出作用域存在的理由就是生成了MallocBlock对象,即使出栈了还是能继续调用
  • 2.forwarding的理由就是无论在堆上还是栈上,我们都能访问Block,而且能保证访问同一个
  • 3.GlobalBlock 、MallocBlock和StackBlock的区别以及如何Block如何会被copy到堆上
  • 4.特别上作为参数传递时,类似类方法的autoreleasepool注册进去,避免内存泄露
  • 5.在正常写代码的时候不需要管理这个,默认百分之99的情况基本都是MallocBlock
  • 6.无论什么情况下,copy一下或者强指针引用一下是不会有错的,能保证必然是MallocBlock
  • 7.各种迹象表明,他就是一个对象,可以通过copy改变类型的特殊对象

以上文章整理自:https://blog.csdn.net/deft_mkjing/article/details/53143076


目录
相关文章
|
iOS开发
iOS block修饰符用copy还是strong
iOS block修饰符用copy还是strong
169 0
|
iOS开发 Python
iOS小技能:lldb打印block参数签名
iOS逆向时经常会遇到参数为block类型,本文介绍一个lldb script,可快速打印出Objective-C方法中block参数的类型。
203 0
iOS小技能:lldb打印block参数签名
|
iOS开发 开发者
iOS开发 - 如何写出漂亮的block
iOS开发 - 如何写出漂亮的block
110 0
|
iOS开发
iOS开发- 关于Block的几种应用
iOS开发- 关于Block的几种应用
120 0
|
自然语言处理 iOS开发
IOS——Block
IOS——Block
81 0
|
iOS开发
iOS代理 通知 block传值的规范写法
iOS代理 通知 block传值的规范写法
146 0
|
iOS开发
iOS Principle:CGAffineTransform
iOS Principle:CGAffineTransform
185 0
iOS Principle:CGAffineTransform
|
安全 Unix API
iOS Principle:CALayer(下)
iOS Principle:CALayer(下)
178 0
iOS Principle:CALayer(下)
|
iOS开发
iOS Principle:CALayer(中)
iOS Principle:CALayer(中)
155 0
iOS Principle:CALayer(中)
|
API C语言 iOS开发
iOS Principle:CALayer(上)
iOS Principle:CALayer(上)
184 0
iOS Principle:CALayer(上)