【Swift學習】Swift編程之旅---ARC(二十)

  Swift使用自動引用計數(ARC)來跟蹤并管理應用使用的內存。大部分情況下,這意味著在Swift語言中,內存管理"仍然工作",不需要自己去考慮內存管理的事情。當實例不再被使用時,ARC會自動釋放這些類的實例所占用的內存。然而,在少數情況下,為了自動的管理內存空間,ARC需要了解關于你的代碼片段之間關系的更多信息。本章描述了這些情況,并向大家展示如何打開ARC來管理應用的所有內存空間。

注意:引用計數只應用在類的實例。結構體(Structure)和枚舉類型是值類型,并非引用類型,不是以引用的方式來存儲和傳遞的。
How ARC Works
每次創建一個類的實例,ARC就會分配一個內存塊,用來存儲這個實例的相關信息。這個內存塊保存著實例的類型,以及這個實例相關的屬性的值。當實例不再被使用時,ARC釋放這個實例使用的內存,使這塊內存可作它用。這保證了類實例不再被使用時,它們不會占用內存空間。但是,如果ARC釋放了仍在使用的實例,那么你就不能再訪問這個實例的屬性或者調用它的方法。如果你仍然試圖訪問這個實例,應用極有可能會崩潰。為了保證不會發生上述的情況,ARC跟蹤與類的實例相關的屬性、常量以及變量的數量。只要有一個有效的引用,ARC都不會釋放這個實例。
為了讓這變成現實,只要你將一個類的實例賦值給一個屬性或者常量或者變量,這個屬性、常量或者變量就是這個實例的強引用(strong reference)。之所以稱之為“強”引用,是因為它強持有這個實例,并且只要這個強引用還存在,就不能銷毀實例。
下面的例子展示了ARC是如何工作的。本例定義了一個簡單的類,類名是Person,并定義了一個名為name的常量屬性
class Person { let name: String  init(name: String) { self.name = name println("\(name) is being initialized") }  deinit { println("\(name) is being deinitialized") } 
}

?

接下來的代碼片段定義了三個Person?類型的變量,這些變量用來創建多個引用,這些引用都引用緊跟著的代碼所創建的Person對象。因為這些變量都是可選類型(Person?,而非Person),因此他們都被自動初始化為nil,并且當前并沒有引用一個Person的實例。
var reference1: Person? 
var reference2: Person? 
var reference3: Person? 

?現在我們創建一個新的Person實例,并且將它賦值給上述三個變量中的一個:

reference1 = Person(name: "John Appleseed") 
// prints "Jonh Appleseed is being initialized" 
因為Person的實例賦值給了變量reference1,所以reference1是Person實例的強引用。又因為至少有這一個強引用,ARC就保證這個實例會保存在內存重而不會被銷毀。
如果將這個Person實例賦值給另外的兩個變量,那么將建立另外兩個指向這個實例的強引用:
reference2 = reference1 
reference3 = reference2 

?

現在,這一個Person實例有三個強引用。
如果你通過賦值nil給兩個變量來破壞其中的兩個強引用(包括原始的引用),只剩下一個強引用,這個Person實例也不會被銷毀:

?

reference1 = nil 
reference2 = nil 

直到第三個也是最后一個強引用被破壞,ARC才會銷毀Person的實例,這時,有一點非常明確,你無法繼續使用Person實例:

referenece3 = nil 
// 打印 “John Appleseed is being deinitialized” 

?

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

類實例之間的強引用循環?
在兩個類實例彼此保持對方的強引用,使得每個實例都使對方保持有效時會發生這種情況。我們稱之為強引用循環。
下面的例子展示了一個強引用環是如何在不經意之間產生的。例子定義了兩個類,分別叫Person和Apartment,這兩個類建模了一座公寓以及它的居民:
class Person { let name: String init(name: String) { self.name = name } var apartment: Apartment? deinit { println("\(name) is being deinitialized") } 
} class Apartment { let unit: Int init(unit: Int) { self.unit= unit } var tenant: Person? deinit { println("Apartment #\(number) is being deinitialized") } 
} 

?

