ios 內存管理,weak和unowned

LaunaJones 7年前發布 | 9K 次閱讀 iOS開發 移動開發 Objective-C

懸掛指針及內存泄漏

如果對象a指向對象b,若對象b被釋放了,則此時對象a指向一個未知地址,這種情況叫做懸掛指針。

如果對象a指向對象b,若對象a被釋放了,則此時沒有任何對象能夠指向對象b,對象b無法被釋放,這種情況叫做內存泄漏。

手動內存管理

在ARC出現之前,ios的內存管理是基于手動內存管理,也叫做MRC。

為了防止懸掛指針及內存泄漏,手動內存管理基于一個引用計數(retain count)的概念,所有對象都可以增加或減少一個對象的引用計數,當對象的引用計數大于0,則該對象繼續存在;當該對象的引用計數減少到0,則該對象自動銷毀。NSObject實現了 retainrelease 方法,用于增加或減少引用計數。

具體的規則如下:

  • 如果對象a通過調用初始化函數初始化了對象b,則初始化函數增加b的引用計數。
  • 如果對象a通過 copymutableCopy 或任何帶有 copy 字樣的方法獲得一個對象b的拷貝,則這些copy方法負責增加這個新的b的拷貝的引用計數。
  • 如果對象a直接獲取一個對象b的引用(不是通過初始化或拷貝的方法),則對象a自己負責增加對象b的引用計數。
  • 如果對象a之前直接或間接的增加了對象b的引用計數,當對象a不再需要對象b的引用之后,對象a要負責減少對象b的引用計數。如果對象b的引用計數減少到0,則對象b被釋放。

ARC

ARC(以及Swift)出現之后,我們不能再調用 retainrelease 方法了。對引用計數的工作都由ARC自動完成。ARC被實現為編輯器的一部分,它將在幕后為我們自動加入 retainrelease 方法。

但即使是在ARC的環境下,仍然有一些內存管理問題需要我們注意或進行手動處理。

循環引用

在這里我直接使用官方文檔的例子:

class Person {
  let name: String
  init(name: String) { self.name = name }
  var apartment: Apartment?
  deinit { print("(name) is being deinitialized") }
}

class Apartment { let unit: String init(unit: String) { self.unit = unit } var tenant: Person? deinit { print("Apartment (unit) is being deinitialized") } }

var john: Person? var unit4A: Apartment?

john = Person(name: "John Appleseed") unit4A = Apartment(unit: "4A")

john!.apartment = unit4A unit4A!.tenant = john</code></pre>

Paste_Image.png

這是最典型的強引用循環,當john和unit4A釋放各自的對象之后,Person和Apartment的實例不會銷毀,因為它們在互相引用對方。具體的解決辦法有weak和unowned。

Weak

將上例的tenant屬性聲明為weak即可解決循環引用的問題。

class Person {
  let name: String
  init(name: String) { self.name = name }
  var apartment: Apartment?
  deinit { print("(name) is being deinitialized") }
}

class Apartment { let unit: String init(unit: String) { self.unit = unit } weak var tenant: Person? deinit { print("Apartment (unit) is being deinitialized") } }</code></pre>

此時如圖:

Paste_Image.png

當john釋放時,Person實例因為沒有strong引用,所以也被銷毀了,tenant屬性是weak引用,所以被ARC自動設置為nil。循環引用的問題就此解決。

Weak引用利用了ARC的功能,當一個引用被聲明為weak的時候,ARC并不會retain這個對象。ARC會記錄所有的weak引用以及它們所指向的對象,當某個對象的引用計數降到0并被銷毀之后,ARC會自動將nil賦予這個引用,這也是swift中必須將weak引用聲明為optional var的原因。

Unowned

與weak引用相類似,unowned引用也不會保存一個strong引用至它所指向的對象。

將一個引用聲明為Unowned,意味著ARC對這個引用將不再起任何作用。如果引用的對象被銷毀了,我們將真正面臨懸掛指針的問題。所以,官方的說法是使用unowned引用必須確保unowned引用所指向的對象擁有與unowned引用相同的或更長的生命期限,因為這樣是最安全的。

還是使用官方的例子:

class Customer {
  let name: String
  var card: CreditCard?
  init(name: String) {
  self.name = name
  }
  deinit { print("(name) is being deinitialized") }
}

class CreditCard { let number: UInt64 unowned let customer: Customer init(number: UInt64, customer: Customer) { self.number = number self.customer = customer } deinit { print("Card #(number) is being deinitialized") } }

