iOS arc 記憶體管理

John_LS發表於2016-02-16

Cocoa記憶體管理機制

(1)當你使用new、alloc、copy方法建立一個物件時,該物件的保留計數器值為1.當不再使用該物件時,你要負責向該物件傳送一條release或autorelease訊息。這樣,該物件將在其使用壽命結束時被銷燬。
(2)當你通過其他方法獲得一個物件時,這假設該物件的保留計數器值為1,而且已經被設定為自動釋放,你不需要執行任何操作來確保該物件被清理。如果你打算在一段時間內擁有該物件,這需要保留它並確保在操作完成時釋放它。
(3)如果你保留了某個物件,你需要(最終)釋放或自動釋放該物件。必須保持retain方法和release方法的使用次數相等。

如果我使用了new、alloc或copy方法獲得一個物件,則我必須釋放或自動釋放該物件。”只要你記住了這條規律,你就平安無事了。
無論什麼時候擁有一個物件,有兩間事情必須弄清楚:怎樣獲得該物件的?打算擁有該物件多長時間。

 

Objective-C的物件生成於堆之上,生成之後,需要一個指標來指向它。

alloc:為一個新物件分配記憶體,並且它的引用計數為1。呼叫alloc方法,你便有對新物件的所有權

copy:製造一個物件的副本(克隆體),該副本的引用計數為1,呼叫者具有對副本的所有權

retain:使物件的引用計數加1,並且獲得物件的所有權

release:使物件的引用計數減1,並且放棄物件的所有權

autorelease:使物件的引用計數在未來的某個時候減1,並且在那個時候放棄物件的所有

 

自動引用計數(ARC),是一項為Objective - C程式在編譯時提供自動記憶體管理的功能。ARC可以讓你把注意力集中在你感興趣的程式碼,物件圖,和你的應用程式中的物件之間的關係,讓你不必再花費精力在retain和release操作上。正如下圖所示,ARC可以減少開發中的記憶體管理步驟,簡化開發。

 

 



ARC官方文件修訂歷史

This table describes the changes to Transitioning to ARC Release Notes.

Date

Notes

2012-07-17

Updated for OS X v10.8.

2012-03-14

Noted that under ARC properties are strong by default.

2012-02-16

Corrected out-of-date advice regarding C++ integration.

2012-01-09

Added note to search for weak references.

2011-10-12

First version of a document that describes how to transition code from manual retain/release to use ARC.

 

ARC Support Iphone Os 4.0 or later.

“parent” object should maintain strong references to its “children,” and that the children should have weak references to their parents.

You need to be careful about sending messages to objects for which you hold only a weak reference. If you send a message to an object after it has been deallocated, your application will crash. You must have well-defined conditions for when the object is valid.

 

 

    使用ARC必須遵守的規則

 

l        不可以再顯示呼叫dealloc、或實現呼叫retain、release、retainCount、autorelease這些方法。也不能使用@selector(retain)@selector(release),等等。

在ARC下去自定義dealloc方法不需要呼叫 [super dealloc],(實際上如果你呼叫了 [super dealloc],編譯器會報錯)。super的呼叫是由編譯器自動強制執行的。

l        不能使用NSAllocateObjectNSDeallocateObject

使用alloc來建立物件,由ARC來管理物件執行時的釋放。

l        不能在C語言的結構體中使用物件指標。

建議使用Objective-C的class來管理資料格式,來代替C語言的struct

l        不能隱式轉換 id和void *。

你必須告訴編譯器轉換的型別。當你需要在obj-C的物件和Core Foundation 型別之間轉換時,你可以通過函式的引數來做。詳見“Managing Toll-Free Bridging”

l        不能使用NSAutoreleasePool物件。

l        不能使用memory Zone。

因為現在Objective-C執行時已經忽略NSZone了,所以沒必要再使用NSZone了

Property 屬性

l        assign: 簡單賦值,不更改索引計數(Reference Counting)。

