日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【iOS开发】——MRC(手动内存管理)的一些补充

發布時間:2024/3/12 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【iOS开发】——MRC(手动内存管理)的一些补充 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

【iOS開發】——MRC(手動內存管理)的一些補充

  • 前言
  • 野指針與空指針
  • 多個對象內存管理的思想
    • 玩家沒有使用過房間
    • 一個玩家使用一個游戲房間的情況
    • 一個玩家使用一個房間 r 后,換到另一個房間 r2 的情況
    • 一個玩家使用一個房間,不再使用房間,將房間釋放掉之后,再次使用該房間的情況
  • MRC需要注意的一些知識點
    • @property 參數
    • 自動釋放池(AutoreleasePool)
      • 使用 autorelease 有什么好處呢?
      • autorelease 的原理實質上是什么?
      • autorelease 的創建方法
      • autorelease的使用方法
      • autorelease 的注意事項
      • 自動釋放池的嵌套使用
      • autorelease 錯誤用法
      • MRC 中避免循環引用

前言

上學期的時候我總結過關于MRC的一些知識,具體可以看這篇:iOS開發——MRC(手動內存管理)

最近在復習MRC,發現當時總結的時候有幾個點沒有總結上,今天在寫一篇補充記錄一下

野指針與空指針

空指針:

  • 空指針指的是沒有指向存儲空間的指針(里面存的是 nil, 也就是 0)。
  • 給空指針發消息是沒有任何反應的