var john: Customer? john = Customer(name: "John Appleseed") john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)</code></pre>

Paste_Image.png

這個例子是使用unowned引用最安全的方法,因為unowned引用所指向的對象擁有與unowned引用相同的或更長的生命期限,我們可以理解為兩個對象一損俱損的情況下,使用unowned最安全。

但是,如果此時CreditCard實例有另外一個對象指向它,那么當john被釋放了,Customer實例也被銷毀了,但是CreditCard實例仍然存在,CreditCard類中的customer屬性將指向一個懸掛指針,我們需要手動的將nil賦予customer屬性。這種情況也是非常常見的。

一些特殊例子

ARC不支持的delegate

使用weak引用最常見的情況就是在delegate中,如下:

class ColorPickerController : UIViewController { 
  weak var delegate: ColorPickerDelegate? // ...
}

但是,一些內置的Cocoa類使用了ARC不支持的引用(因為它們是非常老舊的代碼或有向后兼容的需求),這種屬性使用了 assign 關鍵字,例如, AVSpeechSynthesizer 的delegate屬性聲明如下:

@property(nonatomic, assign, nullable) id<AVSpeechSynthesizerDelegate> delegate;

在Swift中,對應的聲明如下:

unowned(unsafe) var delegate: AVSpeechSynthesizerDelegate?

在Swift中, unowned 和Objective-C的 assign 意義相同,都意味著ARC的內存管理機制在這里不起作用。 unsafe 關鍵字是Swift自己加上去的,作為更進一步的警告吧。

即使我們自己的代碼使用了ARC,但是Cocoa的內部代碼沒有使用ARC,這種情況依然能造成內存錯誤。例如,我們將某個對象賦予 AVSpeechSynthesizer 的delegate屬性,如果我們的對象被銷毀了,而這個delegate引用仍然存在,我們就需要手動將nil賦予這個delegate引用。這種情況與我在上一節中最后說的情況一樣。

Notification

如果使用 addObserver(_:selector:name:object:) 方法注冊notification,你其實是在該函數的第一個參數作為一個引用傳遞給notification center,通常是self。notification center對這個對象的引用是non-ARC的unsafe的引用,當我們的對象被銷毀后,notification center將面臨懸掛指針的問題。這就是為什么我們必須要在對象銷毀前進行unregister。這種情況與前面講的delegate例子相類似。

如果使用 addObserver(forName:object:queue:using:) 方法注冊notification,內存管理的問題將更加復雜。

  • addObserver(forName:object:queue:using:) 方法返回的對象將被notification center retain,直到我們unregister它。
  • addObserver(forName:object:queue:using:) 方法的最后一個參數using是一個closure,它可能會引用self,這就意味著在unregister這個notification之前,notification center還會對self進行retain。這同樣意味著我們不可能在 deinit 中進行unregister操作,因為在注冊之后,unregister之前, deinit 不可能被調用。
  • 如果我們自己又引用了 addObserver(forName:object:queue:using:) 方法返回的對象,該方法的using參數又引用了self,則形成了一個循環引用。

我們看下面的例子:

var observer : Any! 
override func viewWillAppear(_ animated: Bool) {
  super.viewWillAppear(animated) 
  self.observer = NotificationCenter.default.addObserver(forName: .woohoo, object:nil, queue:nil) { _ in
  print(self.description)
  }
}

因為unregister的操作不能在 deinit 中進行。

override func viewDidDisappear(_ animated: Bool) { 
  super.viewDidDisappear(animated) 
  NotificationCenter.default.removeObserver(self.observer)
}

現在完成了register和unregister的操作,但是view controller本身因為有循環引用的問題而出現了內存泄漏的情況,可以發現 deinit 函數不會被調用。

deinit { 
print("deinit") 
}

最簡單的解決方法是在closure中定義捕捉列表,將self聲明為unowned。

self.observer = NotificationCenter.default.addObserver( forName: .woohoo, object:nil, queue:nil) { 
  [unowned self] _ in 
  print(self.description)
}

Timer

Timer類文檔中有說明如下:“run loops maintain strong references to their timers”。

scheduled-Timer(timeInterval:target:selector:userInfo:repeats:) 函數的文檔說明如下:“The timer main‐tains a strong reference to target until it (the timer) is invalidated.”

