OC開發系列-記憶體管理

来源:https://www.cnblogs.com/CoderHong/archive/2018/04/14/8826627.html
-Advertisement-
Play Games

概述 移動設備的記憶體極其有限,每個app所有占用的記憶體是有限的。當app所占用的記憶體比較多時,系統會發出記憶體警告,這時得回收一些不需要再使用的記憶體空間。 "任何集成了NSObject的對象都需要手動進行記憶體管理。因為OC對象存放於堆裡面。" 引用計數器 每一個OC對象都有內部有自己的引用計數器。該計 ...


概述

移動設備的記憶體極其有限,每個app所有占用的記憶體是有限的。當app所占用的記憶體比較多時,系統會發出記憶體警告,這時得回收一些不需要再使用的記憶體空間。

任何集成了NSObject的對象都需要手動進行記憶體管理。因為OC對象存放於堆裡面。

引用計數器

  • 每一個OC對象都有內部有自己的引用計數器。該計數器占用4個位元組。從字面可以理解為"多少人在使用這個對象"。
  • 當對象的引用計數器為0時。該對象會才會被釋放。
  • 一個對象通過alloc、 copy 、new創建一個對象,該對象預設的引用計數器為1。

引用計數器的操作

* 給對象發送一條retain消息,對象的引用計數器+1
* 給對象發送一條release消息,對象的引用計數器-1
* 給對象發送一條retainCount消息,對獲得當前對象的引用計數器值

dealloc方法

當一個對象的引用計數器值為0時,這個對象即將被銷毀,器占用的記憶體被系統回收。對象即將被銷毀前系統會自動給改對象發送一條dealloc消息。
通常重寫對象的dealloc方法釋放相關的資源。
* 一旦重寫了dealloc方法,就必須調用[super dealloc],並且放在最後面
* dealloc不能直接調用
* 一旦對象被回收,它占用的記憶體就不再可用被稱之為僵屍對象。指向僵屍對象的指針被稱為野指針。給僵屍對象發消息會造壞訪問報錯。

多對象的記憶體管理

多個對象進行記憶體管理,並且對象之間是有聯繫的,那麼管理就會變得比較複雜。
比如在記憶體中有一個聊天室對象,多個Person對象共同使用者聊天室對象。

對上面的實例我們如果想做好聊天室對象跟Person對象的記憶體管理。需要做到下麵兩點
* 只要有人在使用聊天室對象聊天室對象就不能銷毀
* 沒有人使用聊天室對象聊天室對象需要銷毀

當有Person對象使用聊天室就需要對聊天室對象的引用計數器+1。且當Person對象銷毀之前需要對使用的房間對象引用計數器-1,這樣就可以保證以上兩點。
因此可以這樣定義一個Person類

#import <Foundation/Foundation.h>
#import "ChatRoom.h"
@interface Person : NSObject
{
    ChatRoom *_cRoom;
}
- (void)setCRoom:(ChatRoom *)cRoom;
- (ChatRoom *)cRoom;
@end

#import "Person.h"

@implementation Person
- (void)setCRoom:(ChatRoom *)cRoom
{
    if (_cRoom != cRoom) {
        // 1.先對以前的對象計數器-1
        [_cRoom release];
        // 2.調用對象的retain返回對象本身
        _cRoom = [_cRoom retain];
    }
}

- (ChatRoom *)cRoom
{
    return _cRoom;
}

- (void)dealloc
{
    // 在Person對象即將銷毀先對內部的聊天室對象計數器-1
    [_cRoom release];
    [super dealloc];
}
@end

Property修飾符

assign

預設Proerty修飾符為assig。比如當為Person聲明一個成員屬性@property int age; 編譯器自動轉換成@property(assign) int age;

retain

Proerty修飾符為retain在生成的set方法會自動生成set方法的記憶體管理代碼

@property (retain) ChatRoom *cRoom;

retain修飾生成的set方法:

- (void)setCRoom:(ChatRoom *)cRoom
{
    if (_cRoom != cRoom) {
        // 1.先對以前的對象計數器-1
        [_cRoom release];
        // 2. 調用對象的retain返回對象本身
        _cRoom = [_cRoom retain];
    }
}

nonatomic與nonnull

nonatomic是保證set方法是線程安全的,nonnull則相反。iOS開發一般使用nonatomic性能高。

@class

@class主要用於簡單的引用一個類。@class與#import有著本質的區別,#import是預處理指令在程式預處理時期對#import的文件進行拷貝,而@class只是告訴編譯器@class後面引用的是一個類

