iOS新手入門 — 變數三部曲(2/3) —Swift 中的變數!

威爾哥(Will, Tsai)
蔡胖想學做生意
16 min readAug 19, 2018

諸君!

好久不見!

沒錯,我就是

人帥心善打字快, 寫code debug樣樣來 的 威爾哥!

歡迎來到威爾哥的iOS小教室。

今天的文章主題是,變數三部曲的第二集,Swift的屬性與變數!

在今天的文章中,我們將涵蓋以下幾個部分,

謎之音:(誒,等等,請問平常前面那一堆廢話呢?怎麼不見了?是不是沒梗了啊,是不是啊是不是啊!)

咳恩,其實呢,學習新知識應該是很隆重,很莊嚴的一件事情。

因為每次我們學得新知識之後,很有可能從此改變我們的生活,帶領我們走上成功的階梯,迎來人生的新高峰。

所以在這麼莊嚴的氣氛下,我們實在不應該每次都嘻嘻哈哈,應該要認真對待!大家說是不是!!

謎之音:(靠,我覺得你只是沒梗而已啊)

噓🤫,小聲點…傳出去了多不好意思。

謎之音:(好吧…那你繼續。)

好的,那麼這篇文章將分為以下幾個階段進行。

  1. 宣告變數的方法
  2. Swfit變數成員介紹,Global變數,屬性與區域變數
  3. 平平是變數,作用大不同。Stored variable與Computed Variable
  4. 擁有超級間諜椅的Setter Observer,24小時幫你提供抓耙子的服務!
  5. 哥一出生就已經準備好了!超屌的Computed Initializer
  6. 實作影片
  7. 結論

好,那麼變數三部曲的第二部,我們正式開始!

GO~~~~~~~~~~~~~~

沒問題~~~

宣告變數的方法

在Swift中宣告變數的方法有兩種,

1. 使用var 關鍵字宣告:『使用Var關鍵字宣告的變數稱為變數』,變數所保存的值是可以重複修改的。

2. 使用let 關鍵字宣告:『使用let關鍵字宣告的變數稱為常數』,常數保存的值經過第一次設定之後便不能再更改。

分別使用var與let宣告變數,

如上圖,我們分別使用let與var來宣告一個常數SEX與一個常數name

如果把name的值變成Jakcy,可以順利完成。

但是把SEX的值改成Female就不行了。

想改變let 變數的值是不允許的,會收到警告

如上圖,如果我們改變SEX的值,就會收到XCode的警告。

警告的翻譯是:無法指定新的值到此變數,let是常數,不能改變儲存的值。

Global變數,屬性,與區域變數

接下來,在Swift中,根據宣告的地方不同,我們的變數會有不同的生命週期以及可視範圍,而這三種不同的變數名稱也不一樣,讓我們來看一下吧,Check It Out, yo!

1. Global變數

在Swift中Scope最大的變數就屬Global變數了。

Global變數通常宣告在.swift檔案中的top level,也就是跟型別宣告是相同Level的位置。如下圖

第13行,就是global變數,宣告在import之後,與type declaration 同階層

Top Level指的就是在import Foundation與 class JustAnewFile中間(第十行到第十五行),宣告在這邊的變數就是Global變數。

Global變數的Scope(可視範圍)是整個Module(module是什麼?這有點複雜,總之就先想成整個專案就可以了)。

Global變數的特性就是在同個專案下,每個檔案都可以存取。如下圖

我們在另外一個class, AnotherNewFile之中,也能夠存取上圖JustANewFile中定義的變數APPLICATION_NAME

我們在上上一張圖,也就是JustAnewFile那個檔案中宣告APPLICATION_NAME這個Global變數,在AnotherNewFile這個檔案中也可以存取得到。

Global變數的好處是,我們可以將一些常常需要跨檔案存取的資料宣告成Global變數,這樣我們就可以想用就用,愛怎麼用就怎麼用,真的很舒服!

最常見的例子就是用來宣告一些到處用得到的常數字串。

