首页 » iOS编程基础:Swift、Xcode和Cocoa入门指南 » iOS编程基础:Swift、Xcode和Cocoa入门指南全文在线阅读

《iOS编程基础:Swift、Xcode和Cocoa入门指南》11.10 延迟执行

关灯直达底部

你的代码会以响应某个事件执行;不过反过来,代码可能又会触发新的事件或事件链。有时,这会导致一些恶果:应用可能会崩溃,或是Cocoa可能不会按照你的要求去做。为了解决这个问题,有时需要暂时跳出Cocoa本身的事件链,在继续之前等待一切就绪。

这项技术叫作延迟执行。你告诉Cocoa去做某件事情,但不是现在,而是不久的将来,当一切就绪后再做。也许只需要非常短暂的延迟,甚至接近0秒钟,只是为了让Cocoa完成某些事情,如布局界面。从技术上来说,这是在继续执行代码前让当前的运行循环完成,并展开当前方法的整个调用栈。

在开发iOS应用时,使用延迟执行的频率可能会比你想象得要多。随着经验的不断累积,你会有一种感觉,知道何时应该使用延迟执行来解决问题。

在iOS编程中,使用延迟执行的主要方式是通过调用dispatch_after实现的。它接收一个块(一个函数),表示指定的时间过后会发生什么事情。不过调用dispatch_after有点复杂,特别是在Swift中,因为要进行大量的类型转换;因此,我编写了一个辅助函数,用于简化以及调用dispatch_after:


func delay(delay:Double, closure:->) {    dispatch_after(        dispatch_time(            DISPATCH_TIME_NOW,            Int64(delay * Double(NSEC_PER_SEC))        ),        dispatch_get_main_queue, closure)}  

该辅助函数非常重要,因此我将其粘贴到编写的每个应用的AppDelegate类文件顶部。这样使用起来就会方便很多!为了使用它,我需要调用delay并传递一个延迟时间(通常是个很短的时间,如0.1秒)和一个匿名函数,表示延迟过后要做什么事情。注意,该匿名函数中所要做的事情在不久的将来才会做;你会将自己的代码划分为一行一行的执行序列。这样,延迟执行就是其所在函数中的最后一个调用,并且不返回任何值。

如下示例来自于我所编写的一个应用,用户轻拍表中的一行,我的代码会通过创建并展示一个新的视图控制器进行响应:


override func tableView(tableView: UITableView,    didSelectRowAtIndexPath indexPath: NSIndexPath) {        let t = TracksViewController(            mediaItemCollection: self.albums[indexPath.row])        self.navigationController!.pushViewController(            t, animated: true)}  

但遗憾的是,对TracksViewController初始化器init(mediaItemCollection:)的调用需要一些时间,这样应用就会停下来,同时表中的这一行会高亮显示;虽然时间很短,但会让用户感到奇怪。为了通过某种动作来掩饰这种延迟,我在用户轻拍表中某一行时,让UITableViewCell子类显示一个旋转的活动指示器:


override func setSelected(selected: Bool, animated: Bool) {    if selected {        self.activityIndicator.startAnimating    } else {        self.activityIndicator.stopAnimating    }    super.setSelected(selected, animated: animated)}  

不过还有问题:这个旋转的活动指示器不会出现,也不会旋转。原因在于事件叠加到了一起。直到UITableView的委托方法tableView:didSelectRowAtIndexPath:完成时才会调用UITableViewCell的setSelected:animated:。不过,我们想要掩盖的延迟是在tableView:didSelectRowAtIndexPath:过程中的,整个问题就在于它完成的没那么快。

这时,延迟执行就派上用场了!我重写了tableView:didSelectRowAtIndexPath:,使之能够立刻完成,这样触发setSelected:animated:就会导致活动指示器立刻出现并开始旋转,稍后,我会使用延迟执行来调用init(mediaItemCollection:),就在界面恢复原状之时:


override func tableView(tableView: UITableView,    didSelectRowAtIndexPath indexPath: NSIndexPath) {        delay(0.1) { // let spinner start spinning            let t = TracksViewController(                mediaItemCollection: self.albums[indexPath.row])            self.navigationController!.pushViewController(                t, animated: true)        }}