看我怎么搞定OC的ios block 闭包和Swift的闭包

OC与Swift闭包对比总结 - 简书
OC与Swift闭包对比总结
最近在看Swift闭包截获变量时遇到了各种问题,总结之后发现主要是还用停留在OC时代的思维来思考Swift问题导致的。借此机会首先复习一下OC中关于block的细节,同时整理Swift中闭包的相关的问题。不管是目前使用OC还是Swift,又或者是从OC转向Swift,都可以阅读这篇文章并与我交流。
OC的block已经有很多相关的文章介绍了,主要难点在于__block修饰符的作用和原理,以及循环引用问题。我们首先由浅入深举几个例子看一看__block修饰符,最后分析循环引用问题。这里的讨论都是基于ARC的。
截获基本类型
int value = 10;
void(^block)() = ^{
NSLog(@"value = %d", value);
value = 20;
// 打印结果是:"value = 10"
OC的block会截获外部变量,对于int等基本数据类型,block的内部会拷贝一份,简单来说,它的实现大概是这样的:
struct block_impl {
//其它内容
因为block内部拷贝了截获的变量的副本,所以生成block后再修改变量,不会影响被block截获的变量。同时block内部也不能修改这个变量。
修改基本类型
如果要想在block中修改被截获的基本类型变量,我们需要把它标记为__block:
__block int value = 10;
void(^block)() = ^{
NSLog(@"value = %d", value);
value = 20;
// 打印结果是:"value = 20"
这是因为,对于被标记了__block的变量,block在截获它时,会保存一个指针。简单来说,它的实现大概是这样的:
struct block_impl {
//其它内容
block_ref_value *
struct block_ref_value {
// 这里保存的才是被截获的value的值。
由于block中一直有一个指针指向value,所以block内部对它的修改,可以影响到block外部的变量。因为block修改的就是那个外部变量而不是外部变量的副本。
上面关于block具体实现的例子只是一个简化模型,事实上并非如此,但本质类似。总的来说,只有由__block修饰符修饰的变量,在被block截获时才是可变的。关于这方面的详细解释,可以参考这三篇文章:
:这个很详细地讲了__block的实现原理
:这个讲了一些block底层的实现原理以及循环引用问题。
:这是我之前写过的一篇介绍__block原理的文章,内容会详细一些。
block截获指针和截获基本类型是相似的,不过稍稍复杂一些。先看一个最简单的例子。
Person *p = [[Person alloc] initWithName:@"zxy"];
void(^block)() = ^{
NSLog(@"person name = %@", p.name);
p.name = @"new name";
// 打印结果是:"person name = new name"
在截获基本类型时,block内部可能会有int capturedValue =这样的代码,类比到指针也是一样的,block内部也会有这样的代码:Person *capturedP =。在ARC下,这其实是强引用(retain)了block外部的p。
由于block内部的p和外部的p指向的是同一块内存地址。所以在block外部修改p的属性,依然会影响到block内部截获的p。
需要强调一点,这里的p依然不是可变的。修改p的name不是改变p,只是改变p内部的属性:
Person *p = [[Person alloc] initWithName:@"zxy"];
void(^block)() = ^{
p.name = @"new name"; //OK,没有改变p
p = [[Person alloc] initWithName:@"new name"]; //编译错误
NSLog(@"person name = %@", p.name);
类比__block修饰符对基本类型的作用原理,由它修饰的指针,在被block截获时,截获的其实是这个指针的指针。比如我们把刚刚的例子修改一下:
__block Person *p = [[Person alloc] initWithName:@"zxy"];
void(^block)() = ^{
NSLog(@"person name = %@", p.name);
// 打印结果是:"person name = (null)"
此时,block内部有一个指向外部的p的指针,一旦p被设为nil,这个内部的指针就指向了nil。所以打印结果就是null了。
__block与强引用
还记得以前有一次面试时被问到,__block会不会retain变量?答案是:会的。从原理上分析,__block修饰的变量被封装在结构体中,block内部持有对这个结构体的强引用。这一点不管是对于基本类型还是指针都是通用的。从实际例子上来说:
if (true) {
__block Person *p = [[Person alloc] initWithName:@"zxy"];
block = ^{
NSLog(@"person name = %@", p.name);
// 打印结果是:"person name = zxy"
如果没有retain被标记为__block的指针p,那么超出作用于后应该会得到nil。
避免循环引用
不管对象是否标记为__block,一旦block截获了它,就会强引用它。所以,判断是否发生循环引用,只要判断block截获的对象,是否也持有block即可。如果这个对象确实需要直接或间接持有block,那么我们需要避免block强引用这个对象。解决办法是使用__weak修饰符。
// block是self的一个属性
id __weak weakSelf =
block = ^{
//使用weakSelf代替self
block不会强引用被标记为__weak的对象,只会对其产生弱引用。为了防止在block内的操作会释放wself,可以先强引用它。这种做法有一个很漂亮的名字叫weak-strong dacne,具体实现方法可以参考RAC的@strongify和@weakify。
OC中block总结
简单来说,除非标记为__weak,block总是会强引用任何捕获的对象。而__block表示捕获的就是指针本身,而非另一个指向这个对象的指针。也就是说,被__block修饰的对象在block内、外的改动会互相影响。
如果想避免循环引用问题,首先要确定block引用了哪些对象,然后判断这些对象是否直接或间接持有block,如果有的话把这些对象标记为__weak避免block强引用它。
Swift的闭包
OC中的__block是一个很讨厌的修饰符。它不仅不容易理解,而且在ARC和非ARC的表现截然不同。__block修饰符本质上是通过截获变量的指针来达到在闭包内修改被截获的变量的目的。
在Swift中,这叫做截获变量的引用。闭包默认会截取变量的引用,也就是说所有变量默认情况下都是加了__block修饰符的。
var x = 42
// [x] in //如果取消注释,结果是42
f() // 结果是43
如果如果被截获的变量是引用,和OC一样,那么在闭包内部有一个引用的引用:
var block2: (() -& ())?
var a: A? = A()
block2 = {
print(a?.name)
a = A(name: "new name")
block2?() //结果是:"Optional("new name")"
如果把变量写在截获列表中,那么block内部会有一个指向对象的强引用,这和在OC中什么都不写的效果是一样的:
var block2: (() -& ())?
var a: A? = A()
block2 = {
print(a?.name)
a = A(name: "new name")
block2?() //结果是:"Optional("old name")"
Swift会自动持有被截获的变量的引用,这样就可以在block内部直接修改变量。不过在一些特殊情况下,Swift会做一些优化。通过之前OC中对__block的分析可以看到,持有变量的引用肯定比直接持有变量开销更大。所以Swift会自动判断你是否在闭包中或闭包外改变了变量。如果没有改变,闭包会直接持有变量,即使你没有显式的把它卸载捕获列表中。下面这句话截取自:
As an optimization, Swift may instead capture and store a copy of a value if that value is not mutated by or outside a closure.
Swift循环引用
不管是否显示的把变量写进捕获列表,闭包都会对对象有强引用。如果闭包是某个对象的属性,而且闭包中截获了对象本身,或对象的某个属性,就会导致循环引用。这和OC中是完全一样的。解决方法是在捕获列表中把被截获的变量标记为weak或unowned。
关于Swift的循环引用,有一个需要注意的例子:
var name: String = "A"
var block: (() -& ())?
//其他方法
var a: A? = A()
var block = {
print(a?.name)
a?.block = block
我们先创建了可选类型的变量a,然后创建一个闭包变量,并把它赋值给a的block属性。这个闭包内部又会截获a,那这样是否会导致循环引用呢?
答案是否定的。虽然从表面上看,对象的闭包属性截获了对象本身。但是如果你运行上面这段代码,你会发现对象的deinit方法确实被调用了,打印结果不是“A”而是“nil”。
这是因为我们忽略了可选类型这个因素。这里的a不是A类型的对象,而是一个可选类型变量,其内部封装了A的实例对象。闭包截获的是可选类型变量a,当你执行a = nil时,并不是释放了变量a,而是释放了a中包含的A类型实例对象。所以A的deinit方法会执行,当你调用block时,由于使用了可选链,就会得到nil,如果使用强制解封,程序就会崩溃。
如果想要人为造成循环引用,代码要这样写:
var block: (() -& ())?
var a = A()
print(a.name)
a.name = "New Name"
Weak-Strong Dance
为了避免weak变量在闭包中提前被释放,我们需要在block一开始强引用它。这在OC部分已经讲过如何使用了。Swift中实现Weak-Strong Dance一般有三种方法。分别是最简单的if let可选绑定、标准库的withExtendedLifetime方法和自定义的withExtendedLifetime方法。
OC中默认截获变量,Swift默认截获变量的引用。它们都会强引用被截获的变量。
Swift中没有__block修饰符,但是多了截获列表。通过把截获的变量标记为weak避免引用循环
两者都有Weak-Strong Dance,不过这一点上OC的写法更简单。
在使用可选类型时,要明确闭包截获了可选类型还是实例变量。这样才能正确判断是否发生循环引用。
个人博客:
全栈计划:http://fullstack.blog/
我的微博:/bestswifter
扫码加入我的高质量付费私聊圈swift语言开发(29)
最新一些学妹问起,所以抽点时间来写的,适合入门级别的swift 与 OC 混编 的程序猿。 &
本文章将从两个方向分别介绍 OC 与 swift 混编 &
1. 第一个方向从 swift工程 中引入 oc类&
& & 1. 1 如何在swift的类中使用oc类
& & 1.2 &如何在swift中实现oc的代理方法
& & 1.3 & 如何在swift中实现oc的Block回调
2. 第二个方向从OC工程中引入swift类
& & 2.1 &如何在OC类中使用swift类
& & 2.2 & 如何在OC中实现swift的代理方法
& & 2.3 & 如何在OC中实现swift中类似Block回调
下面是具体的实现过程:
&1.1 &如何在swift的类中使用oc类?&
1. &工程中引入OC类。 具体实现过程。
& & 1.1 新建一个swift工程类。 取名 swiftOrOC
& & 1.2 &实现的功能为 : &从swift. viewController.swift 中 push到 O&secondViewController
1.2.1 &新建SecondViewController 类 。
& && 1.2.2 建立桥接文件。 (很重要)
& & 一定要记得点击这个按钮。&
& & & &1.2.3 &接下来工程目录如下:
& & &1.2.4 接下来就可以实现具体的跳转功能了。&
& & & ViewController.swift中具体实现
1.2 如何在swift中实现oc的代理方法
& & & &1.2.1 首先在 SecondViewController.h 中声明一个协议。具体代码
& & &1.2.2 然后在SecondViewController.m中,通过一个UITextField,让用户输入内容,当用户点击返回的时候把输入框中的内容返回给对应的代理。具体代码如下
1.2.3 接下来就非常简单了,让ViewController.swift只需要成为SecondViewController的代理,然后遵循她的协议,就可以了。 具体代码如下。
& & & &1.2.3.1 遵循协议
& & &1.2.3.2 成为代理,并实现协议方法,更改controller.swift中hintLabel的text。
&1.3 & 如何在swift中实现oc的Block回调
1.3.1 具体过程与1.2小节一样。 直接上代码。
& & & & 1.3.2 声明
& & & & 1.3.3 block的回调。 SecondViewController.m中
& & & & 1.3.4 在swift类中调用 oc的block.
& &工程已上传到上,git地址:&
2. &OC工程中引入swift类。 具体实现过程。
& & 耽误了不少时间, 今天才开始写oc工程中引入swift类。
& & demo地址:&
& & &2.1 &如何在OC类中使用swift类
& & & &2.1.1 & 新建一个基于OC语言的工程 ,取名 OcOrSwiftTwo
& & & &2.1. 2 &实现的功能为 : 从oc类 viewcontroller中, push 至 swift语言 SecondViewController &,然后SecondViewController可以通过代理或者swift闭包把值传回viewcontroller.&
& & & &2.1.3 & 当前文件目录看下图: &(第四个箭头: 桥接文件)
& & 2.2 & 如何在OC中实现swift的代理与闭包Block方法
& & & & & &&
& & 2.2.1 如何在oc中引入swift类。#import &工程名-swift.h&
& &2.2.2 在secondViewController.swift 中实现代理与闭包,代码如下:
& & 注意: @objc(代理名) &才能在外部可见这个代理
& & 2.2.3 & 在oc类中viewcontroller.m 文件中实现SecondviewController.swift的相关代理与闭包(block). 代码如下:
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:358485次
积分:6077
积分:6077
排名:第4093名
原创:215篇
转载:321篇
评论:25条
微信:chenfan1112015 QQ:
(12)(6)(7)(9)(5)(13)(14)(30)(16)(8)(13)(15)(10)(8)(40)(24)(1)(3)(2)(5)(8)(3)(8)(10)(9)(2)(4)(3)(5)(1)(8)(15)(24)(21)(4)(64)(60)(8)(13)(4)(1)(8)(12)2015年4月 移动开发大版内专家分月排行榜第二
2015年5月 移动开发大版内专家分月排行榜第三2015年3月 移动开发大版内专家分月排行榜第三2014年10月 移动开发大版内专家分月排行榜第三
2015年4月 移动开发大版内专家分月排行榜第二
2015年5月 移动开发大版内专家分月排行榜第三2015年3月 移动开发大版内专家分月排行榜第三2014年10月 移动开发大版内专家分月排行榜第三
匿名用户不能发表回复!|
每天回帖即可获得10分可用分!小技巧:
你还可以输入10000个字符
(Ctrl+Enter)
请遵守CSDN,不得违反国家法律法规。
转载文章请注明出自“CSDN(www.csdn.net)”。如是商业用途请联系原作者。CocoaChina
前言本文不会详细介绍 Block ( 闭包 ) 使用,网上也有很多详细的介绍。我们使用 Block 经常要注意循环引用问题,在很早以前我只用到了 __weak 并不知道 __strong 用的有啥意义存在。后来遇到坑了才明白其中的真理!之前文章中也提到这个问题,仅仅是讲了使用层面,并没有去讲如何理解其中的道理,接下来我们来理解一下。目录1 ) __weak 的使用2 ) __weak
一起使用1.OC 中 Block 的循环引用我创建一个 LRShop 类,其中有下面 2 个属性:@property
( nonatomic,
copy ) NSString
*@property
( nonatomic,
copy ) void ( ^myBlock ) ( ) ;首先我们从基础一步一步去理解,先分析下面的代码:LRShop
[ [ LRShop
alloc ] init ] ;
shop.string
shop.myBlock
NSLog ( @"%@",shop.string ) ;
shop.myBlock ( ) ;如果 block 代码块的内部,使用了外面的强引用 shop 对象(也就是 shop.myBlock 代码块内部使用了 NSLog ( @"%@",shop.string ) ;),block 代码块的内部会自动产生一个强引用,引用着 shop 对象!所以上面的 shop 不会被销毁,造成循环引用!下面画一张图方便去理解:图中的每条橙色的线都是强引用。shop 指向着 LRShop 对象,内部 myBlock 指向着 Block 代码块,string 指向着 @"", 最后 Block 代码块指向着 LRShop 对象。__weak 的使用解决循环引用的问题我们第一个会用到 __weak 我这里声明了一个宏,如果不明白这个宏可以看中的第 8 点:#define
LRWeakSelf ( type )
typeof ( type )
weak##type
LRWeakSelf ( shop ) ;
shop.myBlock
NSLog ( @"%@",weakshop.string ) ;
shop.myBlock ( ) ;Block 外部声明了一个弱引用,在内部使用就不会造成循环引用,所以如果 block 代码块的内部,使用了外面声明的的弱引用 weakshop 对象(也就是 shop.myBlock 代码块内部使用了 NSLog ( @"%@",weakshop.string ) ;),block 代码块的内部会自动产生一个弱引用,引用着 shop 对象!我们继续来看下内存图:总结:Block 内部使用外部的一个对象,如果外部对象是强引用那么内部会自动生成一个强引用,引用着外部对象。如果外部对象是弱引用那么内部会自动生成一个弱引用,引用着外部对象。如果还是有点迷茫我们最后在举一个例子:
self.myName
@" 我的名字是杰克 !";
[ [ LRShop
alloc ] init ] ;
shop.myBlock
NSLog ( @"%@",self.myName ) ;
shop.myBlock ( ) ;上面的代码会不会造成循环引用呢?答案是不会的,首先 self.myName 是 ViewController 控制器的一个属性,Block 内部使用外部的 self.myName,外部的 self.myName 是强引用那么内部会自动生成一个强引用引用着 self.myName。Block 内部强引用 self.myName,但是 self.myName 没有强引用 Block!说白了就是粉丝与明星的关系,粉丝 ( Block ) 单方面追求明星 ( self.myName ) ,但是随便粉丝怎么单方面的追求,明星都不搭理粉丝!__weak
一起使用我们先看看下面代码://2 个宏 #define
LRWeakSelf ( type )
typeof ( type )
weak##type
LRStrongSelf ( type )
typeof ( type )
weak##------------------------------------------------------
[ [ LRShop
alloc ] init ] ;
shop.string
LRWeakSelf ( shop ) ;
shop.myBlock
LRStrongSelf ( shop )
NSLog ( @"%@",shop.string ) ;
shop.myBlock ( ) ;表面来看外部一个弱引用,内部一个强引用那不是跟没写一样么?我们要理解一个问题 LRStrongSelf ( shop ) 是 Block 内部的强引用,而不是外部强引用。所以 Block 内部声明的强引用不管怎么访问都是不会干扰外部的对象,也不会自动产生一个强引用。所以没有循环引用,也能输出 shop.string 看着跟之前讲的仅仅使用 __weak 没什么区别,那我们在来看看下面的代码:仅仅使用 LRWeakSelf ( shop ) ; 并且在 myBlock 中增加一个延迟 2 秒在输出就会出现问题 , 虽然对象销毁了 , 输出的值却是 null// 弱引用
LRWeakSelf ( shop ) ;
shop.myBlock
dispatch_after ( dispatch_time ( DISPATCH_TIME_NOW,
( int64_t ) ( 2.0
NSEC_PER_SEC ) ) ,
dispatch_get_main_queue ( ) ,
NSLog ( @"%@",weakshop.string ) ;
shop.myBlock ( ) ;如果 LRWeakSelf ( shop ) ; 与 LRStrongSelf ( shop ) ; 一起使用输出的 shop.string 有值 , 对象也销毁了:LRWeakSelf ( shop ) ;
shop.myBlock
LRStrongSelf ( shop )
dispatch_after ( dispatch_time ( DISPATCH_TIME_NOW,
( int64_t ) ( 2.0
NSEC_PER_SEC ) ) ,
dispatch_get_main_queue ( ) ,
NSLog ( @"%@",shop.string ) ;
shop.myBlock ( ) ;那这又是什么原因呢?我们继续画个内存图来看看:额 ~ 这图有点乱啊,那就来一步一步分析,分析完之后上面所有的问题就会迎面而解!LRShop *shop =
[ [ LRShop alloc ] init ] ; 这行代码一执行就会出现图中第 1 条强引用的线,引用着 LRShop 对象。LRWeakSelf ( shop ) ; 这行代码一执行就会出现图中第 2 条弱引用的线,引用着 LRShop 对象,第 3 条线我们可以跳过不用看。shop.myBlock = ^{} 这行 Block 代码一执行就会出现图中第 4 条强引用的线。之后会执行 shop.myBlock ( ) ; 这行代码,回调到 Block 代码块中,然后会执行 LRStrongSelf ( shop ) 这行代码,在执行这行代码前会自动产生第 5 条弱引用的线引用着 LRShop 对象 ( 原因:Block 内部使用外部的一个对象,如果外部对象是弱引用那么内部会自动生成一个弱引用,引用着外部对象 ) ,最后就产生第 6 条强引用的线,引用着 LRShop 对象。dispatch_after 这行代码一执行就会出现图中第 7 条 GCD 系统内部强引用的线,引用着 dispatch_after。由于 GCD 的 dispatch_after 代码块内部用到 NSLog ( @"%@",shop.string ) ; 用到了外部的强引用对象 shop ( 原因:Block 内部使用外部的一个对象,如果外部对象是强引用那么内部会自动生成一个强引用,引用着外部对象。 ) 所以就会出现图中第 8 条强引用的线,引用着 LRShop 对象。上面 6 条是如何创建的,下面是如何释放的:dispatch_after 的 Block 内部会延迟 2 秒执行,并且不会阻塞线程,所以任务会一直往下走,当 shop.myBlock = ^{} 的 Block 大括号执行完,内部的局部变量 LRStrongSelf ( shop ) 就会销毁,第 6 条线就会销毁。最终 -
( void ) viewDidLoad {} 的大括号也会执行完,所以 LRShop *shop =
[ [ LRShop alloc ] init ] ; 与
LRWeakSelf ( shop ) ; 局部变量也会销毁,第 1 条线与第 2 条线都会销毁。现在只剩下 GCD 的第 8 条线强引用着 LRShop 对象,所以 LRShop 对象没有销毁,只有等待的 2 秒结束后,因为 LRShop 对象没有死所以输出有值,然后 GCD 系统内部不会再去强引用 dispatch_after 的 Block,首先第 7 条线销毁,第 8 条线在销毁。最后没有人强引用 LRShop 对象所以全部销毁!Swift 闭包在 Swift 中解决闭包循环引用有三种办法我们来看看:1.weak var weakShop = shop 方式解决循环引用,在平时开发中我们不用这个方法,用起来很麻烦!
LRShop ( )
shop.myBlock
weakShop?.string
print ( ( weakShop?.string ) ! )
shop.myBlock! ( str:
" 哈喽,你好!" ) 2. [ unowned shop ] 方式解决循环引用,在平时开发中我们不用这个方法,这个方法是很危险的!
LRShop ( )
shop.myBlock
{ [ unowned
shop.string
print ( shop.string! )
shop.myBlock! ( str:
" 哈喽,你好!" ) 3. [ weak self ] 方式解决循环引用,在平时开发中我们经常用这个方法,这个方式只是一种简便的写法!
LRShop ( )
shop.myBlock
shop?.string
print ( ( shop?.string ) ! )
shop.myBlock! ( str:
" 哈喽,你好!" ) 输出结果都能解决循环引用问题,下图 deinit 相当 OC 中的 dealloc 方法 :那我们已经知道 Swift 中用 weak 也能解决循环引用,那么可不可以 weak 与 strong 一起使用呢?我找了没有 strong 这个关键字,那我们该如何解决下面延迟 2 秒后在执行任务的问题呢:let
LRShop ( ) 9
shop.myBlock
// 时间设置
NSTimeInterval
//GCD:延迟 2 秒
dispatch_after ( dispatch_time ( DISPATCH_TIME_NOW,
Int64 ( time
Double ( NSEC_PER_SEC ) ) ) ,
dispatch_get_main_queue ( ) )
shop?.string
print ( ( shop?.string ) )
shop.myBlock! ( str:
" 哈喽,你好!" ) 我觉得既然没有 strong 那肯定会有其他办法来解决这个问题,既然只缺少一个强引用,那我就声明一个强引用给他用:let
strongShop
=上面代码就是我在闭包内部声明的一个 strongShop 强引用,详细代码如下:
LRShop ( )
shop.myBlock
strongShop
// 时间设置
NSTimeInterval
//GCD:延迟 2 秒
dispatch_after ( dispatch_time ( DISPATCH_TIME_NOW,
Int64 ( time
Double ( NSEC_PER_SEC ) ) ) ,
dispatch_get_main_queue ( ) )
strongShop?.string
// 打印输出
print ( ( strongShop?.string ) ! )
shop.myBlock! ( str:
" 哈喽,你好!" ) 不管在 OC 中还是 Swift 中我们都解决了 Block ( 闭包 ) 中如何优雅的解决循环引用问题,并且也了解了造成循环引用的内存表现形式。上面在 Swift 解决循环引用的问题,有更好的办法还请大神多多指教,如果有错误的地方帮忙纠正,非常感谢!
相关标签:
原网页已经由 ZAKER 转码排版
科技频道9小时前
科技频道4小时前
科技频道15小时前
科普中国网4小时前
科技频道9小时前
App精选5小时前
IT之家3小时前
威锋网1小时前
cnBeta40分钟前
IT之家3小时前
威锋网1小时前
腾讯科技28分钟前
腾讯科技刚刚
中关村在线1小时前
爱范儿1小时前

我要回帖

更多关于 javascript 闭包 的文章

 

随机推荐