例如上圖中,

26行我們定義一個方法,作用是將產品的價格乘上會員折扣之後返回最後的售價。

在27行中,我們假設會員的折扣是0.8就是打八折,所以在這邊打上0.8。

接下來在另外一個Class中,我們定義了一個一模一樣的方法,

但是這次我們一時手誤,把折扣打成0.3。

因為我們預期相同的方法應該要回傳相同的結果,但是卻事與願違。

所以我們的程式因此產生了Bug。

像這種情況之下,

如果我們宣告一個global variable 就可以避免這種情況發生。

如下圖,

第15行我們宣告一個Global Variable, MEMBER_DISCOUNT = 0.8

在27行中把原本的0.8換成MEMBER_DISCOUNT

另一個Class中把原本打錯的0.3也改成MEMBER_DISCOUNT

如此一來這個問題就徹底解決啦!

灰腸的好

但是事實上,除了宣告常數之外,Global 變數是要盡量避免使用的。

因為過度使用Global 變數會產生許多Bug,而且非常難以除錯。

舉例來說:

有一個箱子放在家裡,只有我們家的人可以用,萬一有人放錯東西或者把裡面的東西通通拿走了。那我們只要找一下家裡面誰有動過這個箱子就可以找出兇手是誰。

但是如果這個箱子是放在西門町捷運站六號出口,每個經過的路人都可以使用這個箱子,那萬一箱子被弄壞了,我們根本不知道怎麼找出誰是兇手!

所以實務上,除了把常數宣告成Global,其他時候要盡量避免使用global 變數。

2.屬性,Property

好的,看完Global 變數之後,讓我們接下來看第二種變數,我們稱為Property.

Property通常宣告在型別宣告與任何function 之間。

例如上圖中,

在class JustAnewFile的左右大括號之內,方法之外的地方宣告的變數稱為Property。

第23行,宣告了一個變數property,名字叫 memberLevel。

第25行,宣告了另一個常數property,名字叫memberID。

Property擁有第二大的Scope,只要是在這個型別宣告裡面,到處都可以存取這個變數

如上圖,在class JustAnewFile中的每個地方都可以存取 memberID以及memberLevel兩個變數。

3.Local變數

Local變數通常宣告在function, closure , if判斷式之中

Local變數的Scope最小,生命週期最短,只要離開宣告該Local變數的範圍,這個變數就會被消滅,無法再使用。

如上圖,我們在34行宣告了一個區域變數,discountPrice。

在41行我們想要印出disountPrice,但是變數下面出現了紅色底線,並且我們還因此得到了一個錯誤訊息

中文翻譯是:使用未定義的變數

這個錯誤訊息代表XCode不認識這個變數所以無法使用。

因為這個變數只能活在itemPriceOfMember這個方法裡面,也就是第34~36行。出了這個範圍,這個變數就會自動被消滅了。

雖然Local變數生命週期短,可視範圍小,但是相對的,local變數是最安全的變數,產生Bug最少,也最好debug。

Stored Property 與 Computed Property

property根據其用途又分為Stored Property與Computed Property。

接下來讓我們來看一下到底哪邊不一樣吧!

保管資料的好幫手,Stored Property

Stored Property,顧名思義就是用來儲存資料的Property。

舉例來說,上圖中我們宣告了兩個變數分別儲存了兩個字串。

SEX與name雖然一個是常數,一個是變數。但是他們的作用都是儲存值。

所以SEX與name都是Stored Property。

每次都是新鮮現作,Computed Property

Computed Property主要的任務不是儲存值,而是每次被使用的時候,透過執行一些『運算』後,返回運算後的結果。

第十二行執行結果

例如上圖,我們有一個Stored property是seconds,秒數。

另一個是Computed Property叫做secondToHour,

當我們在第十二行,存取secondToHour這個變數的時候,secondToHour後面那對大括號中間的程式碼就會被執行。(第三行最後面開始,第八行結束)

也因為Computed Property的值每次都會重新計算,

