Swift中的Weak Strong Dance

亲爱的博客园的关注着博主文章的朋友们告诉你们一个很不幸的消息哦,

这篇文章将会是博主在博客园发表的最后一篇文章咯,

因为之后的文章博主只会发布到这里哦 http://daiweilai.github.io/

新博客排版布局更好,阅读体验更佳,欢迎吐槽、留言、订阅哟

马上又要过年了,诶,再也不能像当初那样无耻地逗利是了(我们广东的方言讨红包的意思)

图1

图2

看来今年没利了

谁让哥已经工作了呢。

公司今年的开发任务算是完结了,苹果又极不负(hǎo)责(yàng)任(de)地放圣诞不审核了,所以这半个月就该清闲下来了。

博主掐指一算,Swift已经养到2.1了,并且也开源了,这样看来Swift也够肥了,语法也绝逼不会再有大改动了,是该再次抓起来了。

为什么说再呢,其实在当初Swift Beta版本的时候,我们项目经理尝试了一下palyground后,一拍手,棒棒哒,”我们用Swift开发接下来的项目吧”,然后就是Swift1.0 … 2.0,每一次升级,每一次语法更迭,每一次XCode打开的那一刻都是满江红,那触目惊心的场面无数次让博主受尽折磨。最后庆幸的是,这个项目死了,哦耶!

好吧,重新捡起Swift吧,今天的这篇文章是一篇激情翻译大作,而且本人的英文水平获得了原作者充分的肯定:

图3

看到了吧,毫无障碍地交流!!我们愉快地聊了很久~~ ,激情四射,biu biu ~

好吧回到博文的主题中来,这次我们说说“Weak/Strong Dance”

在block中解决循环引用要追寻的2011 WWDC Session #322 当时惊艳的代码是这样的:

- (void)dealloc
{
  [[NSNotificationCenter defaultCenter] removeObserver:_observer];
}

- (void)loadView
{
  [super loadView];

  __weak TestViewController *wself = self;
  _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
                                                                object:nil
                                                                 queue:nil
                                                            usingBlock:^(NSNotification *note) {
      TestViewController *sself = wself;
      [sself dismissModalViewControllerAnimated:YES];
  }];
}

或者可以看看AFNetWorking是这样用block的:

__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};

我们都很熟悉Objective-C中的“weak/strong dance”,但是寂寞无聊的我突然就很想知道Swift语言中该怎么做呢?是否存在传说中的最佳实践呢?

好啦,翻译开始!

原文

首先,我们祭出一个在闭包中没有使用weak的引用导致的循环引用的例子

class C {
    let name: String
    var block: (() -> Void)?

    init(name: String) {
        self.name = name
        block = {
            self.doSomething()
        }
    }
    deinit { print("Destroying \(name)") }
    func doSomething() { print("Doing something for \(name)") }
}

var c = C(name: "one")
print(c.name)
c = C(name: "two")
print(c.name)

输出

one
two

这是一个巨基础又明显的循环引用的例子,self -> block ->self

所以,deinit 方法是绝逼不会被执行的,即使你把 c 重新指向nil或者其他的实例,c也不会被销毁,这就是顽固又调皮的循环引用了,尤其是当你把c指向nil之后,这个对象你就再也引用不了了,它就静静的躺在堆内存里面,遗世而独立,然后你就堆内存泄露了,然后你就淡淡的忧伤从下体传来 ~ ~没有然后了

其实Swift中闭包的参数列表(Capture List) 已经能够很好的让你获取一个weak self来避免循环引用了,但这还达不到我们的要求,只有weak是构不成“weak/strong dance”滴。

使用闭包参数列表

class C {
    let name: String
    var block: (() -> Void)?

    init(name: String) {
        self.name = name
        block = { [weak self] in  // <-- 这里做出一些改变
            self?.doSomething()
        }
    }
    deinit { print("Destroying \(name)") }
    func doSomething() { print("Doing something for \(name)") }
}

var c = C(name: "one")
print(c.name)
c = C(name: "two")
print(c.name)

输出

one
Destroying one
two

这样就没有循环引用啦~

在闭包中使用self

使用 [weak self]有一个细节,就是self在闭包中会变成Optional 从上面的代码中self?.doSomething() 就可以看出来了。

但是如果你在这个闭包中狂轰乱炸的使用self? (多次使用self?),问题就来了,因为这个self?是一个弱引用的,那么你没法确定在这个闭包中所有的self?操作都能执行完毕,毕竟若引用的self可能随时都挂掉,然后怒举一个栗子:

图4

