Cocoa內存管理機制
(1)當你使用new、alloc、copy方法創建一個對象時,該對象的保留計數器值為1.當不再使用該對象時,你要負責向該對象發送一條release或autorelease消息。這樣,該對象將在其使用壽命結束時被銷毀。
(2)當你通過其他方法獲得一個對象時,這假設該對象的保留計數器值為1,而且已經被設置為自動釋放,你不需要執行任何操作來確保該對象被清理。如果你打算在一段時間內擁有該對象,這需要保留它并確保在操作完成時釋放它。
(3)如果你保留了某個對象,你需要(最終)釋放或自動釋放該對象。必須保持retain方法和release方法的使用次數相等。
“如果我使用了new、alloc或copy方法獲得一個對象,則我必須釋放或自動釋放該對象。”只要你記住了這條規律,你就平安無事了。
無論什么時候擁有一個對象,有兩間事情必須弄清楚:怎樣獲得該對象的?打算擁有該對象多長時間。
Objective-C的對象生成于堆之上,生成之后,需要一個指針來指向它。
(1) alloc:為一個新對象分配內存,并且它的引用計數為1。調用alloc方法,你便有對新對象的所有權
(2) copy:制造一個對象的副本(克隆體),該副本的引用計數為1,調用者具有對副本的所有權
(3) retain:使對象的引用計數加1,并且獲得對象的所有權
(4) release:使對象的引用計數減1,并且放棄對象的所有權
(5) 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必須遵守的規則
不可以再顯示調用dealloc、或實現調用retain、release、retainCount、autorelease這些方法。也不能使用@selector(retain), @selector(release),等等。
在ARC下去自定義dealloc方法不需要調用 [super dealloc],(實際上如果你調用了 [super dealloc],編譯器會報錯)。super的調用是由編譯器自動強制執行的。
不能使用NSAllocateObject或NSDeallocateObject。
使用alloc來創建對象,由ARC來管理對象運行時的釋放。
不能在C語言的結構體中使用對象指針。
建議使用Objective-C的class來管理數據格式,來代替C語言的struct。
不能隱式轉換 id和void *。
你必須告訴編譯器轉換的類型。當你需要在obj-C的對象和Core Foundation 類型之間轉換時,你可以通過函數的參數來做。詳見“Managing Toll-Free Bridging”
不能使用NSAutoreleasePool對象。
不能使用memory Zone。
因為現在Objective-C運行時已經忽略NSZone了,所以沒必要再使用NSZone了
Property 屬性
assign: 簡單賦值,不更改索引計數(Reference Counting)。
copy: 建立一個索引計數為1的對象,然后釋放舊對象(開辟新的內存地址)
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和其子類
nonatomic關鍵字:
atomic是Objc使用的一種線程保護技術,基本上來講,是防止在寫未完成的時候被另外一個線程讀取,造成數據錯誤。而這種機制是耗費系統資源的,所以在iPhone這種小型設備上,如果沒有使用多線程間的通訊編程,那么nonatomic是一個非常好的選擇。
iOS 5 中對屬性的設置新增了strong 和weak關鍵字來修飾屬性(iOS 5 之前不支持ARC)
strong關鍵字:
strong 用來修飾強引用的屬性;對應原來的retain。
該屬性值對應 __strong 關鍵字,即該屬性所聲明的變量將成為對象的持有者。
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的那個開始方法中時這樣寫的:
- (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#里面這種情況是不會釋放掉了。
例子二:在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
例子二,不知道什么原因,看看有沒有拼寫錯誤吧。
另外,我感覺局部變量的內存一般只在它的生命周期內有效。出了它所定義的區域,即使不釋放,也最好不要用了。
補充 : IOS開發 strong,weak,retain,assign,copy nomatic 等的區別與作用
一、strong,weak,retain,assign,copy nomatic 等的區別
copy與retain:
1、copy其實是建立了一個相同的對象,而retain不是;
2、copy是內容拷貝,retain是指針拷貝;
3、copy是內容的拷貝 ,對于像NSString,的確是這樣,但是如果copy的是一個NSArray呢?這時只是copy了指向array中相對應元素的指針.這便是所謂的"淺復制".
4、copy的情況:NSString *newPt = [pt copy];
此時會在堆上重新開辟一段內存存放@"abc" 比如0X1122 內容為@"abc 同時會在棧上為newPt分配空間 比如地址:0Xaacc 內容為0X1122 因此retainCount增加1供newPt來管理0X1122這段內存;
assign與retain:
1、assign: 簡單賦值,不更改索引計數;
2、assign的情況:NSString *newPt = [pt assing];
此時newPt和pt完全相同 地址都是0Xaaaa 內容為0X1111 即newPt只是pt的別名,對任何一個操作就等于對另一個操作, 因此retainCount不需要增加;
3、assign就是直接賦值;
4、retain使用了引用計數,retain引起引用計數加1, release引起引用計數減1,當引用計數為0時,dealloc函數被調用,內存被回收;
5、retain的情況:NSString *newPt = [pt retain];
此時newPt的地址不再為0Xaaaa,可能為0Xaabb 但是內容依然為0X1111。 因此newPt 和 pt 都可以管理"abc"所在的內存,因此 retainCount需要增加1 ;
readonly:
1、屬性是只讀的,默認的標記是讀寫,如果你指定了只讀,在@implementation中只需要一個讀取器。或者如果你使用@synthesize關鍵字,也是有讀取器方法被解析
readwrite:
1、說明屬性會被當成讀寫的,這也是默認屬性。設置器和讀取器都需要在@implementation中實現。如果使用@synthesize關鍵字,讀取器和設置器都會被解析;
nonatomic:
1、非原子性訪問,對屬性賦值的時候不加鎖,多線程并發訪問會提高性能。如果不加此屬性,則默認是兩個訪問方法都為原子型事務訪問;
weak and strong property (強引用和弱引用的區別):
1、 weak 和 strong 屬性只有在你打開ARC時才會被要求使用,這時你是不能使用retain release autorelease 操作的,因為ARC會自動為你做好這些操作,但是你需要在對象屬性上使用weak 和strong,其中strong就相當于retain屬性,而weak相當于assign。
2、只有一種情況你需要使用weak(默認是strong),就是為了避免retain cycles(就是父類中含有子類{父類retain了子類},子類中又調用了父類{子類又retain了父類},這樣都無法release)
3、聲明為weak的指針,指針指向的地址一旦被釋放,這些指針都將被賦值為nil。這樣的好處能有效的防止野指針。
ARC(Automatic Reference Counting):
1、就是代碼中自動加入了retain/release,原先需要手動添加的用來處理內存管理的引用計數的代碼可以自動地由編譯器完成了。
該機能在 iOS 5/ Mac OS X 10.7 開始導入,利用 Xcode4.2 以后可以使用該特性。
strong,weak,copy 具體用法:
1.具體一點:IBOutlet可以為weak,NSString為copy,Delegate一般為weak,其他的看情況。一般來說,類“內部”的屬性設置為strong,類“外部”的屬性設置為weak。說到底就是一個歸屬權的問題。小心出現循環引用導致內存無法釋放。
2.不用ARC的話就會看到很多retian。
3.如果你寫了@synthesize abc = _abc;的話,系統自動幫你聲明了一個_abc的實例變量。
使用assign: 對基礎數據類型 (NSInteger)和C數據類型(int, float, double, char,等)
使用copy: 對NSString
使用retain: 對其他NSObject和其子類
readwrite,readonly,assign,retain,copy,nonatomic屬性的作用
@property是一個屬性訪問聲明,擴號內支持以下幾個屬性:
1,getter=getterName,setter=setterName,設置setter與getter的方法名
2,readwrite,readonly,設置可供訪問級別
2,assign,setter方法直接賦值,不進行任何retain操作,為了解決原類型與環循引用問題
3,retain,setter方法對參數進行release舊值再retain新值,所有實現都是這個順序(CC上有相關資料)
4,copy,setter方法進行Copy操作,與retain處理流程一樣,先舊值release,再Copy出新的對象,retainCount為1。這是為了減少對上下文的依賴而引入的機制。
copy是在你不希望a和b共享一塊內存時會使用到。a和b各自有自己的內存。
5,nonatomic,非原子性訪問,不加同步,多線程并發訪問會提高性能。注意,如果不加此屬性,則默認是兩個訪問方法都為原子型事務訪問。鎖被加到所屬對象實例級(我是這么理解的...)。
atomic和nonatomic用來決定編譯器生成的getter和setter是否為原子操作。在多線程環境下,原子操作是必要的,否則有可能引起錯 誤的結果。
懶加載
一直知道懶加載是什么東西,但是沒怎么用過,最近準備用的時候一直不能調用懶加載的方法,最近趁著項目剛剛結束,所以有空研究了一些小東西。東西雖然很簡單,但是感覺還是挺實用的。
所謂的懶加載可以定義為:延時加載,即當對象需要用到的時候再去加載。其實就是所謂的重寫對象的get方法,當系統或者開發者調用對象的get方法時,再去加載對象。
需要注意:重寫get方法時,先判斷對象當前是否為空,為空的話再去實例化對象 當使用self.xxx會調用xxx的get方法而_xxx并不會調用,正確的使用個方式是通過self去調用才會執行懶加載方法 我就是因為一直沒怎么注意self.xxxh 和 _xxx的區別,一直實用_xxx,所以才導致懶加載方法一直沒有被調用
懶加載的優點 不需將對象的實例化寫到viewDidLoad,可以簡化代碼,增強代碼的可讀性 對象的實例化在getter方法中,各司其職,降低耦合性 對系統的內存占用率會減小代碼實例
import "ViewController.h" @interface ViewController () @property (nonatomic,strong)NSMutableArray groupCarArray; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self createButton]; } - (void)createButton{ UIButton button = [UIButton buttonWithType:UIButtonTypeCustom]; button.frame = CGRectMake(100, 100, 100, 50); button.baCKgroundColor = [UIColor orangeColor]; [button setTitle:@"按鈕" forState:UIControlStateNormal]; [button addTarget:self action:@selector(Mybutton) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:button]; } - (void)Mybutton{ //調用方法 注意是self.groupCarArray,而不是_groupCarArray NSLog(@"%@",self.groupCarArray); } //懶加載數據 - (NSArray *)groupCarArray { if (_groupCarArray == nil) { NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"info.plist" ofType:nil]]; [_groupCarArray addObjectsFromArray:array]; } return _groupCarArray; } @end strong,weak,retain,assign,copy
weak和strong的區別:
weak和strong不同的是 當一個對象不再有strong類型的指針指向它的時候 它會被釋放 ,即使還有weak型指針指向它。一旦最后一個strong型指針離去 ,這個對象將被釋放,所有剩余的weak型指針都將被清除。
copy與retain的區別
copy其實是建立了一個相同的對象,而retain不是;copy是內容拷貝,retain是指針拷貝;copy是內容的拷貝 ,對于像NSString,的確是這樣,如果拷貝的是NSArray這時只是copy了指向array中相對應元素的指針.這便是所謂的"淺復制"
set和get方法
目的:為了能讓類的成員變量正確的被外接訪問,我們需要設置set和get方法。 用property就會自動生成get,set方法的過程中處理好retain,copy,release的關系,而且還可以在main中調用時使用
- set 函數 set 函數,對成員變量賦值。 Set函數的一般寫法: -(void)setAge:(int)newage; 2.Get函數 getter函數,對成員變量取值。Get函數一般寫法 -(int)age;
點運算符在oc的類對象中不能訪問成員變量,如dog.age=5,這里不是使用成員變量age,而是調用成員方法setAge,相當于給dog.age賦值,調用方法[dog setAge:5];
" . " 不能調用成員變量,只是調用set函數和get函數的一種簡寫。
Blocks理解
Blocks可以訪問局部變量,但是不能修改如果修改局部變量,需要加__block
__block int multiplier = 7; int (^myBlock)(int) = ^(int num) { multiplier ++;//這樣就可以了 return num * multiplier; }; __weak __typeof(*self)weakSelf =self; 等同于 __weak UIViewController *weakSelf =self; 為什么不用__block 是因為通過引用來訪問self的實例變量 ,self被retain,block也是一個強引用,引起循環引用,用__week是弱引用,當self釋放時,weakSelf已經等于nil。 在引用計數的環境里面,默認情況下當你在block里面引用一個Objective-C對象的時候,該對象會被retain。當你簡單的引用了一個對象的實例變量時,它同樣被retain。但是被__block存儲類型修飾符標記的對象變量不會被retain