所以computed Property只能是var 變數。

什麼?不知道哪時候會到Computed Property?

好的,舉例來說

我們有一個變數叫做薪水,負責儲存每個月上班賺到的薪資。

還有許多其他的變數,例如,房租,交通費,伙食費,娛樂費…等等。

如果我們想要知道這個月扣掉所有支出的話,我們還剩下多少零用錢。

那麼,一般來說我們要手動進行計算,如下圖

但是因為交通費,伙食費,娛樂費等等,這些費用每個月可能都不一樣,

所以每次我們要知道剩下多少零用錢的時候,我們都要重新計算一次(重複 12~15行)。

這真的是太麻煩了。

為了解決這種問題,我們可以宣告一個computed variable叫做零用錢(pocketMoney)。然後把計算過程放到getter中,如下圖

每次當我們使用pocketMoeny這個變數的時候,在getter中,便會執行計算過程,最後把計算結果回傳出來。

如此一來我們不需要重複進行手動計算也可以得到最新的零用錢數字。

是不是很方便啊!

最後,有些小夥伴可能會問,

那麼,Computed Property有沒有Setter呢?

答案是,可有可無,預設是沒有。

因為每次Computed Property的值都是重新計算得來的,所以並不需要儲存的功能。

那如果有setter的話呢?

如果Computed Property有setter的話是怎麼運作呢?

因為Computed Property本身不儲存值,所以如果Computed Property有Setter的話,通常是把值存在別的變數中。

上圖中,我們實作了pocketMoney的setter,在setter中我們把收到的值存在salary中。

如果我們在這邊把setter的值指定給自己,也就是pocketMoney的話,

pocketMoney的setter將收到的值放在pocketMoney中
這一行代碼總共執行了15663次之後就崩潰了

如上圖,這個setter就會進入無窮迴圈,一直重複執行沒有停止的一天,直到程式崩潰,所以使用上要小心。

最後補充,因為Computed Property預設沒有setter,所以在實作的時候我們可以省略getter以及左右大括號,如下圖。

這樣寫也是可以的唷!

但是如果有實作setter的話,那getter就不能省略,一定要寫出來了。

Property Observer

接下來,讓我們來看看,Property Observer這個東西。

不過在這之前,讓我們先瞭解一下什麼是Observer!

所謂的Observer中文翻譯成觀察者,簡單的說就是一個一直盯著目標看的人。在人類的社會中一般翻譯做癡漢,或者偷窺狂!

如果是專業的observer還會自備一副超級間諜椅呢!(唉唷,梗要來了喔)

總之,Observer的任務就是死命地盯著目標一直看,只要有風吹草動,就會馬上回報給我們知道。

所以我們可以知道Property Observer就是屬性的觀察者,任務就是盯著property不放,當property的值有任何改變的時候就會通知我們。

一般來說,當我們宣告Property Observer的時候,就已經宣告好willSet與didSet這兩個方法,只是預設這兩個方法不做任何事情。

所以透過在Stored Property中複寫willSet以及didSet兩個方法,每次只要有新的值要儲存到我們監視的變數裡面,這個observer就會回報給我們知道,讓我們可以採取一些動作。

接下來讓我們看一下Observer回報的兩個時機點吧!

實作salary變數的getter與setter

上圖中我們宣告了一個變數salary,初始值是22000,並且複寫salary的willSet與didSet方法。宣告完畢的時候,我們在第16行給予salary一個新的值30000。

第一階段:willSet

在這個階段,表示有一個新的值即將要放入這個變數之中,取代原本的值。

在這個方法中,預設會有一個newValue可以用。

oldValue代表新收到的值也就是30000,此時salray本身儲存的值尚未改變依然是22000。

第二階段:didSet

正所謂生米煮成熟飯,回頭太難,曾經滄海難為水。(廢話也太多)

didSet這個階段代表willSet中的newValue已經取代掉oldValue了。