上面的文檔已經說明當repeat timer沒有被invalidate之前,target參數(通常是self)是被run loops retain的,這意味著在invalidate之前,target參數(通常是self)是無法釋放的,這同樣意味著我們不可能在 deinit 中進行invalidate操作,因為在invalidate之前, deinit 不可能被調用。與上例相似,我們可以在 viewWillAppearviewDidDisappear 進行相關的操作:

var timer : Timer! 
override func viewWillAppear(_ animated: Bool) {
  super.viewWillAppear(animated) 
  self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(fired), userInfo: nil, repeats: true)
  self.timer.tolerance = 0.1
} 
func fired(_ t:Timer) {
  print("timer fired") 
}
override func viewDidDisappear(_ animated: Bool) { 
  super.viewDidDisappear(animated) 
  self.timer.invalidate()
}

在ios 10中,我們可以使用 scheduledTimer(withTimeInterval:repeats:block:) 方法,該方法可以 deinit 函數中進行invalida。但是要注意,如果在block參數(closure)中引用了self,同樣要解決循環引用的問題。

var timer : Timer! 
override func viewWillAppear(_ animated: Bool) {
  super.viewWillAppear(animated) 
  self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
    [unowned self] // * 
    t in 
    self.fired(t)
    }
  self.timer.tolerance = 0.1 
}
func fired(_ t:Timer) { 
  print("timer fired")
}
deinit {
  self.timer.invalidate() 
}

Objective-C 屬性

在Objective-C中,一個 @property 的聲明包含一些關于內存管理的描述,例如在 UIViewController 中,view的屬性描述如下:

@property(null_resettable, nonatomic, strong) UIView *view;

strong表示setter方法將會retain傳遞進來的UIView對象,Swift將上述聲明翻譯如下:

var view: UIView!

Swift默認的聲明就是strong。

下面列出幾種Cocoa屬性的內存管理描述:

strong, retain (no Swift equivalent)

這兩個描述是一個意思,retain來自于在ARC出現之前的時代。

weak (Swift weak)

該屬性將會利用ARC功能,傳遞進來的對象不會被retain,當該對象被銷毀后,ARC將會自動賦予nil給該屬性,所以該屬性必須聲明為optional var。

assign (Swift unowned(unsafe))

ARC的內存管理機制在對屬性不起作用,如果該屬性所引用的對象被銷毀,該屬性將變成一個懸掛指針。需要我們自己手動賦值為nil。

copy (no Swift equivalent, or @NSCopying)

與strong和retain相似,不同點在于setter方法會通過發送 copy 方法拷貝傳遞進來的對象。該對象必須遵循NSCopy。

copy 屬性的應用場合是如果一個immutable類有一個mutable子類,(如 NSStringNSMutableString , 或者 NSArrayNSMutableArray ),如果setter期望傳進來的是一個immutable類對象,結果傳進來的是一個mutable子類對象(根據多態性)。為防止這種情況發生, copy 屬性會使setter方法對傳進來的對象調用copy方法并創建一個新的屬于immutable類實例。

在Swift中,這種情況不會發生在string或array上,因為string和array在Swift中實現為struct,本身就是值類型,并且通過拷貝的方式進行傳遞。所以 NSStringNSArray 在Swift中對于的 StringArray 沒有任何特殊的標記。但那些沒有橋接到Swift中的Cocoa類型則需要用到 @NSCopying 標記。例如UILabel中的 attributedText 屬性,在Swift中聲明如下:

@NSCopying var attributedText: NSAttributedString?

因為 NSAttributedString 有一個mutable子類 NSMutableAttributedString

我們同樣可以在我們自己的代碼中使用 @NSCopying 標記,Swift會負責管理具體的拷貝操作。如下:

class StringDrawer { 
  @NSCopying var attributedString : NSAttributedString! 
  // ...
}

有時我們會遇到這種情況,我們的類,在類內部可以使用mutable,但是類外部傳進來的必須是immutable的,具體方法就是創建一個private的計算型屬性即可。

class StringDrawer { 
  @NSCopying var attributedString : NSAttributedString! 
  private var mutableAttributedString : NSMutableAttributedString! {
  get { 
    if self.attributedString == nil {return nil} 
    return NSMutableAttributedString(attributedString:self.attributedString)
    }
  set {
    self.attributedString = newValue 
    }
  }
// ...
}

 

來自:http://www.jianshu.com/p/09a0b5ce35f8

 

 本文由用戶 LaunaJones 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!