class C {
    deinit { println("Destroying C") }
    func log(msg: String) { println(msg) }
    func doClosure() {
        dispatch_async(dispatch_get_global_queue(0, 0)) { [weak self] in
            self?.log("before sleep")
            usleep(500)
            self?.log("after sleep")
        }
    }
}

var c: C? = C()  // Optional, so we can set it to nil
c?.doClosure()

dispatch_async(dispatch_get_global_queue(0, 0)) {
    usleep(100)
    c = nil  // This will dealloc c
}

dispatch_main()

输出

before sleep
Destroying C

小提示:当然一般来说在dispatch_async()中你不必担心会有循环引用,因为self并不会持有dispatch_async()的block,所以上述的代码中并不会真的导致循环引用,如果你的闭包并不是很注重结果的,那么selfnil闭包就不会再执行,这个还是挺有用的。

上述的代码中不会打印after sleep,因为self?在打印这句话之前已经挂掉了。

通常这种无根之源的bug可以把你整的半死。所以通常遇到这种闭包中多次试用self?的操作的时候,一般会把self?变为又粗又壮的strong self,(博主也是又粗又壮的,捂脸~~)这就是传说中的“weak/strong dance”,这个舞蹈,额,什么鬼,为什么把这个技术叫做dance啊,我觉得叫做美队解禁奥义技还不错,妇联里面的美国队长也是由weak变成strong的嘛~,好吧,扯太远,菊花都扯疼了,我们这是在技术翻译呢!要严肃!要尊重原作者!我们还是叫dance吧,有了这个dance之后呢,我们就能确保一旦闭包被执行,self就不会为nil

但是,就像文章开头说的,对于在Swift的“weak/strong dance”中变回strong的这部分的最佳实践是什么我也不是很确定的。。。

获取强引用的一些想法

使用可选绑定if let

func doClosure() {
    dispatch_async(dispatch_get_global_queue(0, 0)) { [weak self] in
        if let strongSelf = self {  // <-- 这里就是精髓了
            strongSelf.log("before sleep")
            usleep(500)
            strongSelf.log("after sleep")
        }
    }
}

// or in Swift 2, using `guard let`:
dispatch_async(dispatch_get_global_queue(0, 0)) { [weak self] in
    guard let strongSelf = self else { return }  // <-- 这里就是精髓了
    strongSelf.log("before sleep")
    usleep(500)
    strongSelf.log("after sleep")
}

输出

before sleep
after sleep
Destroying C

优点:

  • 很明显的看出整个操作的流程
  • 在闭包中拿到了非可选的本地变量

缺点:

  • 很不幸的是我们不能if let self = self,因为self是常量,不可变,这样的话我们就只能if let strongSelf = self 在闭包的作用域中都要使用丑陋的strongSelf了。
  • 在swift的闭包中,如果你没有试用strongSelf而是使用了self,这样编译器会警告!因为这个时候self是可选的嘛,相比较OC中,就不会警告了。(这句话哥读了21遍,为什么觉得这个不是缺点呢)

使用withExtendedLifetime

在Swift的标准库中有一个函数:withExtendedLifetime(),感觉就像Apple这个金鱼佬故意诱导我们使用这个函数来实现“weak/strong dance”。

/// Evaluate `f()` and return its result, ensuring that `x` is not
/// destroyed before f returns.
func withExtendedLifetime<T, Result>(x: T, @noescape _ f: () -> Result) -> Result

那就试试

func doClosure() {
    dispatch_async(dispatch_get_global_queue(0, 0)) { [weak self] in
        withExtendedLifetime(self) {
            self!.log("before sleep")
            usleep(500)
            self!.log("after sleep")
        }
    }
}

优点:

  • 闭包中不再需要使用丑陋的strongSelf

缺点:

  • self还是他妈可选的,调用方法什么的还是要!?,还是要解包,博主突然想起自己的一个技能:单手解,呵呵

自定义一个withExtendedLifetime()

这个方法是 @jtbandes 这哥们想的,大概会是这样:

 extension Optional {
    func withExtendedLifetime(body: Wrapped -> Void) {
        if let strongSelf = self {
            body(strongSelf)
        }
    }
}

// Then:
func doClosure() {
    dispatch_async(dispatch_get_global_queue(0, 0)) { [weak self] () -> Void in
        self.withExtendedLifetime {
            $0.log("before sleep")
            usleep(500)
            $0.log("after sleep")
        }
        return
    }
}

优点:

  • Follows naming conventions set by the standard library.(原文) 感觉没优点~

缺点:

  • strongSelf变成了使用$0 ,博主认为哦,还是很丑陋,并且可读性更差了
  • In this case, I had to add some extra type info to the dispatch_async() closure. I’m not totally sure why. 不知道他说什么鬼~

翻译至此结束了

后记