所以在這個階段有oldValue可以使用,oldValue代表已經被取代掉的值(就是22000),而目前變數裡面儲存的是新的值(也就是willSet中的newValue 30000)。

上圖的執行結果如下

最後…你說你整段都懶得看就在等超級間諜椅這個梗,但是為什麼整段都講完了還沒看到這個梗呢?

哎呀,那個不重要麻~。

什麼?我既然提了就要負責任?

哎呀,真拿你們沒辦法,連結在下面,自己看吧!嘻嘻。

哥一出生就已經準備好!超屌的Computed Initializer

關於此章節最後一個要提到的就是,Computed initializer!

這個名字一聽感覺就很膩害。Computed什麼的感覺就是屌。

所以說這個東西到底是什麼呢?

簡單地講,computed initializer就是可以透過執行許多行程式碼來初始化變數。

舉例來說:

我們有一個變數叫做零用錢pocketMoney,裡面儲存著我們的薪水扣掉所有支出之後剩下的金額。

當我們要初始化這個變數的時候,我們要先拿出我們的月薪,然後扣除所有的開支,最後再把餘額放入這個變數。

因此零用錢這個變數在初始化的時候需要進行許多步驟。

這種情況就是使用Computed Initializer的好時機!

確切的使用如下

在pocketMoeny這個變數宣告的等號右邊原本是給定一個值,但是computed initializer是給予一個匿名方法,寫成 “{}()”,要執行的程式碼放在左右大括號{}中間。

上圖中第三行的最後到第十行這個區塊就是一個匿名方法。

在此匿名方法中我們可以執行一連串的動作來初始化我們的變數。

例如我們在上圖的第十二行與第十五行存取了pocketMoney,執行的結果如下

從印出來的執行結果可以知道,當我們使用零用錢這個變數的時候,裡面儲存的值就是已經扣除必要支出之後剩下的數字了。是不是很方便啊!

但是有些人或許會覺得疑惑,就是Computed Initializer跟computed variable有什麼不同呢?

答案是,

Computed Variable『每次』被使用的時候,computed variable的getter都會被執行一次。

但是Computed Intializer裡面的程式碼,只會在這個變數第一次被使用的時候執行一次。一但初始化結束之後,這些程式碼將不會再被執行。初始化執行結束之後,這個變數就只是一般的Stored Property而已。

所以在上圖中,我們呼叫兩次pocketMoeny,但是第8行的訊息只有被印出來一次。

所以結論就是Computed variable與Computed initializer是不一樣的。

實作影片

那按照慣例,了解了原理之後,最重要的還是要來看一下到底要怎麼實作這些觀念,你說對吧!

所以~~~

導播,上影片~~~~~~~~~

結論

在這篇文章中,我們提到根據Scope的不同,Swift有三種變數。而每種變數依照功能又分為儲存型變數與運算型變數。然後變數中又有屬性監視器(Setter Observer)可以幫我們監控變數中儲存的值的改變。最後,Swift還提供了可以執行多個步驟的Computed Initializer給我們使用。

正確使用變數不管在任何程式語言中,都是最重要的一環。所以各位小夥伴們,一定要把變數弄得死去活來…阿不對,是把變數用得出神入化唷!

好,那麼這篇文章就到這邊啦!

正所謂,

持續關注威爾哥,學習iOS笑呵呵。

威爾哥的iOS小教室,我們下次見!

️千山萬水總是情,按個「拍手」行不行。如果你覺得我的文章還不錯,可以「Follow」我,然後順手按個拍手鼓勵鼓勵我!按住👏 1秒 ,神清氣爽。
按住👏 5秒 ,通體舒暢。
按住👏 10秒,說吧,要吃什麼,今晚宵夜我請。
謝謝大家!

--

--

威爾哥(Will, Tsai)
蔡胖想學做生意

作者自稱威爾哥,是個想養貓但是自己都需要人養,漂流在墨爾本的程式設計師,曾經碰過前端開發,後端開發,android開發,現在主要研究iOS開發,興趣是寫寫廢文跟大家分享。