iOS Apprentice中文版-從0開始學(xué)iOS開發(fā)-第四十課

回到LocationDetailsViewController.swift,當(dāng)用戶點(diǎn)擊done按鈕后關(guān)閉這個(gè)界面。

我們來做個(gè)新的挑戰(zhàn):不要立即關(guān)閉這個(gè)界面,如果HUD界面還沒有消失的話,界面就已經(jīng)被關(guān)閉了,那么會(huì)顯現(xiàn)非常蠢。

打開LocationDetailsViewController.swift,添加這一行:

import Dispatch

這是一個(gè)中央調(diào)度框架,Grand Central Dispatch,簡(jiǎn)寫為GCD。這是一個(gè)非常便利,但是比較底層的庫,用于處理異步任務(wù)。用這種異步的任務(wù)通知app等待幾秒鐘再合適不過了。

在done()方法的底部添加以下代碼:

let delayInSeconds = 0.6
DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds, execute:
{
  self.dismiss(animated: true, completion: nil)
})

這部分代碼的作用是在0.6秒后關(guān)閉Tag Location界面。

核心部分是DispatchQueue.main.asyncAfter()。這個(gè)函數(shù)的最后一個(gè)參數(shù)是一個(gè)閉包。在這個(gè)閉包內(nèi)部你通知視圖控制器把自己關(guān)閉掉。并且這個(gè)行為不是立即發(fā)生的。這就是閉包的特點(diǎn),函數(shù)內(nèi)的其他代碼都是逐行順序執(zhí)行的,而閉包內(nèi)的則不會(huì)。

DispatchQueue.main.asyncAfter()從.now() + delayInSeconds中得到延遲的時(shí)間,在時(shí)間到達(dá)后,執(zhí)行閉包內(nèi)的代碼。

??:我花了些時(shí)間來微調(diào)時(shí)間參數(shù),HUD的完全消失大概需要0.3秒,但是在HUD消失的同時(shí)立即關(guān)閉當(dāng)前界面,給用戶的感知也不好,所以經(jīng)過多次調(diào)整,我把延遲的時(shí)間確定為0.6秒。

運(yùn)行app,點(diǎn)擊Done按鈕,觀察一下效果,一切都感覺非常平滑。

不知道你感覺怎么樣,我的感覺是GCD這部分代碼把整個(gè)done方法弄的亂糟糟的。讓我們重新整理下代碼。

添加一個(gè)新的Swift文件,命名為Functions.swift。

把下面的內(nèi)容都放到新的文件中去:

import Foundation

import Dispatch

func afterDelay(_ seconds: Double, closure: @escaping () -> ()) {
    DispatchQueue.main.asyncAfter(deadline: .now() + seconds,execute: closure)
}

這些代碼和剛才done方法中的非常相似,只是被單獨(dú)放到了一個(gè)新的函數(shù)中,afterDelay()。這是一個(gè)自由函數(shù),不是對(duì)象中的方法,它可以在你的所有代碼中隨意使用。

我們來看看這個(gè)方法中的第二個(gè)參數(shù),名字叫做closure。它的類型是 () -> ()。看起來很奇怪,它的意思是,這個(gè)參數(shù)的值是一個(gè)沒有參數(shù)及返回值的閉包。完整的定義是像下面這個(gè)樣子:

(parameter list) -> return type

在我們的應(yīng)用中,參數(shù)和返回值都是空,記做()。也可以寫作Void -> Void,但是我比較喜歡前一種。

所以,無論何時(shí),你在參數(shù)中看到->這個(gè)符號(hào),你就知道這個(gè)參數(shù)是一個(gè)閉包。

afterDelay()將這個(gè)閉包傳遞給DispatchQueue.main.asyncAfter()。

@escaping的意思是,閉包中的代碼不要立即執(zhí)行,swift看到這個(gè)符號(hào)時(shí),就知道應(yīng)該將閉包中的代碼保存起來,在適當(dāng)?shù)臅r(shí)候執(zhí)行。

也許你想知道,為什么我們要費(fèi)這么大事,接下來我就為你展示這一點(diǎn)。