每個Person實例擁有一個String類型的name屬性以及一個被初始化為nil的apartment可選屬性。apartment屬性是可選的,因為一個人并不一定擁有一座公寓。
類似的,每個Apartment實例擁有一個Int類型的number屬性以及一個初始化為nil的tenant可選屬性。tenant屬性是可選的,因為一個公寓并不一定有居民。
這兩個類也都定義了初始化函數,打印消息表明這個類的實例正在被初始化。這使你能夠看到Person和Apartment的實例是否像預期的那樣被銷毀了。
下面的代碼片段定義了兩個可選類型變量,john和number73,分別被賦值為特定的Apartment和Person的實例。得益于可選類型的優點,這兩個變量初始值均為nil:
var john: Person? 
var unit4A: Apartment?

?現在,你可以創建特定的Person實例以及Apartment實例,并賦值給john和number73:

jhon = Person(name: "John Appleseed") 
unit4A = Apartments(number: 4A) 

?下面的圖表明了在創建以及賦值這兩個實例后強引用的關系。john擁有一個Person實例的強引用,unit4A擁有一個Apartment實例的強引用:

?現在你可以將兩個實例關聯起來,一個人擁有一所公寓,一個公寓也擁有一個房客。注意:用感嘆號(!)來展開并訪問可選類型的變量,只有這樣這些變量才能被賦值:

john!.apartment = unit4A
unit4A!.tenant = john

?

?實例關聯起來后,強引用關系如下圖所示

?

?關聯這倆實例生成了一個強循環引用,Person實例和Apartment實例各持有一個對方的強引用。因此,即使你破壞john和number73所持有的強引用,引用計數也不會變為0,因此ARC不會銷毀這兩個實例

?

john = nil
unit4A = nil

當上面兩個變量賦值為nil時,沒有調用任何一個析構方法。強引用阻止了Person和Apartment實例的銷毀,進一步導致內存泄漏。

?

  避免強引用循環

  Swift提供兩種方法避免強引用循環:弱引用和非持有引用。?

?  對于生命周期中引用會變為nil的實例,使用弱引用;對于初始化時賦值之后引用再也不會賦值為nil的實例,使用非持有引用。

?

  弱引用

  弱引用不會增加實例的引用計數,因此不會阻止ARC銷毀被引用的實例,聲明屬性或者變量的時候,關鍵字weak表明引用為弱引用。弱引用只能聲明為變量類型,因為運行時它的值可能改變。弱引用絕對不能聲明為常量

?  因為弱引用可以沒有值,所以聲明弱引用的時候必須是可選類型的。在Swift語言中,推薦用可選類型來作為可能沒有值的引用的類型。

?下面的例子和之前的Person和Apartment例子相似,除了一個重要的區別。這一次,我們聲明Apartment的tenant屬性為弱引用:

class Person {let name: Stringinit(name: String) { self.name = name }var apartment: Apartment?deinit { print("\(name) is being deinitialized") }
}class Apartment {let unit: Stringinit(unit: String) { self.unit = unit }weak var tenant: Person?deinit { print("Apartment \(unit) is being deinitialized") }
}

?

?然后創建兩個變量(john和unit4A)的強引用,并關聯這兩個實例:

var john: Person?
var unit4A: Apartment?john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")john!.apartment = unit4A
unit4A!.tenant = john

?

?下面是引用的關系圖:

?

Person的實例仍然是Apartment實例的強引用,但是Apartment實例則是Person實例的弱引用。這意味著當破壞john變量所持有的強引用后,不再存在任何Person實例的強引用:?

?

?既然不存在Person實例的強引用,那么該實例就會被銷毀:

?