l        copy: 建立一個索引計數為1的物件,然後釋放舊物件(開闢新的記憶體地址)

l        retain:釋放舊的物件,將舊物件的值賦予輸入物件,再提高輸入物件的索引計數為1

retain的實際語法為:

- (void)setName:(NSString *)newName {

    if (name != newName) {

       [name release];

       name = [newName retain];

       // name’s retain count has been bumped up by 1

    }

}

說了那麼麻煩,其實接下來的話最重要:

如果你不懂怎麼使用他們,那麼就這樣

·         使用assign: 對基礎資料型別 (NSInteger,CGFloat)和C資料型別(int, float, double, char, 等等)

·         使用copy: 對NSString

·         使用retain: 對其他NSObject和其子類

l        nonatomic關鍵字:

atomic是Objc使用的一種執行緒保護技術,基本上來講,是防止在寫未完成的時候被另外一個執行緒讀取,造成資料錯誤。而這種機制是耗費系統資源的,所以在iPhone這種小型裝置上,如果沒有使用多執行緒間的通訊程式設計,那麼nonatomic是一個非常好的選擇。

iOS 5 中對屬性的設定新增了strong 和weak關鍵字來修飾屬性(iOS 5 之前不支援ARC)

l        strong關鍵字:

strong 用來修飾強引用的屬性;對應原來的retain

該屬性值對應 __strong 關鍵字,即該屬性所宣告的變數將成為物件的持有者。

l        weak關鍵字:

weak 用來修飾弱引用的屬性;對應原來的assign。

但是不同的是當物件被釋放以後,物件自動賦值為nil;並且,delegate 和 Outlet 蘋果推薦用 weak 屬性來宣告。同時,如上一回介紹的 iOS 5 之前的版本是沒有 __weak 關鍵字的,所以 weak 屬性是不能使用的。這種情況我們使用 unsafe_unretained。

 

OSMemoryNotification.h(記憶體警告)   


/*
 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
 
#ifndef _OSMEMORYNOTIFICATION_H_
#define _OSMEMORYNOTIFICATION_H_
 
#include <sys/cdefs.h>
 
/*
**  OSMemoryNotification.h
**  
**  Kernel-generated notification mechanism to alert registered tasks when physical memory
**  pressure reaches certain thresholds. Notifications are triggered in both directions
**  so clients can manage their memory usage more and less aggressively.
**
*/
 
__BEGIN_DECLS
 
struct timeval;
 
/*
** Opaque type for notification object
*/
 
typedef struct _OSMemoryNotification * OSMemoryNotificationRef;
 
/*
** Threshold values for notifications
*/
 
typedef enum {
        OSMemoryNotificationLevelAny      = -1,
        OSMemoryNotificationLevelNormal   =  0,
        OSMemoryNotificationLevelWarning  =  1,
        OSMemoryNotificationLevelUrgent   =  2,
        OSMemoryNotificationLevelCritical =  3
} OSMemoryNotificationLevel;
 
/*
** Creation routines. Returns the created OSMemoryNotificationRef in the note param.
** returns: 0 on success
**          ENOMEM if insufficient memory or resources exists to create the notification object
**          EINVAL if the threshold is not a valid notification level
*/
 
int OSMemoryNotificationCreate(OSMemoryNotificationRef *note);
 
/*
** returns: 0 on success
**          EINVAL if the notification is not an initialized notification object
*/
 
int OSMemoryNotificationDestroy(OSMemoryNotificationRef note);
 
/*
** Block waiting for notification
** returns: 0 on success, with the level that triggered the notification in the level param
**          EINVAL if the notification object is invalid
**          ETIMEDOUT if abstime passes before notification occurs
*/
int OSMemoryNotificationWait(OSMemoryNotificationRef note, OSMemoryNotificationLevel *level);
int OSMemoryNotificationTimedWait(OSMemoryNotificationRef note, OSMemoryNotificationLevel *level, const struct timeval *abstime);
 
/*
** Simple polling interface to detect current memory pressure level
*/
 