打開LocationDetailsViewController.swift,將done方法修改為:

@IBAction func done() {
        let hudView = HudView.hud(inView: navigationController!.view,animated: true)
        hudView.text = "Tagged"
        afterDelay(0.6, closure: {
            self.dismiss(animated: true, completion: nil)
        })
    }

這就是swift的魅力所在,只要稍微看一下,就基本可以明白代碼的意思。

通過將繁瑣的GCD代碼移到新的函數(shù)中,done方法的可讀性大大的增加了。(原文啰嗦了很多,大意就是這個(gè),本書的作者是我見過的寫技術(shù)文檔中最喜歡賣弄文采的一個(gè),堪稱代碼界的郭小四。)

注意一下,因?yàn)殛P(guān)閉界面的代碼位于閉包中,所以你必須使用self關(guān)鍵字,就像你看到的這樣:

afterDelay(0.6) {
  self.dismiss(animated: true, completion: nil)
}

看起來有點(diǎn)無法理解是嗎,閉包被放到參數(shù)的外面去了,Swift有個(gè)特性,如果一個(gè)函數(shù)或者方法的最后一個(gè)參數(shù)是個(gè)閉包,那么可以把這個(gè)閉包寫到參數(shù)外面,緊隨其后,就像上面那樣。

順便說一下,你可以把LocationDetailsViewController.swift文件中的import Dispatch這一句刪掉了。

運(yùn)行app,看看效果是否一致。

值類型和引用類型

Swift中的變量分為值類型和引用類型,它們的規(guī)則也是不同的。

從類中生成的對(duì)象都是引用類型的。其他的諸如:Int, Bool, Float and Double, String, Array,Dictionary以及enums都是值類型。

那么它們具體有些什么不同呢?

當(dāng)你聲明一個(gè)類型為Int或者CLLocationCoordinate2D的變量時(shí),它們就是值類型,編譯器會(huì)把它們的值存儲(chǔ)到一小塊內(nèi)存中保存下來。對(duì)于Int而言是8個(gè)字節(jié),CLLocationCoordinate2D是16個(gè)字節(jié)。

引用類型的變量在這一點(diǎn)上并無不同,比如你創(chuàng)建一個(gè)UILabel outlet,編譯器同樣是用8個(gè)字節(jié)存儲(chǔ)它,但是UILabel中可能存在多行文本,8個(gè)字節(jié)明顯不夠用。

當(dāng)你把值存儲(chǔ)到這兩種類型的變量中時(shí),它們的工作原理分別是這樣的:

值1337被直接存儲(chǔ)在變量number的內(nèi)存中,但是變量label的值是一個(gè)奇怪的數(shù)字。明顯它看起來和text label沒什么關(guān)系。這個(gè)數(shù)字并不是實(shí)際的UILabel對(duì)象,而是指向UILable對(duì)象的內(nèi)存地址。這就是引用類型。

內(nèi)存中的每一個(gè)字節(jié)都有獨(dú)一無二的地址,這里地址58741002引用UILabel對(duì)象在內(nèi)存中的第一個(gè)字節(jié)。

顯然,8個(gè)字節(jié)存儲(chǔ)label中的各種屬性是不夠的。

所以當(dāng)UILabel對(duì)象被創(chuàng)建時(shí),電腦會(huì)把它放到內(nèi)存中的某個(gè)地方,然后返回一個(gè)它的門牌號(hào)給你。

比如你的房子是一個(gè)實(shí)際的對(duì)象,而你的門牌號(hào)就是關(guān)于你的房子這個(gè)對(duì)象的引用,根據(jù)門牌號(hào)就可以找到你的房子。

我們來看幾組例子:

規(guī)則1:當(dāng)你創(chuàng)建一個(gè)常量后,你只能對(duì)它賦值一次:

let coord1 = CLLocationCoordinate2D(latitude: 0, longitude: 0)
coord1.latitude = 37.33233141  // 錯(cuò)誤
var coord2 = CLLocationCoordinate2D(latitude: 0, longitude: 0)
coord2.latitude = 37.33233141  //正確,因?yàn)檫@是一個(gè)變量“var”