非持有引用
和弱引用相似,非持有引用也不強持有實例。但是和弱引用不同的是,非持有引用默認始終有值。因此,非持有引用只能定義為非可選類型(non-optional type)。在屬性、變量前添加unowned關鍵字,可以聲明一個非持有引用。
因為是非可選類型,因此當使用非持有引用的時候,不需要展開,可以直接訪問。不過非可選類型變量不能賦值為nil,因此當實例被銷毀的時候,ARC無法將引用賦值為nil。
class Customer { let name: String var card: CreditCard? init(name: String) { self.name = name } deinit { println("\(name) is being deinitialized") 
} class CreditCard { let number: Int unowned let customer: Customer init(number: Int, customer: Customer) { self.number = number self.customer = customer } deinit { println("Card #\(number) is being deinitialized") 
} 

?

下面的代碼定義了一個叫john的可選類型Customer變量,用來保存某個特定消費者的引用。因為是可變類型,該變量的初始值為nil:
var john: Customer?

?現在創建一個Customer實例,然后用它來初始化CreditCard實例,并把剛創建出來的CreditCard實例賦值給Customer的card屬性:

john = Customer(name: "John Appleseed") 
john!.card = CreditCard(number: 1234_5678_9012_3456, customer:john!)

?此時的引用關系如下圖所示

因為john對CreditCard實例是非持有引用,當破壞john變量持有的強引用時,就沒有Customer實例的強引用了

此時Customer實例被銷毀。然后,CreditCard實例的強引用也不復存在,因此CreditCard實例也被銷毀

john = nil 
// 打印"John Appleseed is being deinitialized" 
// 打印"Card #1234567890123456 is being deinitialized"

??

  非持有引用以及隱式展開的可選屬性

Person和Apartment的例子說明了下面的場景:兩個屬性的值都可能是nil,并有可能產生強引用環。這種場景下適合使用弱引用。
Customer和CreditCard的例子則說明了另外的場景:一個屬性可以是nil,另外一個屬性不允許是nil,并有可能產生強引用環。這種場景下適合使用無主引用。
但是,存在第三種場景:兩個屬性都必須有值,且初始化完成后不能為nil。這種場景下,則要一個類用無主引用屬性,另一個類用隱式展開的可選屬性。這樣,在初始化完成后我們可以立即訪問這兩個變量(而不需要可選展開)

?下面的例子定義了兩個類,Country和City,都有一個屬性用來保存另外的類的實例。在這個模型里,每個國家都有首都,每個城市都隸屬于一個國家。所以,類Country有一個capitalCity屬性,類City有一個country屬性:

class Country { let name: String let capitalCity: City! init(name: String, capitalName: String) { self.name = name self.capitalCity = City(name: capitalName, country: self) } 
} class City { let name: String unowned let country: Country init(name: String, country: Country) { self.name = name self.country = country } 
} 
City的初始化函數有一個Country實例參數,并且用country屬性來存儲這個實例。這樣就實現了上面說的關系。
Country的初始化函數調用了City的初始化函數。但是,只有Country的實例完全初始化完后(在Two-Phase Initialization),Country的初始化函數才能把self傳給City的初始化函數。
為滿足這種需求,通過在類型結尾處加感嘆號(City!),我們聲明Country的capitalCity屬性為隱式展開的可選類型屬性。就是說,capitalCity屬性的默認值是nil,不需要展開它的值(在Implicity Unwrapped Optionals中描述)就可以直接訪問。
因為capitalCity默認值是nil,一旦Country的實例在初始化時給name屬性賦值后,整個初始化過程就完成了。這代表只要賦值name屬性后,Country的初始化函數就能引用并傳遞隱式的self。所以,當Country的初始化函數在賦值capitalCity時,它也可以將self作為參數傳遞給City的初始化函數。
綜上所述,你可以在一條語句中同時創建Country和City的實例,卻不會產生強引用環,并且不需要使用感嘆號來展開它的可選值就可以直接訪問capitalCity:
var country = Country(name: "Canada", capitalName: "Ottawa") 
println("\(country.name)'s captial city is called \(country.capitalCity.name)") 
// 打印"Canada's capital city is called Ottawa" 

?

?在上面的例子中,使用隱式展開的可選值滿足了兩個類的初始化函數的要求。初始化完成后,capitalCity屬性就可以做為非可選值類型使用,卻不會產生強引用環。

?

  閉包的強引用循環

將一個閉包賦值給類實例的某個屬性,并且這個閉包使用了實例,這樣也會產生強引用環。這個閉包可能訪問了實例的某個屬性,例如self.someProperty,或者調用了實例的某個方法,例如self.someMethod。這兩種情況都導致了閉包使用self,從而產生了搶引用環。
因為諸如類這樣的閉包是引用類型,導致了強引用環。當你把一個閉包賦值給某個屬性時,你也把一個引用賦值給了這個閉包。實質上,這個之前描述的問題是一樣的-兩個強引用讓彼此一直有效。但是,和兩個類實例不同,這次一個是類實例,另一個是閉包。
Swift提供了一種優雅的方法來解決這個問題,我們稱之為閉包捕獲列表(closuer capture list)。
下面的例子將會告訴你當一個閉包引用了self后是如何產生一個強引用循環的。
class HTMLElement { let name: String let text: String? lazy var asHTML: () -> String = { if let text = self.text { return "<\(self.name)>\(text)</\(self.name)>" } else { return "<\(self.name) />" } } init(name: String, text: String? = nil) { self.name = name self.text = text } deinit { println("\(name) is being deinitialized") } } 

?

HTMLElement定義了一個name屬性來表示這個元素的名稱,例如代表段落的"p",或者代表換行的"br";以及一個可選屬性text,用來設置HTML元素的文本。
除了上面的兩個屬性,HTMLElement還定義了一個lazy屬性asHTML。這個屬性引用了一個閉包,將name和text組合成HTML字符串片段。該屬性是() -> String類型,就是“沒有參數,返回String的函數”。
默認將閉包賦值給了asHTML屬性,這個閉包返回一個代表HTML標簽的字符串。如果text值存在,該標簽就包含可選值text;或者不包含文本。對于段落,根據text是"some text"還是nil,閉包會返回"<p>some text</p>"或者"<p />"。
可以像實例方法那樣去命名、使用asHTML。然而,因為asHTML終究是閉包而不是實例方法,如果你像改變特定元素的HTML處理的話,可以用定制的閉包來取代默認值。

?閉包使用了self(引用了self.name和self.text),因此閉包占有了self,這意味著閉包又反過來持有了HTMLElement實例的強引用。這樣就產生了強引用環

?

  避免閉包產生的強引用循環

在定義閉包時同時定義捕獲列表作為閉包的一部分,可以解決閉包和類實例之間的強引用環。捕獲列表定義了閉包內占有一個或者多個引用類型的規則。和解決兩個類實例間的強引用環一樣,聲明每個占有的引用為弱引用或非持有引用,而不是強引用。根據代碼關系來決定使用弱引用還是非持有引用。
注意:Swift有如下約束:只要在閉包內使用self的成員,就要用self.someProperty或者self.someMethod(而非只是someProperty或someMethod)。這可以提醒你可能會不小心就占有了self。

?

?  定義捕獲列表

?

捕獲列表中的每個元素都是由weak或者unowned關鍵字和實例的引用(如self或someInstance)組成。每一對都在花括號中,通過逗號分開。
捕獲列表放置在閉包參數列表和返回類型之前:
lazy var someClosure: (Int, String) -> String = { [unowned self] (index: Int, stringToProcess: String) -> String in // closure body goes here 
} 

?

?如果閉包沒有指定參數列表或者返回類型(可以通過上下文推斷),那么占有列表放在閉包開始的地方,跟著是關鍵字in:

lazy var someClosure: () -> String = { [unowned self] in // closure body goes here 
  
} 

?前面提到的HTMLElement例子中,非持有引用是正確的解決強引用的方法。這樣編碼HTMLElement類來避免強引用環:

class HTMLElement { let name: String let text: String? lazy var asHTML: () -> String = { [unowned self] in if let text = self.text { return "<\(self.name)>\(text)</\(self.name)>" } else { return "<\(self.name) />" } } init(name: String, text: String? = nil) { self.name = name self.text = text } deinit { println("\(name) is being deinitialized") } } 

?

上面的HTMLElement實現和之前的實現相同,只是多了占有列表。這里,占有列表是[unowned self],代表“用無主引用而不是強引用來占有self”。
和之前一樣,我們可以創建并打印HTMLElement實例:
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") 
println(paragraph!.asTHML()) 
// 打印"<p>hello, world</p>" 

?引用關系如下圖

這一次,閉包以無主引用的形式占有self,并不會持有HTMLElement實例的強引用。如果賦值paragraph為nil,HTMLElement實例將會被銷毀,并能看到它的deinitializer打印的消息。  
paragraph = nil 
// 打印"p is being deinitialized" 

?

轉載于:https://www.cnblogs.com/salam/p/5465317.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/257199.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/257199.shtml
英文地址,請注明出處:http://en.pswp.cn/news/257199.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

像元大小及精度

說完了光學系統的分辨率之后我們來看看相機的圖像分辨率。圖像分辨率比較好理解&#xff0c;就是單位距離內的像用多少個像素來顯示。以我們的ORCA-Flash4.0為例&#xff0c;芯片的像元大小為 6.5 μm&#xff0c;在 40X物鏡的放大倍率下&#xff0c;1 μm的物經光學系統放大為…

轉:傳入的表格格式數據流(TDS)遠程過程調用(RPC)協議流不正確 .

近期在做淘寶客的項目&#xff0c;大家都知道&#xff0c;淘寶的商品詳細描述字符長度很大&#xff0c;所以就導致了今天出現了一個問題 VS的報錯是這樣子的 ” 傳入的表格格式數據流(TDS)遠程過程調用(RPC)協議流不正確“ 還說某個desricption 過長之類的話 直覺告訴我&#…

合并bin文件-----帶boot發布版本比較好用的bat(便捷版)

直接上圖上代碼&#xff08;代碼在結尾&#xff09;&#xff0c;有不會用的可以留言&#xff1a; 第一步&#xff1a;工程介紹&#xff0c;關鍵點--- 1.bat文件放所在app和boot工程的同級目錄下 2.release為運行bat自動生成文件夾 第二步&#xff1a;合版.bat 針對具體項目需…

第五天 斷點續傳和下載

1 斷點續傳&#xff0c; 2.多線程下載原理 3.httpUtils 多線程斷點下載的使用。 ------------- 1.拿到需要下載的文件的大小&#xff0c;和需要初始的線程數 2.得到每個線程需要下載的大小&#xff0c;最后一個線程負責將剩下的數據全部下載。 3.同時需要設置一個與下載文件同大…

關于cmake從GitHub上下載的源碼啟動時報錯的問題

關于cmake從GitHub上下載的源碼啟動時報錯的問題&#xff1a; 由于cmake會產生all_build和zero_check兩個project&#xff0c;此時需要右擊鼠標將需要運行的項目設為啟動項&#xff0c;在進行編譯&#xff0c;現只針對“找不到all_build文件“的出錯信息&#xff0c;若有相關編…

一個人的Scrum之準備工作

在2012年里&#xff0c;我想自己一人去實踐一下Scrum&#xff0c;所以才有了這么一個開篇。 最近看了《輕松的Scrum之旅》這本書&#xff0c;感覺對我非常有益。書中像講述故事一樣描述了在執行Scrum過程中的點點滴滴&#xff0c; 仿佛我也跟著進行了一次成功的Scrum。同樣的&a…

Elementary OS安裝Chrome

elementary os 官方網站&#xff1a;https://elementary.io/ 這os是真好看&#xff01;首先這是基于ubuntu的&#xff0c;所以可以安裝ubuntu的軟件&#xff01; 電腦必備瀏覽器必須是chrome呀&#xff01;下載地址&#xff1a; https://www.chrome64bit.com/index.php/google…

vs+opencv編譯出現內存問題

將圖片路徑改為項目下的相對路徑&#xff0c;如 …\data\01.jpg; 其中…表示項目所在目錄的上級目錄&#xff0c;不要用絕對路徑&#xff0c;具體原因未知&#xff0c;同時&#xff0c;出現opencv_worldxxx.lib找不到情況&#xff0c;1.鏈接中依賴項是否寫錯&#xff08;英文輸…

runtime--實現篇02(Category增加屬性)

在iOS設計Category中&#xff0c;默認不能直接添加屬性&#xff0c;如果分類中通過property修飾的屬性&#xff0c;只會生成setter和getter的聲明&#xff0c; 不會生成其實現&#xff1b;因此&#xff0c;如果一定要添加屬性的話&#xff0c;需要借助runtime特性&#xff0c;通…

spark、oozie、yarn、hdfs、zookeeper、

為什么80%的碼農都做不了架構師&#xff1f;>>> spark、 oozie:任務調度 yarn:資源調度 hdfs:分布式文件系統 zookeeper、 轉載于:https://my.oschina.net/u/3709135/blog/1556661

關于halcon多區域挑選有關算法的自我理解(tuple_sort_index)

多區域根據面積挑選想要的obj area_center&#xff08;regions&#xff0c;areas&#xff09; tuple_sort_index(areas&#xff0c;indexs) tuple_sort_index算子將一組數組進行升序排列&#xff0c;然后將其在原數組的index按升序放入indexs中&#xff0c; 例如原數組areas[20…

JLOI2016 方

bzoj4558 真是一道非常excited的題目啊…JLOI有毒 題目大意&#xff1a;給一個(N1)*(M1)的網格圖&#xff0c;格點坐標為(0~N,0~M)&#xff0c;現在挖去了K個點&#xff0c;求剩下多少個正方形&#xff08;需要注意的是正方形可以是斜著的&#xff0c;多斜都可以&#xff09; N…

opencv 直方圖反向投影

轉載至&#xff1a;http://www.cnblogs.com/zsb517/archive/2012/06/20/2556508.html 直方圖反向投影式通過給定的直方圖信息&#xff0c;在圖像找到相應的像素分布區域&#xff0c;opencv提供兩種算法&#xff0c;一個是基于像素的&#xff0c;一個是基于塊的。 使用方法不寫了…

request請求在Struts2中的處理步驟

2019獨角獸企業重金招聘Python工程師標準>>> 一個請求在Struts2框架中的處理大概分為以下幾個步驟 1 客戶端初始化一個指向Servlet容器&#xff08;例如Tomcat&#xff09;的請求 2 這個請求經過一系列的過濾器&#xff08;Filter&#xff09;&#xff08;這些過濾…

vs聯合torch,ZED相機api,opencv建立C++項目

ZED相機api下載及cmake教程 generate產生工程文件后打開&#xff0c;配置如下&#xff1a; 將ZED項目作為啟動項 然后在main.cpp中寫入自己的工程代碼即可&#xff0c;運行也在release X64下進行 注&#xff1a;cmake之前源文件下main.cpp&#xff0c;也就是tutorial 1 - h…

POJ 2186

//在一張有向無環圖G&#xff0c;圖G會包含很多環&#xff08;環里面的點是等價的&#xff09;&#xff0c; //當然可以把環縮成一個點&#xff08;利用tarjan縮點&#xff09;&#xff0c; //形成一棵樹&#xff0c;題目要求是求除他以外的點都指向他&#xff0c;也就是只有一…

使用DataGridView數據窗口控件,構建用戶快速輸入體驗

使用DataGridView數據窗口控件&#xff0c;構建用戶快速輸入體驗 在“隨風飄散” 博客里面&#xff0c;介紹了一個不錯的DataGridView數據窗口控件《DataGridView數據窗口控件開發方法及其源碼提供下載》&#xff0c;這種控件在有些場合下&#xff0c;還是非常直觀的。因為&…

pip安裝

下載pip安裝包&#xff0c;解壓。復制到C:\Users\administrator\下&#xff0c;用cmd打開當前文件夾&#xff0c;用Python安裝&#xff0c; Python setup.py install 安裝完之后記得把Python根目錄下的scripts也放在環境變量里。 以上是我pip安裝的成功例子&#xff0c;可能不…

深入剖析授權在WCF中的實現[共14篇]

I、身份&#xff08;Identity&#xff09;與安全主體&#xff08;Security Principal&#xff09; 從兩個重要的概念談起&#xff1a;Identity與Principal[上篇] 從兩個重要的概念談起&#xff1a;Identity與Principal[下篇] WCF的三種授權模式 II、Windows用戶組授權 基于Wind…

sqlserver 查看鎖表,解鎖

查看被鎖表&#xff1a; 代碼如下 復制代碼 select request_session_id spid,OBJECT_NAME(resource_associated_entity_id) tableName from sys.dm_tran_locks where resource_typeOBJECT spid 鎖表進程 tableName 被鎖表名 [more] 解鎖&#xff1a; 創建一個臨時Table 代碼如下…