关于Swift中 “Weak/Strong Dance”,中的Weak部分,大家可以参阅喵大的这篇文章 内存管理,weakunowned

用回了一个多礼拜的Swift真是感受颇多,虽然Xcode在写Swift就像纯文本编辑器一样,但是我还是想说一句:Swift真™安全!想crash都难咯。

还有 Happy Christmas!

收笔,走人。

时间: 2024-10-09 12:59:51

Swift中的Weak Strong Dance的相关文章

iOS开发 -------- Block技术中的weak - strong

一 Block是什么? 我们使用^运算符来声明一个Block变量,而且在声明完一个Block变量后要像声明普通变量一样,后面要加; 声明Block变量 int (^block)(int) = NULL; Block变量的语法 数据返回值类型 (^变量名)(参数列表) = NULL 赋值Block变量 block = ^(int m) { return m * m; }; 使用Block变量 // 通过使用block变量,计算整型常量10的平方,并且打印在控制器输出 NSLog(@"10的平方是:

初识Swift中的值和引用,循坏引用、代理的注意点

1.0 在Swift中分有值类型和引用类型 Int .String . 结构体和枚举都属于值类型, 将值类型传递给方法是,将在内存中创建其副本,并传递这个副本:这样我们就可以随心所欲修改它,而不用担心这会修改传入的原始值. 传递引用类型时不会复制它,而将其地址提供给可能使用他们的函数或方法.闭包以及从类实例化得到的对象都属于引用类型.将闭包或者对象传递给方法时,不会创建其副本,而是传递引用(内存地址).由于传递引用类型时不会创建其副本,因此需要特别小心,确保在正确的时间妥善地释放它们,过早的释放

assign, retain, weak, strong, copy,unsafe_unretain

readonly, readwrite:是控制属性的访问权限,readonly只生成getter方法,其他类是无法修改其值的.readwrite是会同时生成getter和setter方法,其他类可以修改其值. assign, retain, weak, strong, copy,unsafe_unretained:在non-ARC中,assign和retain是一组,assign的对象属性引用计数不变,而retain会被+1.对应的在ARC中,weak和strong是一组,weak的对象属性引用

Swift 中的基础语法(二)

1.Swift 中的函数 /// 函数的定义 /// /// - Parameters: /// - x: 形参 /// - y: 形参 /// - Returns: 返回值 func sum(x: Int, y: Int) -> Int { return x + y } print(sum(x: 10, y: 20))   /* 外部参数就是在形参前面加了一个字 外部参数不会影响函数内部的细节 外部参数会让外部调用看起来更加直观 外部参数如果使用了'_',在外部调用函数时,会忽略形参的名字 &qu

swift中代理的使用

下面以自定义的UITableViewCell的代理为例,记录一下swift中代理的使用 controller中的代码如 1 // 2 // ViewController.swift 3 // simpleDemo 4 // 5 // Created by liubo on 16/7/25. 6 // Copyright © 2016年 liubo. All rights reserved. 7 // 8 9 import UIKit 10 11 class ViewController: UIV

NSString NSMutableString copy mutableCopy retain weak strong

NSString 与 NSMutableString NSString是不可变字符串对象,这句话的意思,结合代码: #import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { NSString *str = @"Shaw"; NSString *str1 = @"Root"; // NSString *str1的意思是str1

浅谈swift中的内存管理

Swift使用自动引用计数(ARC(Automatic Reference Count))来管理应用程序的内存使用.这表示内存管理已经是Swift的一部分,在大多数情况下,你并不需要考虑内存的管理.当实例并不再被需要时,ARC会自动释放这些实例所使用的内存. 内存管理:针对的是实例的内存占用的管理(放在堆里面) 实例:1:由class类型构建的实例,2:闭包对象 下面我们来写一个实例来证明一下 class Person { var name: String init(name: String )

Swift 中的利刃,函数和闭包

input[type="date"].form-control,.input-group-sm>input[type="date"].input-group-addon,.input-group-sm>.input-group-btn>input[type="date"].btn,input[type="time"].input-sm,.form-horizontal .form-group-sm input

Swift中KVO(监听)的使用方法及注意事项

---恢复内容开始--- 相信研究swift语言的开发者都多多少少了解或者精通Objective—C语言,熟练掌握Objective—C语言的开发者,在学习swift语言的过程中,是比较快速,而又轻松的.本人就是一位熟练掌握OC语言,后开始研究的swift.在学习swift语言的过程中,笔者建议有OC基础的开发者,在写swift的代码过程中,再写一下OC中的代码,二者相互比较,相信你能找到快速学会swift语言的方法.资深,有耐心和有天赋的开发者,相信能在一周左右,能够运用swift开发项目.其