但是引用類型常量的值似乎可以改變:

let label = UILabel()
label.text = "Hello, world!"  // OK
label.text = "I like change"  // OK

這是因?yàn)橐妙愋统A看鎯?chǔ)的是地址,而不是實(shí)際對(duì)象的值。但是你不能改變它存儲(chǔ)的地址,比如下面的語句是錯(cuò)誤的:

let label = UILabel()
let newLabel = UILabel()
label = newLabel //錯(cuò)誤

規(guī)則2:當(dāng)你把值類型的對(duì)象放到一個(gè)新的變量或者常量中去的話,它會(huì)產(chǎn)生一份拷貝,比如:

var a = [1, 2, 3]
let b = a
a.append(4)
print(a)    // prints [1, 2, 3, 4]
print(b)    // prints [1, 2, 3]

而引用類型則不會(huì)這樣,例如:

var firstLabel = UILabel()
firstLabel.text = "Programmers are the best!"
var secondLabel = firstLabel
secondLabel.text = "I like ice cream"
print(firstLabel.text)   // prints “I like ice cream”

為什么secondLabel的文本變更了之后,firstLabel的會(huì)跟著一起變呢?因?yàn)樗鼈兇鎯?chǔ)的并不是lable對(duì)象本身,而是到這個(gè)label的引用,見下圖:

記住,只有從類中創(chuàng)建的對(duì)象是引用類型,其余的都是值類型。

object ownership(對(duì)象所有權(quán))

對(duì)象不是隱士,不會(huì)把自己關(guān)到山上不下來(看完全書我都不知道這句話有啥作用)。你的app中到處充斥著對(duì)象,它們互相間合作緊密。

你的app中的對(duì)象之間的關(guān)系可以用對(duì)象圖表來描述。例如:CurrentLocationViewController和許多對(duì)象有關(guān)聯(lián):

這些是它的實(shí)例變量以及實(shí)例常量,CurrentLocationViewController擁有它們。同時(shí)CurrentLocationViewController也可以被別的對(duì)象擁有,它屬于UITabBarController,而UITabBarController屬于UIWindows。

這只是你app中對(duì)象間關(guān)系的一小部分。不要把這個(gè)概念和類的層級(jí)弄混了。

對(duì)象所有權(quán)是iOS編程中的一個(gè)重要課題。你需要清楚地了解哪些對(duì)象擁有哪些對(duì)象,因?yàn)檫@些對(duì)象的存在取決于它(對(duì)象所有權(quán)) - 你的app的正常運(yùn)行也取決于它。

如果一個(gè)對(duì)象不在有任何其他對(duì)象擁有它,那么這個(gè)對(duì)象會(huì)被釋放掉。另一方面,如果一個(gè)對(duì)象的擁有者太多,可能會(huì)導(dǎo)致它永遠(yuǎn)無法被釋放,最終消耗掉大量的系統(tǒng)內(nèi)存。

所以“擁有”一個(gè)對(duì)象到底是什么意思呢?我們這里引入一個(gè)概念,strong和weak。

大多數(shù)情況下,當(dāng)你聲明一個(gè)擁有引用對(duì)象的變量時(shí),你就創(chuàng)建了一個(gè)strong關(guān)系。這個(gè)變量就假定擁有這個(gè)對(duì)象,它和其他同樣引用這個(gè)對(duì)象的變量一起對(duì)這個(gè)變量負(fù)責(zé)。

var image: UIImage            // a strong variable

在weak關(guān)系中,不存在擁有者的概念。一個(gè)變量和一個(gè)對(duì)象之間的關(guān)系是weak的話,無法保證這個(gè)對(duì)象的存活。在聲明weak關(guān)系時(shí),你需要寫明weak關(guān)鍵字:

weak var weakImage: UIImage?  // a weak variable

一個(gè)對(duì)象的生命期取決于它有多少個(gè)strong關(guān)系的屬主,而不是weak類型的。

那么weak類型到底有什么用呢?

它的存在主要是用于避免循環(huán)引用,就是兩個(gè)對(duì)象互為屬主,它們就永遠(yuǎn)都不會(huì)被釋放了。直到內(nèi)存被耗盡。