import有一個特點,只要引用的文件發生的變化,那麼import就會重新拷貝一次。比如現在有三個類Person、 Car、 Wheel。Person.h文件import Car.h,Car.h文件import Wheel.h。一旦wheels Wheel.h發生變化,Car.h需要重新拷貝Wheel.h並且Person.h也重新拷貝Car.h。

@class具體使用
* 在.h文件中使用@class引用一個類
* 在.m文件中使用#import包含這個類的.h文件

@class其他使用場景

retain的迴圈引用

現在有兩個類Person與Car。Person中有個成員屬性car,Car中有個成員屬性person。

Person類

#import <Foundation/Foundation.h>
@class Car;
@interface Person : NSObject

@property (retain) Car *car;
@end

#import "Person.h"
@implementation Person

- (void)dealloc
{
    [_car release];
    
    [super dealloc];
    NSLog(@"%s", __func__);
}
@end

Car類

#import <Foundation/Foundation.h>
@class Person;
@interface Car : NSObject

@property (retain) Person *per;
@end

#import "Car.h"
@implementation Car

- (void)dealloc
{
    
    [_per release];
    
    [super dealloc];
    
    NSLog(@"%s", __func__);
}
@end

main.m

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Car.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Person *p = [[Person alloc] init];
        Car *c = [[Car alloc] init];
        
        // 人賦值一輛車對象
        p.car = c;
        // 車賦值人對象
        c.per = p;
        
        [p release];
        [c release];
    }
    return 0;
}

main.m代碼執行完控制台並沒有Person對象與Car對象dealloc方法的列印。因此記憶體兩個對象並未銷毀存在記憶體泄漏。下麵畫圖展現main.m執行完畢記憶體的情況。

為什麼執行p跟c的release方法,對象沒有銷毀。因為在給人對象賦值car對象的時候通過set方法會對car對象引用計數器+1,同理對car對象賦值人對象,人對象引用計數器也會+1。

分別執行兩個對象release只是讓對象應用計數器-1,因此兩個對象都沒有銷毀。

那麼我們可以嘗試讓其中一個類Property修飾符修改為assign。註意:修改為assign修飾一方dealloc千萬不要在dealloc對對象屬性進行release。否則會造成記憶體壞訪問
此時的Person類

#import <Foundation/Foundation.h>
@class Car;
@interface Person : NSObject
// 改為assign修飾
@property (assign) Car *car;

@end

#import "Person.h"
@implementation Person

- (void)dealloc
{
    // [_car release]; 此時Propery修飾符為assign不需要對car進行release
    [super dealloc];
    NSLog(@"%s", __func__);
}
@end

autorelease

通過上面講解,OC多對象的記憶體管理主要有以下幾點:
* 1.在外界通過alloc、copy、new關鍵字創建的對象需要釋放的時候發送release消息
* 2.在set方法中對賦值的對象引用計數器+1,可以使用retain關鍵字自動生成set方法記憶體管理代碼
* 3.在一個對象即將要銷毀前在dealloc方法中對內部引用的對象進行release。

這裡就會發現,我們需要時時刻刻關註對象什麼時候需要銷毀時發送release消息。接下來就可以通過autorelease的機制來解決手動編寫大量的對象的release代碼。不需要一直再關註對象什麼時候需要release。只要可以訪問到對象的作用域都可以使用對象。

autorelease是一種支持引用計數器的記憶體管理方式,只要給對象發送一條autorelease消息會將對象放到自動釋放池中。當自動釋放池銷毀,會對池子中的所有對象發送一條release消息。autorelease操作對象的引用計數器。當一個對象發送release消息返回時對象的本身。

#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Person *p = [[Person alloc] init];
        // 將對象p添加到autorelease釋放池
        p = [p autorelease];
        
        // [p release]; 這裡不需要在寫release 否則壞訪問
    }// 自動釋放池銷毀會給對象中所有的對象發送一條release消息
    return 0;
}

在iOS5.0之前autorelease的寫法

int main(int argc, const char * argv[]) {
    
    // 創建一個自動釋放池
    NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init];
    
        Person *p = [[Person alloc] init];
        // 將對象p添加到autorelease釋放池
        p = [p autorelease];
    
    [pool drain]; // 自動釋放池銷毀會給對象中所有的對象發送一條release消息
    return 0;
}

MRC類工廠方法的編寫

我們在初始化一個對象將它添加進自動釋放池中

@autoreleasepool{
        // 創建一個對象
        Person *p = [[[Person alloc] init] autorelease];
    }