OSMemoryNotificationLevel OSMemoryNotificationCurrentLevel(void);
 
/*
** External notify(3) string for manual notification setup
*/
 
extern const char *kOSMemoryNotificationName;
 
__END_DECLS
 
#endif /* _OSMEMORYNOTIFICATION_H_ */

 

 

 

 

 

#import <libkern/OSMemoryNotification.h>

 

- (void)didReceiveMemoryWarning
 {
     NSLog(@"Recieve memory warning");
     NSLog(@"~~~~~~~~~~~~~~level~~~~~~~~~~~~~~~ %d", (int)OSMemoryNotificationCurrentLevel());
 }

 

程式通常情況下都先呼叫AppDelegate中的applicationDidReceiveMemoryWarning, 然後程式會通知各ViewController,呼叫其didRecieveMemoryWarning方法

 

為單獨檔案指定是否使用ARC 

當你遷移一個久工程到ARC模式下, -fobjc-arc 編譯開關被預設的設定在所有的Objective-C 原始碼上。 你可以使用-fno-objc-arc 來為特殊的class停用ARC 。在Xcode的 target的“Build Phases”標籤, 開啟Compile Sources group,展開原始碼列表, 雙擊你想要修改的原始碼的名字,再彈出框裡輸入-fno-objc-arc,然後點Done按鈕。

 

總結 

嵌入式裝置中堆疊的記憶體大小都有嚴格的限制,所以記憶體的管理是個大問題,在程式設計過程中,及時釋放我們不需要的記憶體物件,是基本原則。設計得不優雅的程式,可能會出現一系列的,你無可預料的問題,比如記憶體溢位,物件過早釋放,導致程式直接crash。

    ARC技術雖然能提供自動引用技術,省掉了讓人煩人和容易遺漏的retain,release,autorelease等操作,其工作原理是將記憶體操作的程式碼(retain,release等)自動新增到需要的位置。即底層上使用和MRC手工引用技術一樣的記憶體管理機制,所以使用ARC簡化編碼工作的同時,還是同樣要對記憶體管理有深入的瞭解。

ARC技術和跟隨Xcode4.2一起釋出的,在預設的工程模板裡可以選擇是否支援ARC技術。隨著 iOS 5.1 的推出,Xcode也推出了4.3版本。在該版本下,ARC 有效時的屬性(@property) 定義的時候,如果不明確指定所有權關鍵字,那麼預設的就是 strong。而在 Xcode4.2 中,即使 strong 也要顯示指定。


arc下的記憶體洩露:
>>因為當一個物件存入到集合中的時候,預設會儲存它的強指標,如果最後不對這個集合進行清空操作,一樣會有記憶體溢位的情況
Person * p = [[Person alloc] init];
NSMutableArray * arr = [[NSMutableArray alloc] init];
[arr addObject:p];
把物件從集合中移除的時候,也會釋放掉這個物件的強指標
[arr removeObject:p];
或者[arr removeAllObjects];
而接下來才是重點:
arr = nil;//如果不進行賦值為nil的操作,一樣存在記憶體溢位的現象,賦值為nil系統會對其進行清空所有強指標的操作.
p = nil;

下面列舉兩種ARC導致記憶體洩露的情況。

1,迴圈參照

A有個屬性參照B,B有個屬性參照A,如果都是strong參照的話,兩個物件都無法釋放。

這種問題常發生於把delegate宣告為strong屬性了。

例,

@interface SampleViewController

@property (nonatomic, strong) SampleClass *sampleClass;

@end

@interface SampleClass

@property (nonatomic, strong) SampleViewController *delegate;

@end

 

上例中,解決辦法是把SampleClass 的delegate屬性的strong改為assing即可。

 

2,死迴圈

如果某個ViewController中有無限迴圈,也會導致即使ViewController對應的view關掉了,ViewController也不能被釋放。

這種問題常發生於animation處理。

例,

比如,

CATransition *transition = [CATransition animation];