在之前的課程中你學(xué)習(xí)了,將委托方法聲明為weak類型的,來避免這種事的發(fā)生。過會(huì)你會(huì)看到另一種類型的循環(huán)引用。

另一種常見的使用weak的地方是在聲明@IBOutlet屬性時(shí)。這是因?yàn)橐晥D控制器并不是@IBOutlet的真正屬主,它的屬主其實(shí)是視圖控制器的頂層視圖。

如果你使用strong類型的outlet,不會(huì)有任何不良影響,但是假如你使用了weak,那么視圖控制器就會(huì)明白它僅僅是租用了這些屬性,而不是擁有它們。

weak引用有一個(gè)副作用,假如你對(duì)某個(gè)對(duì)象是weak引用,那么當(dāng)這個(gè)對(duì)象被釋放時(shí),你的引用計(jì)數(shù)器會(huì)自動(dòng)為nil。

這也是一件好事,因?yàn)槿绻贿@樣做,你會(huì)指向一個(gè)死去的對(duì)象,如果你試圖使用它,各種各樣的混亂將會(huì)發(fā)生。 (使用這種“僵尸”對(duì)象是沒有弱引用的語言程序崩潰的常見原因。)

這也是為什么weak引用變量必須始終是可選項(xiàng)(無論是?還是!),因?yàn)樗鼈兛赡茉谀硞€(gè)時(shí)刻成為nil。

??:Swift有第三種關(guān)系類型:無主的。 它和weak很像,用來打破循環(huán)。 不同的是,無主變量不會(huì)為nil,因此不必是可選型。 它沒有weak的功能強(qiáng)大,但是你可能在這里或那里碰到無主型變量。 你需要把它們都當(dāng)作是weak型就可以了。

總結(jié)一下:如果你想保持一段時(shí)間的對(duì)象,你應(yīng)該保存一個(gè)strong關(guān)系。 但是,如果你不需要擁有這個(gè)對(duì)象,并且不介意它在某個(gè)時(shí)候離開,那就使用一個(gè)weak或者無主的引用。

讓我們來看看一個(gè)對(duì)象有多個(gè)所有者的情況。

例如,當(dāng)用戶點(diǎn)擊Tag Location按鈕時(shí),Current Location視圖控制器將CLPlacemark對(duì)象傳遞給Location Details視圖控制器。 這兩個(gè)引用都是strong型的,所以從現(xiàn)在開始,Location Details視圖控制器將承擔(dān)共享所有權(quán)。 所以現(xiàn)在CLPlacemark有兩個(gè)所有者。

共享所有權(quán)和對(duì)象關(guān)系的概念僅適用于引用類型。 值類型總是從一個(gè)地方復(fù)制到另一個(gè)地方,所以他們永遠(yuǎn)不會(huì)擁有多個(gè)所有者。

結(jié)構(gòu)也是值類型,比如:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) { ...
  controller.coordinate = location!.coordinate
  controller.placemark = placemark
}

這行代碼把location!.coordinate放入controller.coordinate時(shí)會(huì)生成一份CLLocationCoordinate2D結(jié)構(gòu)的拷貝。每個(gè)視圖控制器都會(huì)擁有各自獨(dú)立的一份關(guān)于GPS坐標(biāo)的拷貝。

對(duì)于placemark而言,則是每個(gè)視圖控制器都有一個(gè)到CLPlacemark對(duì)象的引用。

如果你感到頭暈的話,可以停下來,過幾天再看一遍。

說真的,如果你理解了這些概念,你就可以很好地編程。 如果不是,不要放棄。 這些可能是非常難以理解。 但是只要繼續(xù)下去,一定會(huì)搞懂。

??:如果你想復(fù)制引用類型的對(duì)象,則可以將其聲明為@NSCopying。 這會(huì)創(chuàng)建對(duì)象的副本,并將其放入新變量中。 之后這兩個(gè)變量都指向它們自己的對(duì)象。 @NSCopying更像是Objective-C風(fēng)格的東西,但是你應(yīng)該了解它一下。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容