可見每次創建一個Person對象都需要寫很長的代碼,於是我們可以封裝內部細節提供一個類工廠方法

/**快速創建一個Person實例*/
+ (instancetype)person;

+ (instancetype)person
{
    return [[[self alloc] init] autorelease];
}

外界調用類工廠對象初始化對象

int main(int argc, const char * argv[]) {
    
    @autoreleasepool{
        // 創建一個對象
        Person *p = [[[Person alloc] init] autorelease];
        
        // 通過類工廠方法 創建的對象預設添加進自動釋放池當中
        Person *p1 = [Person person];
    }
    return 0;
}

我們想讓Person在初始化後就有一個預設的名字。我們可以提供自定義構造方法。

#import <Foundation/Foundation.h>
@interface Person : NSObject

@property(nonatomic, copy) NSString *name;
/**快速創建一個Person實例*/
+ (instancetype)person;

/**自定義構造方法*/
-(instancetype)initWithName:(NSString *)name;
+(instancetype)personWithName:(NSString *)name;
@end

#import "Person.h"
@implementation Person
/**自定義構造方法*/
-(instancetype)initWithName:(NSString *)name
{
    if(self = [super init]){
        self.name = name;
    }
    return self;
}

+ (instancetype)person
{
    return [[[self alloc] init] autorelease];
}

+(instancetype)personWithName:(NSString *)name
{
    // 類方法中將對象添加進自動釋放池中
    return [[self initWithName:name] autorelease];
}
@end

上面的代碼會發現在MRC類方法中生成的對象預設內部將其添加自動釋放池當中,其實這也是蘋果的規範。蘋果Foundation框架中通過類方法創建的對象都是autorelease。

ARC的記憶體管理

ARC是編譯器的特性,而不是運行時特性,跟其他語言的垃圾回收有著本質的區別。ARC原理就是在編譯階段為我們在必要的時候添加release這些代碼。
ARC的判斷原則
* 只要還有一個強指針指向對象,對象就會保持在記憶體中

ARC與MRC的混編

* 在ARC項目中執行某個文件使用MRC -fno-objc-arc
* 在MRC項目中執行某個文件使用ARC -fobjc-arc

使用Xcode將一個MRC工程轉化成ARC項目


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 據我觀察,中國的開發者創造了一種獨特的SQL發音:/'sɜːkl/,既好聽,又好讀,挺好的。但是今年我開始做資料庫相關的工作,作為一個專業人士,決定對SQL發音進行一些考證。 直接說結論吧,很多人沿用了/ˈsiːkwəl/這個讀音,因為這門語言以前叫做“SEQUEL”。但更官方一些的讀音應該是ISO ...
  • 前言: sql註入想學好,學通。必須得瞭解一下基礎的SQL 語句。這裡我快速理一理 正文: 搭建環境建議下phpsduy快速搭建 select * from kasi select 欄位名 from 表名 查詢表中的所有欄位 結果 查詢指定表名 select 書籍名 from kasi 結果 sel ...
  • 從github獲取插件包例如Head 在elasticsearch安裝目錄中創建插件存放目錄 重啟elasticsearch服務 在瀏覽器訪問 訪問地址是http://{你的ip地址}:9200/_plugin/head/ 更加詳細細節請訪問 https://www.sojson.com/blog/ ...
  • 本文內容: mysql函數的介紹 聚集函數 avg count max min sum 用於處理字元串的函數 合併字元串函數:concat(str1,str2,str3…) 比較字元串大小函數:strcmp(str1,str2) 獲取字元串位元組數函數:length(str) 獲取字元串字元數函數:c... ...
  • Sqlite下載頁面:http://www.sqlite.org/download.html Windows安裝 需要下載 sqlite-tools-win32-*.zip 和 sqlite-dll-win32-*.zip 壓縮文件。 創建文件夾 C:\sqlite,併在此文件夾下解壓上面兩個壓縮文 ...
  • 查詢所有子節點 查詢所有父節點調換顏色部分 ...
  • DDL創造 create 修改 alter 刪除 dorp DML插入 insert 刪除 delete 更新 updateDQLselect from where MySQL與JDBC 一、用命令行對資料庫的操作 1. 創建一個庫 create database 庫名 create databas ...
  • 1.創建活動 首先用AS創建一個add no activity項目名使用ActivityTest,包名為預設的com.example.activitytest 2.右擊app.java.com.example.activitytest包,new-->Activity-->Empty Activity ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...