ios学习路线—Objective-C(Block)
2016-06-16 00:12
405 查看
1.第一部分
定义和使用Block
定义Block变量,就相当于定义了一个函数。但是区别也很明显,因为函数肯定是在-viewDidLoad方法外面定义,而Block变量定义在了viewDidLoad方法内部。当然,我们也可以把Block定义在-viewDidLoad方法外部,例如上面的代码块printNumBlock的定义,就在-viewDidLoad外面。
再来看看上面代码运行的顺序问题,以第(3)个myBlock距离来说,在定义的地方,并不会执行Block{}内部的代码,而在myBlock(3)调用之后才会执行其中的代码,这跟函数的理解其实差不多,就是只要在调用Block(函数)的时候才会执行Block体内(函数体内)的代码。所以上面的简单代码示例,我可以作出如下的结论:
(1).在类中,定义一个Block变量,就是定义一个函数;
(2).Block可以定在方法内部,也可以定义在方法外部;
(3).只有调用Block时候,才会执行其{}体内的代码;
(PS:关于第2条,定义在方法外部的Block,其实就是文件级别的全局变量)
那么在类中定义一个Block,特别是在-viewDidLoad方法体内定义一个Block到底有什么意义呢?我表示这时候只把它当做私有函数就可以了。我之前说过,Block其实就相当于代理,那么这时候我该怎样将其与代理类比以了解呢。这时候我可以这样说:本类中的Block就相当于类自己服从某个协议,然后让自己代理自己去做某个事情。很拗口吧?看看下面的代码。
接着在-viewDidLoad中的代码如下
看出这种写法的奇葩地方了吗?自己委托自己去实现某个方法,而不是委托别的类去实现某个方法。本类中定义的一个Block其实就是闲的蛋疼,委托自己去字做某件事情,实际的意义不大,所以你很少看见别人的代码直接在类中定义Block然后使用的,Block很多的用处是跨越两个类来使用的,比如作为property属性或者作为方法的参数,这样就能跨越两个类了。
2.第二部分
__block关键字的使用
在Block的{}体内,是不可以对外面的变量进行更改的,比如下面的语句
这段代码有什么问题呢,Xcode会提示x变量错误信息:Variable is not assigning (missing __block type),这时候给int x = 100;语句前面加上__block关键字即可,如下:
这样在Block的{}体内,就可以修改外部变量了。
3.第三部分
Block作为property属性实现页面之间传值
需求:在ViewController中,点击Button,push到下一个页面NextViewController,在NextViewController的输入框TextField中输入一串字符,返回的时候,在ViewController的Label上面显示文字内容
(1)第一种方法:首先看看通过”协议代理”是怎么实现两个页面之间传值的吧
接下来我们在看看ViewController文件中的内容
//ViewController.m 文件
@interface ViewController ()
@property (strong, nonatomic) IBOutlet UILabel *nextVCInfoLabel;
@end
//点击Button进入下一个NextViewController页面
- (IBAction)btnClicked:(id)sender
{
NextViewController *nextVC = [[NextViewController alloc] initWithNibName:@”NextViewController” bundle:nil];
nextVC.delegate = self;//设置代理
[self.navigationController pushViewController:nextVC animated:YES];
}
//实现协议NextViewControllerDelegate中的方法
- (void)passTextValue:(NSString *)tfText
{
//self.nextVCInfoLabel是显示NextViewController传递过来的字符串Label对象
self.nextVCInfoLabel.text = tfText;
}
(2)第二种方法:使用Block作为property,实现两个之间传值
先看看NextViewController文件中的内容
再来看看ViewController文件中的内容
好了就这么多代码,可以使用Block来实现两个页面之间传值的目的,实际上就是取代了Delegate的功能。
4.第四部分
Block内存管理
objc层面如何区分不同内存区的Block
Block_private.h中有这样一组值:
其用于对block的isa指针赋值
1.栈
在栈上创建的block,其isa指针是_NSConcreteStackBlock。
2.全局区
在全局区创建的block,其比较类似,其构造函数会将isa指针赋值为_NSConcreteGlobalBlock。
3.堆
我们无法直接创建堆上的block,堆上的block需要从stack block拷贝得来,在
4.其余的isa类型
复制block
对block调用Block_copy方法,或者向其发送objc copy消息,最终都会调用runtime.c中的_Block_copy_internal函数,其内部实现会检查block的flag,从而进行不同的操作:
除了修改isa指针的值之外,拷贝过程中,还会将BLOCK_NEEDS_FREE置入,大家记住这个值,后面会用到。
最后,如果block有辅助copy/dispose函数,那么辅助的copy函数会被调用。
2.全局bloc的复制
全局block进行copy是直接返回了原block,没有任何的其他操作。
3.堆block的复制
栈block复制时,置入的BLOCK_NEEDS_FREE标记此时起作用,_Block_copy_internal函数识别当前block是一个堆block,则仅仅增加引用计数,然后返回原block。
总结:
1.复制的行为
对block调用复制,有以下几种情况:
(1).对全局区的block调用copy,会返回原指针,并且这期间不处理任何东西(至少目前的内部实现是这样);
(2).对栈上的block调用copy,每次会返回新复制到堆上的block的指针,同时,所有__block变量都会被复制至堆一份(多次拷贝,只会生成一份)。
(3).对已经位于heap上的block,再次调用copy,只会增加block的引用计数。
为什么我们不讨论retian的行为?原因是并没有Block_retain()这样的函数,而且objc里面的retain消息发送给block对象后,其内部实现是什么都不做。
2.objc类中的block复制
objc类实例方法中的block如果被复制至heap,那么当前实例会被增加引用计数,当这个block被释放时,此实例会被减少引用计数。
但如果这个block没有使用当前实例的任何成员,那么当前实例不会被增加引用计数。这也是很自然的道理,我既然没有用到这个instance的任何东西,那么我干嘛要retian它?
我们要注意的一点是,我看到网上有很多人说block引起了实例与block之间的循环引用(retain-cycle),并且给出解决方案:不直接使用self而先将self赋值给一个临时变量,然后再使用这个临时变量。
但是,大家注意,我们一定要为这个临时变量增加__block标记
5.第五部分
Block的声明和线程安全
Block属性的声明,首先需要用copy修饰符,因为只有copy后的Block才会在堆中,栈中的Block的生命周期是和栈绑定的。
另一个需要注意的问题是关于线程安全,在声明Block属性时需要确认“在调用Block时另一个线程有没有可能去修改Block?”这个问题,如果确定不会有这种情况发生的话,那么Block属性声明可以用nonatomic。如果不肯定的话(通常情况是这样的),那么你首先需要声明Block属性为atomic,也就是先保证变量的原子性(Objective-C并没有强制规定指针读写的原子性,C#有)
比如这样一个Block类型:
属性声明:
这里ARC和非ARC声明都是一样的,当然注意在非ARC下要release Block。
但是,有了atomic来保证基本的原子性还是没有达到线程安全的,接着在调用时需要把Block先赋值给本地变量,以防止Block突然改变。因为如果不这样的话,即便是先判断了Block属性不为空,在调用之前,一旦另一个线程把Block属性设空了,程序就会crash,如下代码:
所以正确的代码是(ARC):
在非ARC下则需要手动retain一下,否则如果属性被置空,本地变量就成了野指针了,如下代码:
6.第六部分
循环引用问题
在ARC下,由于__block抓取的变量一样会被Block retain,所以必须用弱引用才可以解决循环引用问题,iOS 5之后可以直接使用__weak,之前则只能使用__unsafe_unretained了,__unsafe_unretained缺点是指针释放后自己不会置空。示例代码:
在非ARC下,显然无法使用弱引用,这里就可以直接使用__block来修饰变量,它不会被Block所retain的,参考代码:
定义和使用Block
- (void)viewDidLoad{ [super viewDidLoad]; //(1)定义无参无返回值的Block void(^printBlock)() = ^(){ printf("no number"); }; printBlock(); printBlock(9); int multiplier = 7; //(3)定义名为myBlock的代码块,返回值类型为int int(^myBlock)(int) = ^(int num){ return num*multiplier; }; //使用定义的myBlock int newMutiplier = myBlock(3); printf("newMutiplier is %d",myBlock(3)); } //定义在-viewDidLoad方法外部 //(2)定义一个有参数,没有返回值的Block void (^printNumBlock)(int) = ^(int num){ printf("int number is %d",num); };
定义Block变量,就相当于定义了一个函数。但是区别也很明显,因为函数肯定是在-viewDidLoad方法外面定义,而Block变量定义在了viewDidLoad方法内部。当然,我们也可以把Block定义在-viewDidLoad方法外部,例如上面的代码块printNumBlock的定义,就在-viewDidLoad外面。
再来看看上面代码运行的顺序问题,以第(3)个myBlock距离来说,在定义的地方,并不会执行Block{}内部的代码,而在myBlock(3)调用之后才会执行其中的代码,这跟函数的理解其实差不多,就是只要在调用Block(函数)的时候才会执行Block体内(函数体内)的代码。所以上面的简单代码示例,我可以作出如下的结论:
(1).在类中,定义一个Block变量,就是定义一个函数;
(2).Block可以定在方法内部,也可以定义在方法外部;
(3).只有调用Block时候,才会执行其{}体内的代码;
(PS:关于第2条,定义在方法外部的Block,其实就是文件级别的全局变量)
那么在类中定义一个Block,特别是在-viewDidLoad方法体内定义一个Block到底有什么意义呢?我表示这时候只把它当做私有函数就可以了。我之前说过,Block其实就相当于代理,那么这时候我该怎样将其与代理类比以了解呢。这时候我可以这样说:本类中的Block就相当于类自己服从某个协议,然后让自己代理自己去做某个事情。很拗口吧?看看下面的代码。
//定义一个协议 @protocol ViewControllerDelegate<NSObject> - (void)selfDelegateMethod; @end //本类实现这个协议ViewControllerDelegate @interface ViewController ()<ViewControllerDelegate> @property (nonatomic, assign) id<ViewControllerDelegate> delegate; @end
接着在-viewDidLoad中的代码如下
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view from its nib. self.delegate = self; if (self.delegate && [self.delegate respondsToSelector:@selector(selfDelegateMethod)]) { [self.delegate selfDelegateMethod]; } } #pragma mark - ViewControllerDelegate method //实现协议中的方法 - (void)selfDelegateMethod { NSLog(@"自己委托自己实现的方法"); }
看出这种写法的奇葩地方了吗?自己委托自己去实现某个方法,而不是委托别的类去实现某个方法。本类中定义的一个Block其实就是闲的蛋疼,委托自己去字做某件事情,实际的意义不大,所以你很少看见别人的代码直接在类中定义Block然后使用的,Block很多的用处是跨越两个类来使用的,比如作为property属性或者作为方法的参数,这样就能跨越两个类了。
2.第二部分
__block关键字的使用
在Block的{}体内,是不可以对外面的变量进行更改的,比如下面的语句
- (void)viewDidLoad { //将Block定义在方法内部 int x = 100; void (^sumXAndYBlock)(int) = ^(int y){ x = x+y; printf("new x value is %d",x); }; sumXAndYBlock(50); }
这段代码有什么问题呢,Xcode会提示x变量错误信息:Variable is not assigning (missing __block type),这时候给int x = 100;语句前面加上__block关键字即可,如下:
__block int x = 100;
这样在Block的{}体内,就可以修改外部变量了。
3.第三部分
Block作为property属性实现页面之间传值
需求:在ViewController中,点击Button,push到下一个页面NextViewController,在NextViewController的输入框TextField中输入一串字符,返回的时候,在ViewController的Label上面显示文字内容
(1)第一种方法:首先看看通过”协议代理”是怎么实现两个页面之间传值的吧
//NextViewController是push进入的第二个页面 //NextViewController.h 文件 //定义一个协议,前一个页面ViewController要服从该协议,并且实现协议中的方法 @protocol NextViewControllerDelegate <NSObject> - (void)passTextValue:(NSString *)tfText; @end @interface NextViewController : UIViewController @property (nonatomic, assign) id<NextViewControllerDelegate> delegate; @end //NextViewController.m 文件 //点击Button返回前一个ViewController页面 - (IBAction)popBtnClicked:(id)sender { if (self.delegate && [self.delegate respondsToSelector:@selector(passTextValue:)]) { //self.inputTF是该页面中的TextField输入框 [self.delegate passTextValue:self.inputTF.text]; } [self.navigationController popViewControllerAnimated:YES]; }
接下来我们在看看ViewController文件中的内容
//ViewController.m 文件
@interface ViewController ()
@property (strong, nonatomic) IBOutlet UILabel *nextVCInfoLabel;
@end
//点击Button进入下一个NextViewController页面
- (IBAction)btnClicked:(id)sender
{
NextViewController *nextVC = [[NextViewController alloc] initWithNibName:@”NextViewController” bundle:nil];
nextVC.delegate = self;//设置代理
[self.navigationController pushViewController:nextVC animated:YES];
}
//实现协议NextViewControllerDelegate中的方法
- (void)passTextValue:(NSString *)tfText
{
//self.nextVCInfoLabel是显示NextViewController传递过来的字符串Label对象
self.nextVCInfoLabel.text = tfText;
}
(2)第二种方法:使用Block作为property,实现两个之间传值
先看看NextViewController文件中的内容
//NextViewController.h 文件 @interface NextViewController : UIViewController @property (nonatomic, copy) void (^NextViewControllerBlock)(NSString *tfText); @end //NextViewContorller.m 文件 - (IBAction)popBtnClicked:(id)sender { if (self.NextViewControllerBlock) { self.NextViewControllerBlock(self.inputTF.text); } [self.navigationController popViewControllerAnimated:YES]; }
再来看看ViewController文件中的内容
- (IBAction)btnClicked:(id)sender { NextViewController *nextVC = [[NextViewController alloc] initWithNibName:@"NextViewController" bundle:nil]; nextVC.NextViewControllerBlock = ^(NSString *tfText){ [self resetLabel:tfText]; }; [self.navigationController pushViewController:nextVC animated:YES]; } #pragma mark - NextViewControllerBlock method - (void)resetLabel:(NSString *)textStr { self.nextVCInfoLabel.text = textStr; }
好了就这么多代码,可以使用Block来实现两个页面之间传值的目的,实际上就是取代了Delegate的功能。
4.第四部分
Block内存管理
objc层面如何区分不同内存区的Block
Block_private.h中有这样一组值:
BLOCK_EXPORT void * _NSConcreteStackBlock[32]; BLOCK_EXPORT void * _NSConcreteMallocBlock[32]; BLOCK_EXPORT void * _NSConcreteAutoBlock[32]; BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32]; BLOCK_EXPORT void * _NSConcreteGlobalBlock[32]; BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];
其用于对block的isa指针赋值
1.栈
struct __OBJ1__of2_block_impl_0 { struct __block_impl impl; struct __OBJ1__of2_block_desc_0* Desc; OBJ1 *self; __OBJ1__of2_block_impl_0(void *fp, struct __OBJ1__of2_block_desc_0 *desc, OBJ1 *_self, int flags=0) : self(_self) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
在栈上创建的block,其isa指针是_NSConcreteStackBlock。
2.全局区
在全局区创建的block,其比较类似,其构造函数会将isa指针赋值为_NSConcreteGlobalBlock。
3.堆
我们无法直接创建堆上的block,堆上的block需要从stack block拷贝得来,在
runtime.c中的_Block_copy_internal函数中,有这样几行: // Its a stack block. Make a copy. if (!isGC) { struct Block_layout *result = malloc(aBlock->descriptor->size); ... result->isa = _NSConcreteMallocBlock; ... return result; }
4.其余的isa类型
BLOCK_EXPORT void * _NSConcreteAutoBlock[32]; BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32]; BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];
复制block
对block调用Block_copy方法,或者向其发送objc copy消息,最终都会调用runtime.c中的_Block_copy_internal函数,其内部实现会检查block的flag,从而进行不同的操作:
static void *_Block_copy_internal(const void *arg, const int flags) { ... aBlock = (struct Block_layout *)arg; ... }1.栈block的复制 // reset refcount result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed result->flags |= BLOCK_NEEDS_FREE | 1; result->isa = _NSConcreteMallocBlock; if (result->flags & BLOCK_HAS_COPY_DISPOSE) { //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock); (*aBlock->descriptor->copy)(result, aBlock); // do fixup }
除了修改isa指针的值之外,拷贝过程中,还会将BLOCK_NEEDS_FREE置入,大家记住这个值,后面会用到。
最后,如果block有辅助copy/dispose函数,那么辅助的copy函数会被调用。
2.全局bloc的复制
else if (aBlock->flags & BLOCK_IS_GLOBAL) { return aBlock; }全局block进行copy是直接返回了原block,没有任何的其他操作。
全局block进行copy是直接返回了原block,没有任何的其他操作。
3.堆block的复制
if (aBlock->flags & BLOCK_NEEDS_FREE) { // latches on high latching_incr_int(&aBlock->flags); return aBlock; }
栈block复制时,置入的BLOCK_NEEDS_FREE标记此时起作用,_Block_copy_internal函数识别当前block是一个堆block,则仅仅增加引用计数,然后返回原block。
总结:
1.复制的行为
对block调用复制,有以下几种情况:
(1).对全局区的block调用copy,会返回原指针,并且这期间不处理任何东西(至少目前的内部实现是这样);
(2).对栈上的block调用copy,每次会返回新复制到堆上的block的指针,同时,所有__block变量都会被复制至堆一份(多次拷贝,只会生成一份)。
(3).对已经位于heap上的block,再次调用copy,只会增加block的引用计数。
为什么我们不讨论retian的行为?原因是并没有Block_retain()这样的函数,而且objc里面的retain消息发送给block对象后,其内部实现是什么都不做。
2.objc类中的block复制
objc类实例方法中的block如果被复制至heap,那么当前实例会被增加引用计数,当这个block被释放时,此实例会被减少引用计数。
但如果这个block没有使用当前实例的任何成员,那么当前实例不会被增加引用计数。这也是很自然的道理,我既然没有用到这个instance的任何东西,那么我干嘛要retian它?
我们要注意的一点是,我看到网上有很多人说block引起了实例与block之间的循环引用(retain-cycle),并且给出解决方案:不直接使用self而先将self赋值给一个临时变量,然后再使用这个临时变量。
但是,大家注意,我们一定要为这个临时变量增加__block标记
5.第五部分
Block的声明和线程安全
Block属性的声明,首先需要用copy修饰符,因为只有copy后的Block才会在堆中,栈中的Block的生命周期是和栈绑定的。
另一个需要注意的问题是关于线程安全,在声明Block属性时需要确认“在调用Block时另一个线程有没有可能去修改Block?”这个问题,如果确定不会有这种情况发生的话,那么Block属性声明可以用nonatomic。如果不肯定的话(通常情况是这样的),那么你首先需要声明Block属性为atomic,也就是先保证变量的原子性(Objective-C并没有强制规定指针读写的原子性,C#有)
比如这样一个Block类型:
typedef void (^MyBlockType)(int);
属性声明:
@property (copy) MyBlockType myBlock;
这里ARC和非ARC声明都是一样的,当然注意在非ARC下要release Block。
但是,有了atomic来保证基本的原子性还是没有达到线程安全的,接着在调用时需要把Block先赋值给本地变量,以防止Block突然改变。因为如果不这样的话,即便是先判断了Block属性不为空,在调用之前,一旦另一个线程把Block属性设空了,程序就会crash,如下代码:
if (self.myBlock) { //此时,走到这里,self.myBlock可能被另一个线程改为空,造成crash //注意:atomic只会确保myBlock的原子性,这种操作本身还是非线程安全的 self.myBlock(123); }
所以正确的代码是(ARC):
MyBlockType block = self.myBlock; //block现在是本地不可变的 if (block) { block(123); }
在非ARC下则需要手动retain一下,否则如果属性被置空,本地变量就成了野指针了,如下代码:
//非ARC MyBlockType block = [self.myBlock retain]; if (block) { block(123); } [block release];
6.第六部分
循环引用问题
在ARC下,由于__block抓取的变量一样会被Block retain,所以必须用弱引用才可以解决循环引用问题,iOS 5之后可以直接使用__weak,之前则只能使用__unsafe_unretained了,__unsafe_unretained缺点是指针释放后自己不会置空。示例代码:
//iOS 5之前可以用__unsafe_unretained //__unsafe_unretained typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self; self.myBlock = ^(int paramInt) { //使用weakSelf访问self成员 [weakSelf anotherFunc]; };
在非ARC下,显然无法使用弱引用,这里就可以直接使用__block来修饰变量,它不会被Block所retain的,参考代码:
//非ARC __block typeof(self) weakSelf = self; self.myBlock = ^(int paramInt) { //使用weakSelf访问self成员 [weakSelf anotherFunc]; };
相关文章推荐
- Objective-C --- - UICollectionView (梳理总结)
- 在Objective-C中,如何进行内存管理
- Objective-C中遍历字典、数组和集合的几种常见方式
- Objective-C入门知识
- 空对象模式(Null Object Pattern)
- ios学习路线—Objective-C(KVO)
- android群英传objectAnimator的问题
- ios学习路线—Objective-C(KVC)
- ios学习路线—Objective-C(Extension)
- 【ReactNative】undefined is not an object(evaluating 'this.props.navigator')
- ios学习路线—Objective-C(Protocol)
- ios学习路线—Objective-C(Category)
- Scala伴生类Class和伴生对象Object的区别
- Objective-C动画学习之iOS Animation
- ios学习路线—Objective-C(深浅拷贝)
- ios学习路线—Objective-C(检查内存泄露方式(Instruments))
- ios学习路线—Objective-C(检查内存泄露方式(Enable NSZombie Objects))
- ios学习路线—Objective-C(检查内存泄露方式(Static Analyzer))
- ios学习路线—Objective-C(堆(heap)和栈(stack))
- 关于Object=null