Tables | Are | PS |
---|---|---|
1 | 基本原理 | |
1.1 | 為什么要進行內(nèi)存管理 | |
1.2 | 對象的基本結(jié)構(gòu) | |
1.3 | 引用計數(shù)器的作用 | |
1.4 | 操作 | |
1.5 | 對象的銷毀 | |
2 | 相關(guān)概念和使用注意 | |
3 | 內(nèi)存管理原則 | |
3.1 | 原則 | |
3.2 | 多對象管理 | |
4 | 內(nèi)存管理代碼規(guī)范 | |
4.1 | 基本數(shù)據(jù)類型 | |
4.2 | 內(nèi)存管理代碼規(guī)范 | |
4.2 | dealloc方法的代碼規(guī)范 | |
5 | @property的參數(shù) | |
5.1 | 內(nèi)存管理相關(guān)參數(shù) | |
5.2 | set和get方法的名稱 | |
6 | 內(nèi)存管理中的循環(huán)引用問題以及解決 | |
7 | Autorelease | |
7.1 | 基本用法 | |
7.2 | 好處 | |
7.3 | 錯誤寫法 | |
7.4 | 自動釋放池 | |
7.5 | 使用注意 | |
8 | ARC內(nèi)存管理機制 | |
8.1 | ARC的判斷準則 | |
8.2 | 指針分類 | |
8.3 | ARC的特點總結(jié) | |
8.4 | NSString的retainCount | |
8.4 | 補充 | |
9 | runtime 機制 | |
9.1 | run time 相關(guān)原理 | |
9.2 | runtime 的相關(guān)實現(xiàn) |
內(nèi)存分配
instruments 里面的leaks和allocation
OC 內(nèi)存管理
1.基本原理
1.1 為什么要進行內(nèi)存管理
由于移動設(shè)備的內(nèi)存極其有限筷凤,所以每個APP所占的內(nèi)存也是有限制的刻剥,當app所占用的內(nèi)存較多時,系統(tǒng)就會發(fā)出內(nèi)存警告臣嚣,這時需要回收一些不需要再繼續(xù)使用的內(nèi)存空間,比如回收一些不再使用的對象和變量等洁墙。
下列行為都會增加一個app的內(nèi)存占用
- 創(chuàng)建一個OC對象
- 定義一個變量
- 調(diào)用一個函數(shù)或者方法
管理范圍:任何繼承NSObject的對象剿配,對其他的基本數(shù)據(jù)類型無效。
本質(zhì)原因是因為對象和其他數(shù)據(jù)類型在系統(tǒng)中的存儲空間不一樣咽斧,其它局部變量主要存放于棧中堪置,而對象存儲于堆中,當代碼塊結(jié)束時這個代碼塊中涉及的所有局部變量會被回收张惹,指向?qū)ο蟮闹羔樢脖换厥?此時對象已經(jīng)沒有指針指向舀锨,要是依然存在于內(nèi)存中,會造成內(nèi)存泄露宛逗。
1.2 對象的基本結(jié)構(gòu)
每個OC對象都有自己的引用計數(shù)器坎匿,是一個整數(shù)表示對象被引用的次數(shù),即現(xiàn)在有多少東西在使用這個對象雷激。對象剛被創(chuàng)建時替蔬,默認計數(shù)器值為1,當計數(shù)器的值變?yōu)?時屎暇,則對象銷毀承桥。
在每個OC對象內(nèi)部,都專門有4個字節(jié)的存儲空間來存儲引用計數(shù)器根悼。
1.3 引用計數(shù)器的作用
判斷對象要不要回收的唯一依據(jù)就是計數(shù)器是否為0凶异,若不為0則存在。為0則調(diào)用dealloc函數(shù)挤巡。
1.4 操作
給對象發(fā)送消息剩彬,進行相應的計數(shù)器操作。
Retain消息:使計數(shù)器+1,改方法返回對象本身
Release消息:使計數(shù)器-1(并不代表釋放對象)
retainCount消息:獲得對象當前的引用計數(shù)器值
1.5 對象的銷毀
當一個對象的引用計數(shù)器為0時,那么它將被銷毀驮瞧,其占用的內(nèi)存被系統(tǒng)回收叹括。
當對象被銷毀時,系統(tǒng)會自動向?qū)ο蟀l(fā)送一條dealloc消息绍坝,一般會重寫dealloc方法徘意,在這里釋放相關(guān)的資源,dealloc就像是對象的“臨終遺言”轩褐。一旦重寫了dealloc方法就必須調(diào)用[super dealloc]椎咧,并且放在代碼塊的最后調(diào)用(不能直接調(diào)用dealloc方法)。
一旦對象被回收了,那么他所占據(jù)的存儲空間就不再可用勤讽,堅持使用會導致程序崩潰(野指針錯誤)蟋座。
2.相關(guān)概念和使用注意
野指針錯誤:訪問了一塊壞的內(nèi)存(已經(jīng)被回收的,不可用的內(nèi)存)脚牍。
僵尸對象:所占內(nèi)存已經(jīng)被回收的對象向臀,僵尸對象不能再被使用。(打開僵尸對象檢測)
空指針:沒有指向任何東西的指針(存儲的東西是0,null诸狭,nil)券膀,給空指針發(fā)送消息不會報錯
注意:不能使用[p retaion]讓僵尸對象起死復生。
3.內(nèi)存管理原則
3.1原則
只要還有人在使用某個對象驯遇,那么這個對象就不會被回收芹彬;
只要你想使用這個對象,那么就應該讓這個對象的引用計數(shù)器+1叉庐;
當你不想使用這個對象時舒帮,應該讓對象的引用計數(shù)器-1;
- 誰創(chuàng)建陡叠,誰release
如果你通過alloc,new,copy來創(chuàng)建了一個對象会前,那么你就必須調(diào)用release或者autorelease方法
不是你創(chuàng)建的就不用你去負責 - 誰retain,誰release**
只要你調(diào)用了retain匾竿,無論這個對象時如何生成的瓦宜,你都要調(diào)用release - 總結(jié)
有始有終,有加就應該有減岭妖。曾經(jīng)讓某個對象計數(shù)器加1临庇,就應該讓其在最后-1.
3.2 多對象管理
單個對象的內(nèi)存管理, 看起來非常簡單。如果對多個對象進行內(nèi)存管理, 并且對象之間是有聯(lián)系的, 那么管理就會變得比較復雜
其實, 多個對象的管理思路 跟 很多游戲的房間管理差不多
比如斗地主 \ QQ堂
有一個房間昵慌,有人要用假夺,一號使用retaincount+1 ,release下斋攀,二號使用同理已卷。當沒人使用的話,釋放房間
總的來說, 有這么幾點管理規(guī)律
只要還有人在用某個對象淳蔼,那么這個對象就不會被回收
只要你想用這個對象侧蘸,就讓對象的計數(shù)器+1
當你不再使用這個對象時,就讓對象的計數(shù)器-1
4.內(nèi)存管理代碼規(guī)范
只要調(diào)用了alloc鹉梨,就必須有release(autorelease)
4.1 基本數(shù)據(jù)類型:直接復制
-(void)setAge:(int)age
{
_age=age;
}
4.2 OC對象類型
-(void)setCar:(Car *)car
{
//1.先判斷是不是新傳進來的對象
If(car!=_car)
{
//2 對舊對象做一次release
[_car release];//若沒有舊對象讳癌,則沒有影響
//3.對新對象做一次retain
_car=[car retain];
}
}
4.3 dealloc方法的代碼規(guī)范
- 一定要[super dealloc],而且要放到最后
- 對self(當前)所擁有的的其他對象做一次release操作
-(void)dealloc
{
[_car release];
[super dealloc];
}
下面代碼都會引發(fā)內(nèi)存泄露
p.dog = [[Dog alloc] init];
[[Dog alloc] init].weight = 20.8;
5. @property的參數(shù)
5.1內(nèi)存管理相關(guān)參數(shù)
Retain:對對象release舊值存皂,retain新值(適用于OC對象類型)完整的set方法晌坤,管理內(nèi)存的參數(shù)
Assign:直接賦值(默認,不寫參數(shù)的都是assign,適用于非oc對象類型骤菠,這樣對位于棧的系統(tǒng)自動生成的變量)
Copy:release舊值它改,copy新值,用于NSString\block等類型
ARC中的@property
strong : 用于OC對象, 相當于MRC中的retain
weak : 用于OC對象, 相當于MRC中的assign
assign : 用于基本數(shù)據(jù)類型, 跟MRC中的assign一樣
copy : 一般用于NSString, 跟MRC中的copy一樣
是否要生成set方法(若為只讀屬性,則不生成)
Readonly:只讀商乎,只會生成getter的聲明和實現(xiàn)
Readwrite:默認的搔课,同時生成setter和getter的聲明和實現(xiàn)
多線程管理(蘋果在一定程度上屏蔽了多線程操作)
Nonatomic:高性能,一般使用這個
Atomic:低性能
5.2 Set和get方法的名稱
修改set和get方法的名稱截亦,主要用于布爾類型爬泥。因為返回布爾類型的方法名一般以is開頭,修改名稱一般用在布爾類型中的getter崩瓤。
@propery(setter=setAbc,getter=isRich) BOOL rich;
BOOL b=p.isRich;// 調(diào)用
6. 內(nèi)存管理中的循環(huán)引用問題以及解決
案例:每個人有一張身份證袍啡,每張身份證對應一個人,不能使用#import的方式相互包含却桶,這就形成了循環(huán)引用境输。
新的關(guān)鍵字:
@class 類名;——解決循環(huán)引用問題颖系,提高性能嗅剖;
@class僅僅告訴編譯器,在進行編譯的時候把后面的名字作為一個類來處理嘁扼。
@class的作用:聲明一個類信粮,告訴編譯器某個名稱是一個類
那么我們怎么使用@class,
- 在.h文件中使用@class來聲明類
- 在.m文件中真正要使用到的時候趁啸,使用#import來包含類中的所有東西
兩端循環(huán)引用的解決方法
循環(huán)retain的場景
比如A對象retain了B對象强缘,B對象retain了A對象
循環(huán)retain的弊端
這樣會導致A對象和B對象永遠無法釋放
循環(huán)retain的解決方案
當兩端互相引用時,應該一端用retain不傅、一端用assign(使用assign的在dealloc中也不用再release)
7 Autorelease
7.1 基本用法
- 會將對象放到一個自動釋放池中
- 當自動釋放池被銷毀時旅掂,會對池子里的所有對象做一次release
- 會返回對象本身
- 調(diào)用完autorelease方法后,對象的計數(shù)器不受影響(銷毀時影響)
7.2 好處
- 不需要再關(guān)心對象釋放的時間
- 不需要再關(guān)心什么時候調(diào)用release
7.3 錯誤寫法
連續(xù)調(diào)用多次autorelease访娶,釋放池銷毀時執(zhí)行兩次release(-1嗎商虐?)
Alloc之后調(diào)用了autorelease,之后又調(diào)用了release崖疤。
7.4 自動釋放池
在ios程序運行過程中秘车,會創(chuàng)建無數(shù)個池子,這些池子都是以棧結(jié)構(gòu)(先進后出)存在的戳晌。
當一個對象調(diào)用autorelease時鲫尊,會將這個對象放到位于棧頂?shù)尼尫懦刂?/p>
7.5 Autorelease注意
- 系統(tǒng)自帶的方法中痴柔,如果不包含alloc new copy等沦偎,則這些方法返回的對象都是autorelease的,如[NSDate date];
- 開發(fā)中經(jīng)常會寫一些類方法來快速創(chuàng)建一個autorelease對象豪嚎,創(chuàng)建對象時不要直接使用類名搔驼,而是使用self
- 占用內(nèi)存較大的對象,不要隨便使用autorelease侈询,應該使用release來精確控制;而占用內(nèi)存較小的對象使用autorelease舌涨,沒有太大的影響
8. ARC內(nèi)存管理機制
8.1 ARC的判斷準則
只要沒有強指針指向?qū)ο螅瑢ο缶蜁会尫拧?/p>
8.2 指針分類
- 強指針:默認的情況下扔字,所有的指針都是強指針囊嘉,關(guān)鍵字strong
- 弱指針:___weak關(guān)鍵字修飾的指針
聲明一個弱指針如下:___weak Person *p;
ARC中,只要弱指針指向的對象不在了革为,就直接把弱指針做清空操作扭粱。
___weak Person * p= [[Person alloc] init];
不合理,對象一創(chuàng)建出來就被釋放掉震檩,對象釋放掉后琢蛤,ARC把指針自動清零。
ARC中在property處不再使用retain,而是使用strong抛虏,在dealloc中不需要再[super dealloc]博其。
@property(nonatomic,strong)Dog *dog;// 意味著生成的成員變量_dog是一個強指針,相當于以前的retain迂猴。
如果換成是弱指針慕淡,則換成weak,不需要加 ____ 沸毁。
8.3 ARC的特點總結(jié)
- 不允許調(diào)用release儡率,retain,retainCount
- 允許重寫dealloc,但是不允許調(diào)用[super dealloc]
- @property的參數(shù):
Strong:相當于原來的retain(適用于OC對象類型)以清,成員變量是強指針
Weak:相當于原來的assign,(適用于oc對象類型)儿普,成員變量是弱指針
Assign:適用于非OC對象類型(基礎(chǔ)類型)
8.4 NSString的retainCount
** 提示:字符串是特殊的對象,但不需要使用release手動釋放掷倔,這種字符串對象默認就是autorelease的眉孩,不用額外的去管內(nèi)存,存放到堆中勒葱。 **
//1. 字符串常量
NSString *str1 = @"string";
NSLog(@"str1: %ld",[str1 retainCount]); // -1或18446744073709551615(即UINT_MAX ( Maximum value an `unsigned int'))
//因為"str"為字符串常量浪汪,系統(tǒng)不會收回,也不會對其作引用計數(shù)凛虽,即使我們對str如何retain或release死遭。就是為-1.
//2. stringWithFormat
NSString *str2 = [NSString stringWithFormat:@"%s", "test"];
NSLog(@"str2:%ld",[str2 retainCount]); // 1
//使用stringWithFormat創(chuàng)建的NSString為變量,系統(tǒng)會進行引用計數(shù)凯旋。 以前為1呀潭,現(xiàn)在為-1.
//3. stringWithString
stringWithString這個方法比較特別:它的retainCount取決于它后面跟的string對象
NSString *str3 = [NSString stringWithString:@"test"];
NSString *str4 = [NSString stringWithString:[NSString stringWithFormat:@"test,%d ,%d",1,3]];
NSLog(@"str3:%ld",[str3 retainCount]); // -1
NSLog(@"str4:%ld",[str4 retainCount]); // 2
//可以看到第一個為"常量"對象钉迷,其retainCount方法的實現(xiàn)返回的是maxIntValue。
//第二個為2钠署,這個方法生成的只是一個對另一個對象的引用糠聪。一個對象實例,兩次的stringWithString谐鼎,它的retainCount為2舰蟆,同時都被當前的AutoreleasePool管理。
8.5 補充
** 讓程序兼容ARC和非ARC部分狸棍。轉(zhuǎn)變?yōu)榉茿RC -fno-objc-arc 轉(zhuǎn)變?yōu)锳RC的身害, -f-objc-arc 。**
ARC也需要考慮循環(huán)引用問題:一端使用retain草戈,另一端使用assign题造。
set
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person new];
Car *car = [Car new];
car.speed = 100;
[p setCar:car];
[p drive];
[car release];
/*
//重復調(diào)用,已經(jīng)釋放了猾瘸,要改加上if語句去進行判斷
[p setCar:car];
[p drive];
*/
Car *car2 = [Car new];
car2.speed = 200;
[p setCar:car2];
[p drive];
[car2 release];
[p release];
}
return 0;
}
#import <Foundation/Foundation.h>
#import "Car.h"
@interface Person : NSObject
{
Car *_car;
NSString *_name;
}
- (void)setName:(NSString *)name;
- (NSString *)name;
- (void)setCar:(Car *)car;
- (Car *)car;
- (void)drive;
@end
#import "Person.h"
@implementation Person
- (void)dealloc{
//目的是要保證在p對象存在的時候界赔,car對象一定存在
//對象p被銷毀的時候
[_car release];
[_name release];
[super dealloc];
NSLog(@"被釋放了");
}
- (void)setName:(NSString *)name{
if (_name != name) {
[_name release];
_name = [name retain];
}
}
- (NSString *)name
{
return _name;
}
- (void)setCar:(Car *)car{
//第一次是空,所以release無所謂牵触,第二次減一淮悼,要是不release的話,會造成內(nèi)存泄露的問題揽思。
//car retainCount= 1
if (_car != car) {
[car release];
}
//car2 2
_car = [car retain];//引用計數(shù)加1 袜腥,返回self
}
- (void)drive{
[_car run];
}
@end
#import <Foundation/Foundation.h>
@interface Car : NSObject
@property int speed;
- (void)run;
@end
#import "Car.h"
@implementation Car
- (void)dealloc{
[super dealloc];
NSLog(@"被釋放了 %d",_speed);
}
- (void)run{
NSLog(@"I can run");
}
@end
9. runtime 機制
9.1 run time 相關(guān)原理
對于runtime機制,在網(wǎng)上找到的資料大概就是怎么去用這些東西钉汗,以及查看runtime.h頭文件中的實現(xiàn)羹令,當然這確實是一種很好的學習方法,但是损痰,其實我們還是不會知道runtime底層編譯成C++語言之后做了什么福侈? 查到一個大牛給資料,頓時對runtime有了一定認識卢未!
我們隨便寫一個小程序肪凛,代碼如下: person類頭文件如下,
<!-- lang: cpp -->
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int age;
@end
main.m文件如下
<!-- lang: cpp -->
int main(int argc, const char * argv[])
{
Person *p = [[Person alloc] init];
NSString *str = @"zhangsan";
p.name = str;
// p.name 等價于
[p setName:str];
p.age = 20;
return 0;
}
然后我們打開終端辽社,在命令行找到cd到文件目錄伟墙,然后中輸入:
clang -rewrite-objc main.m
命令可以將main.m編譯成C++的代碼,改成不同的文件名滴铅,就會生成不同的c++代碼 這是就生成了main.cpp這個c++文件戳葵,打開文件代碼 查看該main.cpp最底下的main函數(shù), 這樣我們就可以看到底層具體實現(xiàn)的方式汉匙!
這時拱烁,我們就需要知道這些方法:
**
objc_msgSend 可以給對象發(fā)送消息
objc_getClass(“Person”) 可以獲取到指定名稱的對象
sel_registerName(“alloc”) 可以調(diào)用到對象的方法
**
通過查看生蚁,c++代碼,我們得出結(jié)論:
使用objc_msgSend函數(shù)邻梆,給objc_getClass函數(shù)實例化的對象發(fā)送sel_registerName獲取到的方法 這么一個消息 代碼是給人看的守伸,順帶讓機器實現(xiàn)功能绎秒。
日常的程序開發(fā)過程中浦妄,要少用runtime,
那什么時候會使用runtime呢见芹? runtime應用的時機:
1> 當需要非常高的性能開發(fā)時剂娄,使用runtime,注釋:oc的代碼已經(jīng)無法滿足性能需求
2> 當我們對系統(tǒng)內(nèi)部的實現(xiàn)很好奇的時候玄呛,可以用clang反編譯成c++去看底層的實現(xiàn)機制阅懦!
項目講解的是runtime的底層實現(xiàn)原理, 如果想要知道runtime是怎么用的徘铝,可以查看runtime.h頭文件查看耳胎! 以下是runtime機制方法的一些使用方法介紹,希望對大家有用惕它!
相關(guān)技術(shù)文檔:
http://www.tuicool.com/articles/uimInm http://blog.csdn.net/lengshengren/article/details/17764135
9.2runtime 的相關(guān)實現(xiàn)
首先怕午,第一個問題,
- runtime實現(xiàn)的機制是什么?
runtime是一套比較底層的純C語言API, 屬于1個C語言庫, 包含了很多底層的C語言API淹魄。 在我們平時編寫的OC代碼中, 程序運行過程時, 其實最終都是轉(zhuǎn)成了runtime的C語言代碼, runtime算是OC的幕后工作者比如說郁惜,下面一個創(chuàng)建對象的方法中, 舉例: OC :
[[MJPerson alloc] init]
runtime :
objc_msgSend(objc_msgSend(“MJPerson” , “alloc”), “init”)
- runtime 用來干什么呢甲锡?兆蕉?用在那些地方呢?怎么用呢缤沦?
runtime是屬于OC的底層, 可以進行一些非常底層的操作(用OC是無法現(xiàn)實的, 不好實現(xiàn))
- 在程序運行過程中, 動態(tài)創(chuàng)建一個類(比如KVO的底層實現(xiàn))
- 在程序運行過程中, 動態(tài)地為某個類添加屬性\方法, 修改屬性值\方法
- 遍歷一個類的所有成員變量(屬性)\所有方法
例如:我們需要對一個類的屬性進行歸檔解檔的時候?qū)傩蕴貏e的多虎韵,這時候,我們就會寫很多對應的代碼缸废,但是如果使用了runtime就可以動態(tài)設(shè)置劝术!
例如,PYPerson.h的文件如下所示
import
@interface PYPerson : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int height;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age2;
@property (nonatomic, assign) int height2;
@property (nonatomic, assign) int age3;
@property (nonatomic, assign) int height3;
@property (nonatomic, assign) int age4;
@property (nonatomic, assign) int height4;
@end
而PYPerson.m實現(xiàn)文件的內(nèi)容如下
#import "PYPerson.h"
import
@implementation PYPerson
- (void)encodeWithCoder:(NSCoder *)encoder {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([PYPerson class], &count);
for (int i = 0; i<count; i++) {
// 取出i位置對應的成員變量Ivar ivar = ivars[i];
// 查看成員變量const char *name = ivar_getName(ivar);
// 歸檔NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[encoder encodeObject:value forKey:key];
}
free(ivars);
}
(id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([PYPerson class], &count);for (int i = 0; i<count; i++) {
// 取出i位置對應的成員變量 Ivar ivar = ivars[i];
// 查看成員變量 const char *name = ivar_getName(ivar);
// 歸檔 NSString *key = [NSString stringWithUTF8String:name]; id value = [decoder decodeObjectForKey:key];
// 設(shè)置到成員變量身上 [self setValue:value forKey:key];}free(ivars);
}
return self;
}
@end
這樣我們可以看到歸檔和解檔的案例其實是runtime寫下的.
學習runtime機制首先要了解下面幾個問題
相關(guān)的頭文件和函數(shù)
利用頭文件呆奕,我們可以查看到runtime中的各個方法养晋!相關(guān)應用
NSCoding(歸檔和解檔, 利用runtime遍歷模型對象的所有屬性)
字典 –> 模型 (利用runtime遍歷模型對象的所有屬性, 根據(jù)屬性名從字典中取出對應的值, 設(shè)置到模型的屬性上)
KVO(利用runtime動態(tài)產(chǎn)生一個類)
用于封裝框架(想怎么改就怎么改) 這就是我們runtime機制的只要運用方向相關(guān)函數(shù)
objc_msgSend : 給對象發(fā)送消息
class_copyMethodList : 遍歷某個類所有的方法
class_copyIvarList : 遍歷某個類所有的成員變量
class_….. 這是我們學習runtime必須知道的函數(shù)!必備常識 1> Ivar : 成員變量 2> Method : 成員方法 從上面例子中我們看到我們定義的成員變量梁钾,如果要是動態(tài)創(chuàng)建方法绳泉,可以使用Method.