int main(int argc, const char * argv[]) {@autoreleasepool {Person *p = [[Person alloc] init]; // 執行完引用計數為 1。[p release]; // 執行完引用計數為 0,實例對象被釋放。p = nil; // 此時,p 變為了空指針。[p release]; // 再給空指針 p 發送消息就不會報錯了。[p release];}return 0; }

野指針:

  • 只要一個對象被釋放了,我們就稱這個對象為「僵尸對象(不能再使用的對象)」。
  • 當一個指針指向一個僵尸對象(不能再使用的對象),我們就稱這個指針為「野指針」。
  • 只要給一個野指針發送消息就會報錯(EXC_BAD_ACCESS 錯誤)。
int main(int argc, const char * argv[]) {@autoreleasepool {Person *p = [[Person alloc] init]; // 執行完引用計數為 1。[p release]; // 執行完引用計數為 0,實例對象被釋放。[p release]; // 此時,p 就變成了野指針,再給野指針 p 發送消息就會報錯。[p release]; // 報錯}return 0; }

多個對象內存管理的思想

多個對象之間往往是通過setter方法產生聯系的,其內存管理的方法也是通過setter、delloc方法實現管理的。接下來我們學習一下setter方法的具體實現過程

我們可以舉一個例子來幫助我們理解這個過程:
我記得很早以前,騰訊出過一個叫qq游戲大廳的功能好像,具體記不清了,反正就是有好幾種游戲,比如斗地主,我們打麻將需要三個人和一個房間,所以我們可以定義房間為Room類對象,然后定義玩家為Person類對象,玩家對象擁有 _room 作為成員變量。
一個玩家對象,如果想要玩游戲,就要持有一個房間對象,并保證在使用房間期間,這個房間對象一直存在,并且在游戲房間沒人的時候,還需要將這個房間對象釋放。

那么房間具體的引用情況有哪些呢:

  • 只要一個玩家想使用房間(進入房間),就需要對這個游戲房間的引用計數器 +1。
  • 只要一個玩家不想再使用房間(離開房間),就需要對這個游戲房間的引用計數器 -1。
  • 只要還有至少一個玩家在用某個房間,那么這個游戲房間就不會被回收,引用計數至少為 1。
  • 只要沒有玩家在房子里了,那么這個房間就會被回收


我們可以看到玩家三個玩家對象都持有房間對象,所以房間對象的引用為3。

我們將剛剛說的兩個類對的代碼寫出來:
Room類:

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface Room : NSObject @property int number; @endNS_ASSUME_NONNULL_END

Person類:

#import <Foundation/Foundation.h> #import "Room.h" NS_ASSUME_NONNULL_BEGIN@interface Person : NSObject {Room *_room; }- (void)setRoom:(Room *)room; - (Room *)room; @endNS_ASSUME_NONNULL_END

玩家沒有使用過房間

#import <Foundation/Foundation.h> #import "Person.h" #import "Room.h"int main(int argc, const char * argv[]) {@autoreleasepool {//1.創建兩個對象Room *r = [[Room alloc] init];Person *p = [[Person alloc] init];//給房間號賦值r.number = 808;//釋放兩個對象[r release];[p release];}return 0; }

我們可以看到在上述代碼中Person類創建的對象沒有對房間進行持有,也就是玩家雖然創建出來了但是卻沒有使用過房間,上述代碼運行時內存使用情況如圖所示:

在這里復習兩個知識點:

  • 棧:存放基本類型 的變量數據和對象的引用,但對象本身不存放在棧中,而是存放在堆(new出來的對象)或者常量池中(字符串常量對象存放的常量池中),局部變量【注意:(方法中的局部變量使用final修飾后,放在堆中,而不是棧中)】
  • 堆:存放使用new創建的對象,全局變量

我們在來看上面的例子,通過上圖可以發現Room 實例對象和 Person 實例對象之間沒有相互聯系,所以各自釋放不會報錯。等兩個對象釋放以后,內存的情況如圖所示:

最后由于引用計數變為0了,各自實例對象的內存就會被系統回收。

一個玩家使用一個游戲房間的情況

在調用 setter 方法的時候,因為 Room 實例對象多了一個 Person 對象引用,所以應將 Room 實例對象的引用計數 +1 才對,即 setter 方法應該像下邊一樣,對 room 進行一次 retain 操作。

- (void)setRoom:(Room *)room { // 調用 room = r;// 對房間的引用計數器 +1[room retain];_room = room; }

然后我們在main函數里完成一下一個玩家使用一個游戲房間的情況:

#import <Foundation/Foundation.h> #import "Person.h" #import "Room.h"int main(int argc, const char * argv[]) {@autoreleasepool {Room *r = [[Room alloc] init];Person *p = [[Person alloc] init];r.number = 808;// 將房間賦值給玩家,表示玩家在使用房間// 玩家需要使用這間房,只要玩家在,房間就一定要在p.room = r;// [p setRoom:r][r release];// 在這行代碼之前,玩家都沒有被釋放,但是因為玩家還在,那么房間就不能銷毀[p release];}return 0; }

此時我們的內存分配情況就應該為:

其實還是很好理解的,我們主要來理解一下引用計數這部分,Room創建實例對象引用計數?1,然后Person創建實例對象Person的引用計數也?1同時Person通過setter方法對Room實例對象進行了持有,所以此時Room的引用計數再?1變為了2。

然后我們看,Room的實例對象釋放了對應的Room的引用計數就要?1,此時內存的分配情況為:

然后執行代碼 [p release];,釋放Person實例對象。這時候因為玩家不在房間里了,房間也沒有用了,所以在釋放玩家的時候,要把房間也釋放掉,也就是在 delloc 里邊對房間再進行一次 release 操作。

這樣對房間對象來說,每一次 retain / alloc 操作都對應一次 release 操作。

- (void)dealloc {// 人釋放了, 那么房間也需要釋放[_room release];NSLog(@"%s", __func__);[super dealloc]; }

最終內存情況變為了:

一個玩家使用一個房間 r 后,換到另一個房間 r2 的情況

#import <Foundation/Foundation.h> #import "Person.h" #import "Room.h"int main(int argc, const char * argv[]) {@autoreleasepool {Room *r = [[Room alloc] init];Person *p = [[Person alloc] init];r.number = 808;// 將房間賦值給玩家,表示玩家在使用房間// 玩家需要使用這間房,只要玩家在,房間就一定要在p.room = r;[r release];Room *r2 = [[Room alloc] init];r2.number = 404;p.room = r2;[r2 release]; // 釋放房間 r2// 在這行代碼之前,玩家都沒有被釋放,但是因為玩家還在,那么房間就不能銷毀[p release];}return 0; }

在第一個Room實例對象釋放后,內存情況為:

接著我們進行了第二個房間的創建以及Person實例對象通過setter方法持有第二個Room實例對象。此時我們的內存情況變為了:

在我們執行完所有代碼,我們可以發現內存情況變為了:

此時為什么r還持有Room的實例對象呢,原因其實很簡單,我們調用了兩次Person的setter方法但是只delloc了一次,問題出在哪呢?當r釋放的時候,我們的p并沒有釋放,所以不會調用delloc方法,所以就造成了上述結果,那我們應該怎么辦呢?我們可以在調用 setter 方法的時候,對之前的變量進行一次 release 操作。具體 setter 方法代碼如下:

- (void)setRoom:(Room *)room { // room = r// 將以前的房間釋放掉 -1[_room release];// 對房間的引用計數器 +1[room retain];_room = room;} }

這樣我們在第二次調用setter方法的時候會先將之前通過setter方法增加的引用計數減掉,就不會出現剛剛那種情況了。

所以內存情況就變為了:

一個玩家使用一個房間,不再使用房間,將房間釋放掉之后,再次使用該房間的情況

int main(int argc, const char * argv[]) {@autoreleasepool {// 1. 創建兩個對象Person *p = [[Person alloc] init];Room *r = [[Room alloc] init];r.number = 808;// 2. 將房間 r 賦值給玩家 pp.room = r; // [p setRoom:r][r release]; // 釋放房間 r// 3. 再次使用房間 rp.room = r;[r release]; // 釋放房間 r[p release]; // 釋放玩家 p}return 0; }

執行完以下代碼:

// 1.創建兩個對象 Person *p = [[Person alloc] init]; Room *r = [[Room alloc] init]; r.number = 808;// 2.將房間賦值給人 p.room = r; // [p setRoom:r] [r release]; // 釋放房間 r

內存情況為:

然后再執行 p.room = r;,因為 setter 方法會將之前的 Room 實例對象先釋放掉,所以此時內存表現為:

此時 _room、r 已經變成了一個野指針。之后再對野指針 r 發出 retain 消息,程序就會崩潰。所以我們在進行 setter 方法的時候,要先判斷一下是否是重復賦值,如果是同一個實例對象,就不需要重復進行 release 和 retain。換句話說,如果我們使用的還是之前的房間,那換房的時候就不需要對這個房間再進行 release 和 retain。則 setter 方法具體代碼如下:

- (void)setRoom:(Room *)room { // room = r// 只有房間不同才需用 release 和 retainif (_room != room) { // 0ffe1 != 0ffe1// 將以前的房間釋放掉 -1[_room release];// 對房間的引用計數器+1[room retain];_room = room;} }

因為 retain 不僅僅會對引用計數器 +1, 而且還會返回當前對象,所以上述代碼可最終簡化成:

- (void)setRoom:(Room *)room { // room = r// 只有房間不同才需用 release 和 retainif (_room != room) { // 0ffe1 != 0ffe1// 將以前的房間釋放掉 -1[_room release];_room = [room retain];} }

所以就這樣我們得到了setter最終的形式,這也是多個對象內存管理的思想。

MRC需要注意的一些知識點

@property 參數

  • 在成員變量前加上 @property,系統就會自動幫我們生成基本的 setter / getter 方法,但是不會生成內存管理相關的代碼。
@property (nonatomic) int val;
  • 同樣如果在 property 后邊加上 assign,系統也不會幫我們生成 setter 方法內存管理的代碼,僅僅只會生成普通的 getter / setter 方法,默認什么都不寫就是 assign。
@property(nonatomic, assign) int val;
  • 如果在 property 后邊加上 retain,系統就會自動幫我們生成 getter / setter 方法內存管理的代碼,但是仍需要我們自己重寫 dealloc 方法。
@property(nonatomic, retain) Room *room;

自動釋放池(AutoreleasePool)

以前學MRC的時候了解過一點自動釋放池但是沒有做過系統的總結,今天總結一下關于自動釋放池的一些知識點

當我們不再使用一個對象的時候應該將其空間釋放,但是有時候我們不知道何時應該將其釋放。為了解決這個問題,Objective-C 提供了 autorelease 方法。

  • autorelease 是一種支持引用計數的內存管理方式,只要給對象發送一條 autorelease 消息,會將對象放到一個自動釋放池中,當自動釋放池被銷毀時,會對池子里面的「所有對象」做一次 release 操作。

注意:這里只是發送 release 消息,如果當時的引用計數(reference-counted)依然不為 0,則該對象依然不會被釋放

  • autorelease 方法會返回對象本身,且調用完 autorelease 方法后,對象的計數器不變。
Person *p = [Person new]; p = [p autorelease]; NSLog(@"count = %lu", [p retainCount]); // 計數還為 1

使用 autorelease 有什么好處呢?

  • 不用再關心對象釋放的時間
  • 不用再關心什么時候調用release

autorelease 的原理實質上是什么?

autorelease 實際上只是把對 release 的調用延遲了,對于每一個 autorelease,系統只是把該對象放入了當前的 autorelease pool 中,當該 pool 被釋放時,該 pool 中的所有對象會被調用 release 方法。

autorelease 的創建方法

  • 使用 NSAutoreleasePool 創建
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // 創建自動釋放池 [pool release]; // [pool drain]; 銷毀自動釋放池
  • 使用 @autoreleasepool 創建
@autoreleasepool { // 開始代表創建自動釋放池} // 結束代表銷毀自動釋放池

autorelease的使用方法

NSAutoreleasePool *autoreleasePool = [[NSAutoreleasePool alloc] init]; Person *p = [[[Person alloc] init] autorelease]; [autoreleasePool drain]; @autoreleasepool { // 創建一個自動釋放池Person *p = [[Person new] autorelease];// 將代碼寫到這里就放入了自動釋放池 } // 銷毀自動釋放池(會給池子中所有對象發送一條 release 消息)

autorelease 的注意事項

  • 并不是放到自動釋放池代碼中,都會自動加入到自動釋放池
@autoreleasepool {// 因為沒有調用 autorelease 方法,所以對象沒有加入到自動釋放池Person *p = [[Person alloc] init];[p run]; }
  • 在自動釋放池的外部發送 autorelease 不會被加入到自動釋放池中
    • autorelease 是一個方法,只有在自動釋放池中調用才有效。
@autoreleasepool { } // 沒有與之對應的自動釋放池, 只有在自動釋放池中調用autorelease才會放到釋放池 Person *p = [[[Person alloc] init] autorelease]; [p run];// 正確寫法 @autoreleasepool {Person *p = [[[Person alloc] init] autorelease];}// 正確寫法 Person *p = [[Person alloc] init]; @autoreleasepool {[p autorelease]; }

自動釋放池的嵌套使用

  • 自動釋放池是以棧的形式存在
  • 由于棧只有一個入口,所以調用 autorelease 會將對象放到棧頂的自動釋放池(棧頂就是離調用 autorelease 方法最近的自動釋放池
@autoreleasepool { // 棧底自動釋放池@autoreleasepool {@autoreleasepool { // 棧頂自動釋放池Person *p = [[[Person alloc] init] autorelease];}Person *p = [[[Person alloc] init] autorelease];} }
  • 自動釋放池中不適宜放占用內存比較大的對象
    • 盡量避免對大內存使用該方法,對于這種延遲釋放機制,還是盡量少用
    • 不要把大量循環操作放到同一個 @autoreleasepool 之間,這樣會造成內存峰值的上升。
// 內存暴漲 @autoreleasepool {for (int i = 0; i < 99999; ++i) {Person *p = [[[Person alloc] init] autorelease];} }// 內存不會暴漲 for (int i = 0; i < 99999; ++i) {@autoreleasepool {Person *p = [[[Person alloc] init] autorelease];} }

autorelease 錯誤用法

  • 不要連續調用 autorelease
@autoreleasepool {// 錯誤寫法, 過度釋放Person *p = [[[[Person alloc] init] autorelease] autorelease];}
  • 調用 autorelease 后又調用 release(錯誤)
@autoreleasepool {Person *p = [[[Person alloc] init] autorelease];[p release]; // 錯誤寫法, 過度釋放 }

MRC 中避免循環引用

定義兩個類 Person 類和 Dog 類

Person 類:

#import <Foundation/Foundation.h> @class Dog;@interface Person : NSObject @property(nonatomic, retain)Dog *dog; @end

Dog類:

#import <Foundation/Foundation.h> @class Person;@interface Dog : NSObject @property(nonatomic, retain)Person *owner; @end int main(int argc, const char * argv[]) {Person *p = [Person new];Dog *d = [Dog new];p.dog = d; // retaind.owner = p; // retain assign[p release];[d release];return 0; }

我們看上面的代碼,會出現 A 對象要擁有 B 對象,而 B 對應又要擁有 A 對象,此時會形成循環 retain,導致 A 對象和 B 對象永遠無法釋放。

那我們應該怎么辦呢:

  • 不要讓 A retain B,B retain A,所以其中一方不要做retain方法
  • 當兩端互相引用時,應該一端用 retain,一端用 assign。

總結

以上是生活随笔為你收集整理的【iOS开发】——MRC(手动内存管理)的一些补充的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 国产精品18久久久久久vr下载 | 天堂va在线| 日韩国产成人 | 亚洲精品小说 | 97国产精品人人爽人人做 | 男人天堂亚洲天堂 | 欧美在线免费观看 | 在线激情av| 尤物网站在线播放 | 怡春院视频| 久久福利电影 | 中文字幕欧美专区 | 久久久久蜜桃 | 伊人3| 亚洲国产色图 | av小说免费在线观看 | 久久久久国产一区二区 | 久草网在线观看 | 亚洲一区二区三区高清 | 日日草天天干 | 91在线精品播放 | 在线观看免费日韩av | 夜夜夜夜夜操 | 视频在线观看网站免费 | 国产三级a | 天天插天天干 | 30一40一50老女人毛片 | 中文字幕欧美一区 | 成人二区三区 | 强行糟蹋人妻hd中文字幕 | 欧洲天堂网 | 欧美女优一区 | 精品久久久久久久久久久久久久 | 亚洲精品一区二区二区 | 中文字幕二区 | 三上悠亚激情av一区二区三区 | 免费看黄色大片 | 老太太av| 狠狠干狠狠插 | 国产高清免费av | 日本美女一区 | 尤物视频在线播放 | 亚洲国产果冻传媒av在线观看 | 中文字幕高清视频 | 国产无精乱码一区二区三区 | 精品孕妇一区二区三区 | 少妇高潮一区二区三区99 | 尤物精品| 午夜免费福利影院 | 亚洲视频图片 | 欧美少妇精品 | 日韩一区免费视频 | www.youjizz.com视频| 波多野结衣av无码 | 欧美性高潮 | 一区二区三区免费 | 欧美视频精品在线 | 国产精品免费无码 | 奇米第四色777 | 国产第2页| 狠狠干欧美 | 99re这里只有| 1000部啪啪未满十八勿入 | 免费欧美 | 乌克兰极品av女神 | 香港一级淫片免费放 | 女同av在线| 九九九视频在线观看 | 国产精品探花在线观看 | 天天碰天天 | 午夜嘿嘿 | 在线成人一区 | 亚洲精品国偷拍自产在线观看蜜桃 | 亚洲免费精品视频 | 欧美色图亚洲自拍 | 日韩在线第二页 | 性久久久久久久 | 天天艹天天操 | 亚洲字幕成人中文在线观看 | 国产毛片久久久久 | 色xxxx| 啪啪福利视频 | 99免费在线观看 | 后进极品美女白嫩翘臀 | 久久久久久久女国产乱让韩 | 午夜吃瓜 | 麻豆国产一区二区三区四区 | 大粗鳮巴久久久久久久久 | 自拍天堂 | 亚洲а∨天堂久久精品2021 | 一道本av在线 | 日韩理论片在线观看 | 国产一区二区三区四区五区在线 | 麻豆精品免费视频 | 奴色虐av一区二区三区 | 性自由色xxxx免费视频 | 99久久久久久久 | 对白刺激theporn | 久久久久97 |