transition.duration = 0.5;

tansition.repeatCount = HUGE_VALL;

[self.view.layer addAnimation:transition forKey:"myAnimation"];

 

上例中,animation重複次數設成HUGE_VALL,一個很大的數值,基本上等於無限迴圈了。

解決辦法是,在ViewController關掉的時候,停止這個animation。

-(void)viewWillDisappear:(BOOL)animated {

    [self.view.layer removeAllAnimations];

}

 

記憶體洩露的情況當然不止以上兩種。

即使用了ARC,我們也要深刻理解iOS的記憶體管理機制,這樣才能有效避免記憶體洩露。

arc的程式出現記憶體洩露怎辦

例項一:

用arc和非arc混編,非arc的類在arc裡例項化並且使用,在arc里居然出現記憶體洩露,而且應為是arc,所以無法使用release,autorelease和dealloc去管理記憶體。正常情況下應該是不會出現這種情況的,某一個類若是ARC,則在這個類裡面都應該遵循ARC的用法,而無需關心用到的類是否是ARC的,同樣,在非ARC類裡面,就需要遵循記憶體管理原則。


用ARC,只是編譯器幫你管理了何時去release,retain,不用ARC就需要你自己去管理,說到底只是誰去管理的問題,所以你再好好看看,可能問題與ARC無關。
如果實在找不到問題,建議你找到洩露的那個物件,將其賦值為nil,因為ARC裡面,一旦物件沒有指標指向,就會馬上被釋放。
 

例項二:


最近在學objective-c,我發現建立專案時如果使用了ARC,非常容易記憶體洩露,經常某個物件已經被釋放掉了我還在使用,由於不太瞭解這個機制,現在我舉出兩個例子,請經驗者幫我分析一下。
例子一:一開始,在AppDelegate.m的那個開始方法中時這樣寫的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];    
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    //
    UITabBarController  tabBarController = [[UITabBarController alloc] init];
    [tabBarController setViewControllers:[self showConnectViewOnWindow]];
    [tabBarController setDelegate:self];
    //
    [[self window] addSubview: [tabBarController view]];
     
    [self.window makeKeyAndVisible];
    return YES;
}

然後,我還做了其他的工作:tabBarController中有tabBarItem,點選會呼叫一個方法
但是每次一點選,就會報unrecognized selector send to instance的錯誤,
後來上網一查,說是要把tabBarController定義成全域性變數,不然這個方法一結束,tabBarController就被釋放掉了,這樣點選產生時間的物件都沒了,於是我把它定義成全域性變數,確實可以了,但我的疑問是,為什麼方法一結束他就會釋放掉嗎,[[self window] addSubview: [tabBarController view]];我這一句不是已經在self window裡引用它了嗎,他怎麼還會被釋放,我覺得java和C#裡面這種情況是不會釋放掉了。

1
2
3
4
5
6
7
8
例子二:在viewdidload方法裡面:
    [self.navigationItem setTitle:Title];
     
    leftButton = [[UIBarButtonItem alloc] initWithTitle:Cancel 
                                                  style:UIBarButtonItemStyleBordered 
                                                 target:self 
                                                 action:@selector(CancleButtonClicked)];
    self.navigationItem.leftBarButtonItem = leftButton;

這裡我給螢幕上方那個導航條加了一個左邊的按鈕,然後點選這個按鈕後會用方法CancleButtonClicked來響應,但是我執行起來一點選,還是報unrecognized selector send to instances錯誤了,這裡又是哪個物件釋放了,leftButton嗎?但是self.navigationItem.leftBarButtonItem = leftButton已經引用了啊。


解決方法:


例子一[[self window] addSubview: [tabBarController view]];
你只引用了tabBarController的view,沒有引用tabBarController

例子二,不知道什麼原因,看看有沒有拼寫錯誤吧。

另外,我感覺區域性變數的記憶體一般只在它的生命週期內有效。出了它所定義的區域,即使不釋放,也最好不要用了。


相關文章