Objective-C
1. import的用法
- 拷貝文件內(nèi)容
可以自動防止文件的內(nèi)容被重復(fù)拷貝(#define宏定義) - Foundation 框架頭文件的路徑
Xcode.app 顯示包內(nèi)容
Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/Foundation.framework - 命令行指令
- 編寫 Oc 源文件: .m .c
- 編譯: cc -c xxx.m xxx.c
- 鏈接: cc xxx.o xxx.o -framework Foundation(用到的時候才加)
- 運行: ./a.out
- 主頭文件
主頭文件:最主要的頭文件,名字一般跟框架名稱一樣,包含了框架中的所有其他頭文件 - Foundation框架的主頭文件名稱就是Foundation.h
只需要包含F(xiàn)oundation框架主頭文件,就可以使用整個框架的東西
2. 對象方法和類方法
- 對象方法: - 開頭
- 只能由對象調(diào)用
- 對象方法中能訪問當(dāng)前對象的成員變量(實例變量)
- 類方法: + 開頭
- 只能由類名來調(diào)用
- 類方法中不能訪問成員變量
- 類方法的優(yōu)點和使用場合:
- 不依賴于對象,執(zhí)行效率高
- 能用類方法,盡量用類方法
- 場合:當(dāng)方法內(nèi)部不需要使用到成員變量時,就用類方法
- 可以允許類方法和對象方法同名
3. 成員變量和局部變量
-
成員變量:
- 寫在類聲明的大括號中的變量, 我們稱之為 成員變量(屬性, 實例變量)成員變量只能通過對象來訪問
- 注意: 成員變量不能離開類,,離開類之后就不是成員變量,成員變量不能在定義的同時進行初始化
- 存儲: 堆(當(dāng)前對象對應(yīng)的堆的存儲空間中)
- 存儲在堆中的數(shù)據(jù), 不會被自動釋放, 只能程序員手動釋放
局部變量:
-
寫在函數(shù)或者代碼塊中的變量, 我們稱之為局部變量
- 作用域: 從定義的那一行開始, 一直到遇到大括號或者return
- 局部變量可以先定義再初始化, 也可以定義的同時初始化
- 存儲 : 棧
- 存儲在棧中的數(shù)據(jù)有一個特點, 系統(tǒng)會自動給我們釋放
-
全局變量
- 寫在函數(shù)和大括號外部的變量, 我們稱之為全局變量
- 作用域: 從定義的那一行開始, 一直到文件末尾
- 局部變量可以先定義在初始化, 也可以定義的同時初始化
- 存儲: 靜態(tài)區(qū)
- 程序一啟動就會分配存儲空間, 直到程序結(jié)束才會釋放
4. 方法和函數(shù)
- 方法
- 函數(shù)屬于整個文件,, 方法屬于某一個類,方法如果離開類就不行
- 函數(shù)可以直接調(diào)用, 方法必須用對象或者類來調(diào)用
- 函數(shù)
- 能寫在文件中的任意位置(@interface和@end之間除外),函數(shù)歸文件所有
- 函數(shù)調(diào)用不依賴于對象
- 函數(shù)內(nèi)部不能直接通過成員變量名訪問某個對象的成員變量
5. setter 和 getter 用法簡介
- setter
- 作用: 給成員變量賦值
- 格式:
- 必須是對象方法
- 一定沒有返回值
- 方法名稱一定以set開頭, set后面跟上成員變量的名稱, 并去掉下劃線, 然后將首字母大寫
- 一定有參數(shù), 并且參數(shù)類型和成員變量的類型一致, 參數(shù)名稱就是成員變量的名稱去掉下劃線
- getter
- 作用: 返回成員變量的值
- 格式:
- 必須是對象方法
- 一定有返回值, 返回值類型和成員變量的類型一致
- 方法名稱就是成員變量的名稱去掉下劃線
- 一定沒有參數(shù)
- readLoad 和 readWrite
- 一個屬性可以只有g(shù)etter方法, 沒有setter方法, 這種屬性我們稱之為只讀屬性
- 一個屬性也可以只有setter方法, 沒有g(shù)etter方法, 這種屬性我們稱之為只寫屬性
- 如果既有setter方法又有g(shù)etter方法, 那么這種屬性我們稱之為可讀可寫的屬性
- 一個屬性也可以沒有g(shù)etter和setter, 這種屬性我們稱之為私有屬性
6. self 的基本使用
- 用法
- 那個調(diào)用了當(dāng)前方法,self就代表誰
- self出現(xiàn)在對象方法中, self就代表對象
- self出現(xiàn)在類方法中, self就代表類
- 在對象方法利用 "self->成員變量名” 訪問當(dāng)前對象內(nèi)部的成員變量(實例變量)
- [self 方法名] 可以調(diào)用其他對象方法\類方法
- self會自動區(qū)分類方法和對象方法, 如果在類方法中使用self調(diào)用對象方法, 那么會直接報錯
- 不能在對象方法或者類方法中利用self調(diào)用當(dāng)前self所在的方法
- 動態(tài)綁定:
動態(tài)類型能使程序直到執(zhí)行時才確定對象的真實類型
動態(tài)類型綁定能使程序直到執(zhí)行時才確定要對那個對象調(diào)用的方法 - 使用self調(diào)用本方法,導(dǎo)致死循環(huán)調(diào)用
- #pragma mark
- 將代碼分隔開,方便我們進行查找。
7. super 基本使用
- 編譯器指令符號.
- 利用super給父類的方法發(fā)送一個消息, 那么系統(tǒng)就會自動調(diào)用父類的方法
- 如果以后想在子類中調(diào)用父類的方法可以使用super
- 如果想在給父類方法進行擴展的同時保留父類的方法, 那么可以使用super調(diào)用父類同名的方法
- super 在什么方法中就調(diào)用父類的什么方法
8. 面向?qū)ο蠡舅枷?/h2>
封裝
- 原理:屏蔽內(nèi)部實現(xiàn)的細節(jié),僅僅對外提供共有的方法/接口
- 好處:保證數(shù)據(jù)的安全性
- 規(guī)范:一般情況下不會對外直接暴露成員變量, 都會提供一些共有的方法進行賦值成員變量都需要封裝起來
繼承
- 父類必須聲明在子類的前面
- 不允許子類和父類擁有相同名稱的成員變量, 因為子類繼承父類,子類將會擁有父類的所有成員變量,若在子類中定義父類同名成員變量 屬于重復(fù)定義。
- 調(diào)用某個對象的方法時, 優(yōu)先去當(dāng)前類中找, 如果么有, 去父類中找
- 基類的私有屬性能被繼承, 不能在子類中訪問。
- OC中的繼承是單繼承:也就是說一個類只能一個父類, 不能繼承多個父類
- 缺點:耦合性太強
多態(tài)
- 事物的多種形態(tài)
- 沒有繼承就沒有多態(tài)
- 代碼的體現(xiàn): 父類類型的指針指向子類對象
- 好處: 如果函數(shù)\方法參數(shù)中使用的是父類類型, 可以傳入父類, 子類對象
- 局限性: 父類類型的變量 不能 直接調(diào)用子類特有的方法,必須強轉(zhuǎn)為子類類型變量后, 才能直接調(diào)用子類特有的方法
- 動態(tài)綁定:
- 動態(tài)類型能使程序直到執(zhí)行時才確定對象的真實類型
- 動態(tài)類型綁定能使程序直到執(zhí)行時才確定要對那個對象調(diào)用的方法
假設(shè) 子類 Dog 有一個特有的方法bark
[dog bark];
Animal *an = [Dog new];
[(Dog*)an bark]; //把父類的指針,強制類型轉(zhuǎn)換
9. 成員變量的作用域
- @public 在任何地方都能直接訪問對象的成員變量
- @private 只能在當(dāng)前類的對象方法中直接訪問(子類可以通過seter geter方法訪問父類的私有的成員變量)
- @protected 能在當(dāng)前類和子類的對象方法中直接訪問 (默認(rèn)是 protected)
- @package 只要處在同一個框架中, 就能直接訪問對象的成員變量(不常用)
點語法使用注意
- 點語法的本質(zhì)還是方法調(diào)用
p.age = 10; // [p setAge:10]
- 引發(fā)死循環(huán)
self.age = age; // [self setAge:age]
- 私有成員變量
- 寫在@implementation 中的成員變量,默認(rèn)就是私有成員變量,并且和利用@private 修飾的不太一樣,@implementation 中定義的成員變量在其他類中無法查看,也無法訪問
在@implementation 中定義的私有變量只能在本類中查看
- 私有方法:只有實現(xiàn)沒有聲明,OC 中美有真正的私有方法,因為 OC 是消息機制。私有方法外面不能訪問,只能通過包裝成 sel 就可以訪問
10. @property 用法
Property 編譯器指令
-
生成setter 和 getter 方法聲明(未加強版)
-(void)setAge:(int)age;
-(int)age;
@property int age;
@synthesize age = _age;
setter和getter實現(xiàn)中會訪問成員變量_age, 如果成員變量_age不存在,就會自動生成一個@private的成員變量_age
@synthesize age;
setter和getter實現(xiàn)中會訪問@synthesize后同名成員變量age
如果成員變量age不存在,就會自動生成一個@private的成員變量age
-
多個屬性可以通過一行@synthesize搞定,多個屬性之間用逗號連接
@synthesize age = _age, number = _number, name = _name;
Property 增強
只要利用一個@property就可以同時生成setter/getter方法的聲明和實現(xiàn)傳入的屬性賦值給_開頭的成員變量
@property有一個弊端: 它只會生成最簡單的getter/setter方法的聲明和實現(xiàn), 并不會對傳入的數(shù)據(jù)進行過濾
如果想對傳入的數(shù)據(jù)進行過濾, 那么我們就必須重寫getter/setter方法如果不想對傳入的數(shù)據(jù)進行過濾, 僅僅是提供一個方法給外界操作成員變量, 那么就可以使用@property
注意: 如果沒有會自動生成一個_開頭的成員變量,自動生成的成員變量是私有變量, 聲明在.m中,在其它文件中無法查看,但當(dāng)可以在本類中查看
-
有就不生成,沒有就生成
- 如果重寫了setter方法, 那么property就只會生成getter方法
- 如果重寫了getter方法, 那么property就只會生成setter方法
- 如果同時重寫了getter/setter方法, 那么property就不會自動幫我們生成私有的成員變量
@property(屬性修飾符) 數(shù)據(jù)類型 變量名稱;
readwrite:代表生成 getter 和 setter 方法,默認(rèn)就是
readonly:代表只生成 getter 方法,(只讀)
修改 getter 方法名(常用)
程序員之間有一個約定, 一般情況下獲取BOOL類型的屬性的值, 我們都會將獲取的方法名稱改為isXXX
11. new alloc init 的基本用法及區(qū)別
- alloc
- 開辟存儲空間
- 將所有成員變量設(shè)為0
- 返回當(dāng)前的對象地址
- init
- 初始化成員變量, 但是默認(rèn)情況下init的實現(xiàn)是什么都沒有做 2.返回初始化后的實例對象地址
- alloc和 init 返回的地址是一樣的
12. id和 instancetype
- 靜態(tài)類型和動態(tài)類型
- 靜態(tài)類型:將一個指針變量定義為特定類的對象時,使用的是靜態(tài)類型,在編譯的時候就知道這個指針變量所屬的類,這個變量總是存儲特定類的對象。
Person *p = [Person alloc] init]]
- 動態(tài)類型:這一特性是程序直到執(zhí)行時才確定對象所屬的類
id p = [[Person alloc] init];
- Id
- id 是一種通用的對象類型,它可以指向?qū)儆谌魏晤惖膶ο?也可以理解為萬能指針
- id是動態(tài)類型,所以可以通過id類型直接調(diào)用指向?qū)ο笾械姆椒? 編譯器不會報錯
- 優(yōu)點
- 通過靜態(tài)數(shù)據(jù)類型定義變量, 不能調(diào)用子類特有的方法
- 通過動態(tài)數(shù)據(jù)類型定義變量, 可以調(diào)用子類特有的方法
- 通過動態(tài)數(shù)據(jù)類型定義的變量, 可以調(diào)用私有方法
- 弊端: 由于動態(tài)數(shù)據(jù)類型可以調(diào)用任意方法, 所以有可能調(diào)用到不屬于自己的方法, 而編譯時又不會報錯, 所以可能導(dǎo)致運行時的錯誤
- 應(yīng)用場景
- 多態(tài), 可以減少代碼量, 避免調(diào)用子類特有的方法需要強制類型轉(zhuǎn)換
- 為了避免動態(tài)數(shù)據(jù)類型引發(fā)的運行時的錯誤, 一般情況下如果使用動態(tài)數(shù)據(jù)類型定義一個變量, 在調(diào)用這個變量的方法之前會進行一次判斷, 判斷當(dāng)前變量是否能夠調(diào)用這個方法
id obj = [Student new];
[obj isKindOfClass:[Student class]]
//isKindOfClass , 判斷指定的對象是否是某一個類, 或者是某一個類的子類
- instancetype
- instancetype == id == 萬能指針 == 指向一個對象
- id在編譯的時候不能判斷對象的真實類型
- instancetype在編譯的時候可以判斷對象的真實類型
- id和instancetype除了一個在編譯時不知道真實類型, 一個在編譯時知道真實類型以外, 還有一個區(qū)別
- id可以用來定義變量, 可以作為返回值, 可以作為形參
- instancetype只能用于作為返回值
- 注意: 以后但凡自定義構(gòu)造方法, 返回值盡量使用instancetype, 不要使用id
13. 構(gòu)造方法(- 開頭的對象方法)
用來初始化對象的方法.
重寫構(gòu)造方法的注意:
先調(diào)用父類的構(gòu)造方法.
再進行子類內(nèi)部的成員變量的初始化
-
返回當(dāng)前對象的地址
-(instancetype)init
{
// 注意: 不要把 = 號寫為 ==
// 一定要將[super init]的返回值賦值給self
if (self = [super init]) {
// 初始化子類
_age = 6;
}
return self;
}
自定義構(gòu)造方法
自己做自己的事情
父類的屬性交給父類的方法來處理,子類的方法處理子類自己獨有的屬性
自定義構(gòu)造方法必須以intiWith開頭,并且’W’必須大寫
類工廠方法:
用于快速創(chuàng)建對象的類方法, 我們稱之為類工廠方法
類工廠方法中主要用于 給對象分配存儲空間和初始化這塊存儲空間
-
規(guī)范:
- 一定是類方法 +
- 方法名稱以類的名稱開頭, 首字母小寫
- 一定有返回值, 返回值是id/instancetype
- 注意: 以后但凡自定義類工廠方法, 在類工廠方法中創(chuàng)建對象一定不要使用類名來創(chuàng)建,
一定要使用self來創(chuàng)建
return [[self alloc] init];
14. Category - 分類
- 在不改變原來類模型的前提下, 給類擴充一些方法. 有2種方式 : 繼承 分類
- 好處: 一個龐大的類可以分模塊開發(fā), 一個龐大的類可以由多個人來編寫, 便于團隊合作.
- 使用注意:
- 分類中寫property, 只會生成getter/setter方法的聲明, 不會生成實現(xiàn)和私有成員變量
- Category 可以
訪問原始類的成員變量
, 但不能添加變量, 只能添加方法. 如果想 添加變量, 可以考慮通過繼承創(chuàng) 建子類
- Category 可以實現(xiàn)原始類的方法, 不推薦這么做, 因為它是替換掉原始類的方 法, 這么做以后就不能訪問原來的 方法.
- 多個Category 中如果實現(xiàn)了相同的方法, 只有最后一個參與編譯的才會有效.
- 方法調(diào)用的優(yōu)先級 : 分類(最后參與編譯的分類優(yōu)先) —>原來類—>父類
- 類擴展(Extendsion)
- 某個類擴充一些私有的成員變量和方法
- 寫在.m文件中
- 英文名是Class Extension
- 格式(俗稱匿名分類)
@interface 類名 ()
@end
15. 類的本質(zhì)(typedef struct objc_class * Class)
- 類也是一個對象, 是 class 類型的對象, 簡稱 “類對象”, 類名就代表著類對象, 每個類只有一個類對象
- + load
- + load : 在程序啟動的時候會加載所有的類和分類, 并調(diào)用所有類和分類的 + load 方法,并且只會調(diào)用一次。
- 加載順序
父類 → 子類→ 分類
。不管程序運行過程有沒有用到這個類, 都會調(diào)用 + load 方法。
- + initialize
- + initialize : 在第一次使用某個類時(比如創(chuàng)建對象等), 且只會調(diào)用一次 + initialize 方法.
- 主要用于對某一個類一次性初始化
- 一個類只會調(diào)用一次 + initialize方法, 先調(diào)用父類的, 再調(diào)用子類的
- 獲取類對象的2種方式(獲取內(nèi)存中的類對象)
```
Class c = [Person class] // 類方法
Person *p = [Person new];
Class c1 = [p class]; // 對象方法
```
- 類在內(nèi)存的表現(xiàn)
- 實例對象 → 類對象(對象方法)→ 元類對象(類方法)→ 根元類 (isa 指向自己)
- 元類保存了類方法的列表。當(dāng)一個類方法被調(diào)用時,元類會首先查找它本身是否有該類方法的實現(xiàn),如果沒有則該元類會向它的父類查找該方法,直到一直找到繼承鏈的頭。
- 元類(metaclass)也是一個對象,那么元類的isa指針又指向哪里呢?為了設(shè)計上的完整,所有的元類的isa指針都會指向一個根元類(root metaclass)。
- 根元類(root metaclass)本身的isa指針指向自己,這樣就行成了一個閉環(huán)。上面說到,一個對象能夠接收的消息列表是保存在它所對應(yīng)的類中的。在實際編程中,我們幾乎不會遇到向元類發(fā)消息的情況,那它的isa 指針在實際上很少用到。不過這么設(shè)計保證了面向?qū)ο蟮母蓛?即所有事物都是對象,都有isa指針。
- 由于類方法的定義是保存在元類(metaclass)中,而方法調(diào)用的規(guī)則是,如果該類沒有一個方法的實現(xiàn),則向它的父類繼續(xù)查找。所以為了保證父類的類方法可以在子類中可以被調(diào)用,所以子類的元類會繼承父類的元類,換而言之,類對象和元類對象有著同樣的繼承關(guān)系。
-
如下圖 :
Snip20150623_6.png
16. NSLog
- 使用 NSLog 和 %@輸出某個類對象時, 會調(diào)用類對象 + description 方法, 并拿到返回值(NSString *)進行輸出
- - description方法默認(rèn)返回對象的描述信息(默認(rèn)實現(xiàn)是返回類名和對象的內(nèi)存地址)
- description方法是基類NSObject 所帶的方法. 使用NSLog輸出OC對象,意義就不是很大,因為我們并不關(guān)心對象的內(nèi)存地址,比較關(guān)心的是對象內(nèi)部的一些成變量的值。因此,會經(jīng)常重寫description方法,覆蓋description方法 的默認(rèn)實現(xiàn)
-(NSString *) description
{
return [NSString stringWithFormat:@"age = %d", _age]
}
- + descrption 方法
- 當(dāng)使用NSLog輸出該類的類對象的時候調(diào)用*/(
不常用
)
- 注意: 死循環(huán). 如果在 - description方法中使用 NSLog %@ 輸出self對象會引發(fā)死循環(huán)
17. SEL 基本使用
代表方法的簽名,在類對象的方法列表
中存儲著該簽名與方法代碼的對應(yīng)關(guān)系
-
每個方法都有一個與之對應(yīng)的 SEL類型的對象
- SEL 其實是對方法的一種包裝, 將方法包裝成一個 SEL 類型的數(shù)據(jù), 去找對應(yīng)的方法地址, 進而進行調(diào)用
- 注意:在這個操作過程中又緩存,第一次找的時候一個一個的找,非常耗性能,之后再用到的時候就直接使用
-
對象是否實現(xiàn)了某個方法
- - (BOOL) respondsToSelector: (SEL)selector 判斷實例是否實現(xiàn)這樣方法
- + (BOOL)instancesRespondToSelector:(SEL)aSelector; (類對象)
讓對象執(zhí)行某個方法
- (id)performSelector:(SEL)aSelector;
-
SEL 類型的定義 typedef struct objc_selector *SEL
//SEL 對象的創(chuàng)建
SEL s = @selector(test);
SEL s2 = NSSelectorFromString(@"test”);
// 將SEL對象轉(zhuǎn)為NSString對象
NSString *str = NSStringFromSelector(@selector(test));
Person *p = [Person new];
// 每個類都有以個_cmd 代表當(dāng)前方法
// 調(diào)用對象p的test方法
[p performSelector : @selector (test)];
18. 內(nèi)存管理
Automatic Reference Couting
- 什么是自動引用計數(shù)器
- 每個OC對象都有自己的引用計數(shù)器,它是一個整數(shù),從字面上, 可以理解為”對象被引用的次數(shù)”
- 也可以理解為: 它表示有多少人正在用這個對象
占4個字節(jié)
Manul Refrence Counting
-
什么是手動引用計數(shù)?
- 所有對象的內(nèi)容都需要我們手動管理, 需要程序員自己編寫release/retain等代碼
-
方法的基本使用
- retain : 計數(shù)器 +1 , 會返回對象本身
- release : 計數(shù)器 -1 , 沒有返回值(
release并不代表銷毀對象, 僅僅是計數(shù)器-1
)
- retainCount : 獲取當(dāng)前的計數(shù)器
-
概念
- 僵尸對象: 所占用內(nèi)存已經(jīng)被回收的對象, 僵尸對象不能再使用
- 野指針: 指向僵尸對象(不可用內(nèi)存)的指針, 給野指針發(fā)送消息會報錯(EXC_BAD_ACCES)
- 空指針: 沒有指向任何東西的指針(儲存的東西是nil NULL 0), 給空指針發(fā)送消息不會報錯
- 內(nèi)存管理代碼規(guī)范
- 只要調(diào)用了alloc, 必須有relese(autorelease), 如果對象不是通過alloc產(chǎn)生的, 就不需要release
- set方法的代碼規(guī)范
- 基本數(shù)據(jù)類型: 直接復(fù)制
-(void)setAge:(int)age
{
_age = age;
}
```
- OC對象類型
```
-(void)setCar:(Car *)car
{
// 先判斷是不是新傳進來對象
if( car != _car)
{
// 對舊對象做一次release
[_car release]
// 對新對象做一次retain
_car = [car retain]
}
}
```
- dealloc方法的代碼規(guī)范
- 對self(當(dāng)前)所擁有的其他對象做一次release
- 當(dāng)一個對象要被回收的時候, 就會調(diào)用
- 一定要調(diào)用
[super dealloc]
, 這句調(diào)用放在最后面
@Property 參數(shù)
- set 方法內(nèi)存管理相關(guān)的參數(shù)
- retain : release 舊值 , retain 新值 (適用于OC對象類型)
- assign : 直接賦值(默認(rèn), 適用于非OC對象類型)
- copy : release 舊值, copy 新值
- 是否要生成set方法
- readwrite : 同時生成setter 和 getter的聲明, 實現(xiàn)(默認(rèn))
- readonly : 只會生成getter的聲明, 實現(xiàn)
- 多線程管理
- nonatomic : 性能高 (一般就用這個)
- atomic : 性能低(默認(rèn))
- setter 和 getter方法的名稱
- setter : 決定了set方法的名稱, 一定要有個冒號 :
- getter : 決定了get方法的名稱(一般用在BOOL類型)
@Class(循環(huán)引用)
僅僅告訴編譯器,某個名稱是一個類
-
開發(fā)中引用一個類的規(guī)范
- 在.h 文件中用@class 來聲明類
- 在.m 文件中用#import 來包含類的所有東西
-
和#import 的區(qū)別(面試)
- import會包含引用類的所有信息(內(nèi)容),包括引用類的變量和方法
- @class僅僅是告訴編譯器有這么一個類, 具體這個類里有什么信息, 完全不知
-
總結(jié):
- 如果都在.h中import, 假如A拷貝了B, B拷貝了C , 如果C被修改了, 那么B和A都需要重新拷貝. 因為C修改了那么B就會重新拷貝, 而B重新拷貝之后相當(dāng)于B也被修改了, 那么A也需要重新拷貝. 也就是說如果都在.h中拷貝, 只要有間接關(guān)系都會重新拷貝
- 如果在.h中用@class, 在.m中用import, 那么如果一個文件發(fā)生了變化, 只有和這個文件有直接關(guān)系的那個文件才會重新拷貝
- 所以在.h中用@class可以提升編譯效率
兩端循環(huán)引用(面試)
retain
* 比如A對象retain了B對象,B對象retain了A對象,這樣會導(dǎo)致A對象和B對象永遠無法釋放。
* 當(dāng)兩端互相引用時,應(yīng)該一端用retain、一端用assign。
import
* 如果兩個類相互(#import<>
)拷貝, 例如A拷貝B, B拷貝A, 這樣會報錯
- 如何解決: 在.h中用@class, 在.m中用import
- 因為如果.h中都用import, 那么A拷貝B, B又拷貝A, 會形成死循環(huán)
- 如果在.h中用@class, 那么不會做任何拷貝操作, 而在.m中用import只會拷貝對應(yīng)的文件, 并不會形成死循環(huán)
@ autorelease基本用法
- 會將對象放到一個自動釋放池中,并且會返回對象本身
- 當(dāng)自動釋放池被銷毀時, 會對池子里面的所有對象做一次release 操作
- 調(diào)用完@autorelease 方法后,對象計數(shù)器不變
- @autorelease 的好處
- 不用關(guān)心對象釋放的時間
- 不用關(guān)心什么時候調(diào)用 release
- @autorelease 使用注意
- 占用內(nèi)存較大的對象不要隨便用 autorelease
- 占用內(nèi)存較小的對象使用 autorelease,沒有太大影響(影響:不能控制對象的釋放時間)
- 錯誤寫法
- alloc 之后調(diào)用了 autorelease ,又調(diào)用 release
Person *p = [[[Person alloc] init] autorelease];
[p release];
- 連續(xù)調(diào)用autorelease(野指針錯誤,每個autorelease 釋放時都會調(diào)用 release)
Person *p = [[[[Person alloc] init] auturelease] autorelease]
- 系統(tǒng)自帶方法里面沒有alloc、new、copy,說明返回的對象是autorelease的
- 開發(fā)中經(jīng)常會提供一些類方法,快速創(chuàng)建一個已經(jīng)autorelease過的對象
- 創(chuàng)建對象時不要直接用類名,一般用 self
+ (id)person
{
return [[[self alloc] init] autorelease];
}
自動釋放池
在 IOS 程序運行中,會創(chuàng)建無數(shù)個池子。這些池子都是以“棧”結(jié)構(gòu)存在(先進后出,“杯子”)
當(dāng)一個對象調(diào)用 autorelease 方法時,會將這個對象放到棧頂?shù)尼尫懦?(”棧頂“ 相當(dāng)于杯子底)
-
集合對象的內(nèi)存管理
- 當(dāng)把一個對象添加到集合中時,這個對象會做了一次retain操作,計數(shù)器會+1
- 當(dāng)一個集合被銷毀時,會對集合里面的所有對象做一次release操作,計數(shù)器會-1
- 當(dāng)一個對象從集合中移除時,這個對象會一次release操作,計數(shù)器會-1
ARC
ARC 的判斷準(zhǔn)則:主要沒有強指針指向?qū)ο螅蜁尫艑ο?/p>
指針的2種類型
強指針:_strong 默認(rèn)情況,所有指針都是強指針
-
弱指針:_weak
- _weak Person *p = [[Person alloc] init] 錯誤寫法,沒有意義的寫法,一創(chuàng)建就釋放
-
ARC 特點
- 不允許調(diào)用 release、retain、retainCount
- 允許重寫 dealloc,但是不允許調(diào)用[super dealloc]
- @property 的參數(shù)
- strong : 成員變量是強指針(適用于 OC 對象類型)
- weak : 成員變量是弱指針(適用于 OC 對象類型)
- assign:適用于非 OC 對象
循環(huán)引用
一端用:strong,另一端用:weak
在Compiler Flags一列加上-fno-objc-arc就表示禁止這個.m文件的ARC
mrc 可以轉(zhuǎn) arc ,系統(tǒng)轉(zhuǎn)換
19. copy
- copy的基本原則
- 因為拷貝要求修改原來的對象不能影響到拷貝出來得對象
- 修改拷貝出來的對象也不能影響到原來的對象, 所以需要生成一個新的對象
互不影響
- copy的使用
- 實現(xiàn)拷貝的方法有2個
- copy:返回不可變副本
- mutableCopy:返回可變副本
- 普通對象實現(xiàn)拷貝的步驟
- 遵守NSCopying協(xié)議
- 實現(xiàn)-copyWithZone:方法
- 創(chuàng)建新對象
- 給新對象的屬性賦值
20. Block
- block訪問外面變量
- block內(nèi)部可以訪問外面的變量
- 默認(rèn)情況下,block內(nèi)部不能修改外面的局部變量
- 給局部變量加上__block關(guān)鍵字,這個局部變量就可以在block內(nèi)部修改
- 利用typedef定義block類型
typedef int (^MyBlock)(int, int);
// 以后就可以利用MyBlock這種類型來定義block變量
- block是存儲在堆中還是棧中
- 默認(rèn)情況下block存儲在棧中, 如果對block進行一個copy操作, block會轉(zhuǎn)移到堆中
- 如果block在棧中, block中訪問了外界的對象, 那么不會對對象進行retain操作
- 但是如果block在堆中, block中訪問了外界的對象, 那么會對外界的對象進行一次retain
- 如果在block中訪問了外界的對象, 一定要給對象加上__block, 只要加上了__block, 哪怕block在堆中, 也不會對外界的對象進行retain
- 如果是在ARC開發(fā)中就需要在前面加上__weak
Person *p = [Person new];
__weak Person *weakP = p;
void (^myBlock) () = ^{
weakP.age = 10;
};
myBlock();
NSLog(@"age = %li", p.age);
21. Protocol
協(xié)議
@protocol 協(xié)議名稱 < NSObeject >
// 方法聲明列表....
@end
如何遵守協(xié)議
類遵守協(xié)議
@interface 類名 : 父類名 <協(xié)議名稱1, 協(xié)議名稱2>
@end
協(xié)議遵守協(xié)議
@protocol 協(xié)議名稱 <其他協(xié)議名稱1, 其他協(xié)議名稱2>
@end
協(xié)議中方法聲明的關(guān)鍵字
@required (默認(rèn)): 要求實現(xiàn),如果沒有實現(xiàn),會發(fā)出警告
@optional: 不要求實現(xiàn),不會有警告
定義一個變量的時候,限制這個變量保存的對象遵守某個協(xié)議
-
如果沒有遵守對應(yīng)的協(xié)議,編譯器會警告
類名<協(xié)議名稱> *變量名;
id<協(xié)議名稱> 變量名;
NSObject<MyProtocol> *obj;
id<MyProtocol> obj2;
-
@property中聲明的屬性也可用做一個遵守協(xié)議的限制
@property (nonatomic, strong) 類名<協(xié)議名稱> *屬性名;
@property (nonatomic, strong) id<協(xié)議名稱> 屬性名;
@property (nonatomic, strong) Dog<MyProtocol> *dog;
@property (nonatomic, strong) id<MyProtocol> dog2;
協(xié)議可用定義在單獨.h文件中,也可用定義在某個類中
如果這個協(xié)議只用在某個類中,應(yīng)該把協(xié)議定義在該類中
如果這個協(xié)議用在很多類中,就應(yīng)該定義在單獨文件中
分類可用定義在單獨.h和.m文件中,也可用定義在原來類中
一般情況下,都是定義在單獨文件
定義在原來類中的分類,只要求能看懂語法
- 動態(tài)類型能使程序直到執(zhí)行時才確定對象的真實類型
- 動態(tài)類型綁定能使程序直到執(zhí)行時才確定要對那個對象調(diào)用的方法
假設(shè) 子類 Dog 有一個特有的方法bark
[dog bark];
Animal *an = [Dog new];
[(Dog*)an bark]; //把父類的指針,強制類型轉(zhuǎn)換
點語法使用注意
p.age = 10; // [p setAge:10]
self.age = age; // [self setAge:age]
在@implementation 中定義的私有變量只能在本類中查看
Property 編譯器指令
生成setter 和 getter 方法聲明(未加強版)
-(void)setAge:(int)age;
-(int)age;
@property int age;
@synthesize age = _age;
setter和getter實現(xiàn)中會訪問成員變量_age, 如果成員變量_age不存在,就會自動生成一個@private的成員變量_age
@synthesize age;
setter和getter實現(xiàn)中會訪問@synthesize后同名成員變量age
如果成員變量age不存在,就會自動生成一個@private的成員變量age
多個屬性可以通過一行@synthesize搞定,多個屬性之間用逗號連接
@synthesize age = _age, number = _number, name = _name;
Property 增強
只要利用一個@property就可以同時生成setter/getter方法的聲明和實現(xiàn)傳入的屬性賦值給_開頭的成員變量
@property有一個弊端: 它只會生成最簡單的getter/setter方法的聲明和實現(xiàn), 并不會對傳入的數(shù)據(jù)進行過濾
如果想對傳入的數(shù)據(jù)進行過濾, 那么我們就必須重寫getter/setter方法如果不想對傳入的數(shù)據(jù)進行過濾, 僅僅是提供一個方法給外界操作成員變量, 那么就可以使用@property
注意: 如果沒有會自動生成一個_開頭的成員變量,自動生成的成員變量是私有變量, 聲明在.m中,在其它文件中無法查看,但當(dāng)可以在本類中查看
有就不生成,沒有就生成
- 如果重寫了setter方法, 那么property就只會生成getter方法
- 如果重寫了getter方法, 那么property就只會生成setter方法
- 如果同時重寫了getter/setter方法, 那么property就不會自動幫我們生成私有的成員變量
@property(屬性修飾符) 數(shù)據(jù)類型 變量名稱;
readwrite:代表生成 getter 和 setter 方法,默認(rèn)就是
readonly:代表只生成 getter 方法,(只讀)
修改 getter 方法名(常用)
程序員之間有一個約定, 一般情況下獲取BOOL類型的屬性的值, 我們都會將獲取的方法名稱改為isXXX
Person *p = [Person alloc] init]]
id p = [[Person alloc] init];
- 通過靜態(tài)數(shù)據(jù)類型定義變量, 不能調(diào)用子類特有的方法
- 通過動態(tài)數(shù)據(jù)類型定義變量, 可以調(diào)用子類特有的方法
- 通過動態(tài)數(shù)據(jù)類型定義的變量, 可以調(diào)用私有方法
id obj = [Student new];
[obj isKindOfClass:[Student class]]
//isKindOfClass , 判斷指定的對象是否是某一個類, 或者是某一個類的子類
用來初始化對象的方法.
重寫構(gòu)造方法的注意:
先調(diào)用父類的構(gòu)造方法.
再進行子類內(nèi)部的成員變量的初始化
返回當(dāng)前對象的地址
-(instancetype)init
{
// 注意: 不要把 = 號寫為 ==
// 一定要將[super init]的返回值賦值給self
if (self = [super init]) {
// 初始化子類
_age = 6;
}
return self;
}
自定義構(gòu)造方法
自己做自己的事情
父類的屬性交給父類的方法來處理,子類的方法處理子類自己獨有的屬性
自定義構(gòu)造方法必須以intiWith開頭,并且’W’必須大寫
類工廠方法:
用于快速創(chuàng)建對象的類方法, 我們稱之為類工廠方法
類工廠方法中主要用于 給對象分配存儲空間和初始化這塊存儲空間
規(guī)范:
- 一定是類方法 +
- 方法名稱以類的名稱開頭, 首字母小寫
- 一定有返回值, 返回值是id/instancetype
- 注意: 以后但凡自定義類工廠方法, 在類工廠方法中創(chuàng)建對象一定不要使用類名來創(chuàng)建,
一定要使用self來創(chuàng)建
return [[self alloc] init];
訪問原始類的成員變量
, 但不能添加變量, 只能添加方法. 如果想 添加變量, 可以考慮通過繼承創(chuàng) 建子類 @interface 類名 ()
@end
父類 → 子類→ 分類
。不管程序運行過程有沒有用到這個類, 都會調(diào)用 + load 方法。```
Class c = [Person class] // 類方法
Person *p = [Person new];
Class c1 = [p class]; // 對象方法
```
- 實例對象 → 類對象(對象方法)→ 元類對象(類方法)→ 根元類 (isa 指向自己)
如下圖 :
- description方法是基類NSObject 所帶的方法. 使用NSLog輸出OC對象,意義就不是很大,因為我們并不關(guān)心對象的內(nèi)存地址,比較關(guān)心的是對象內(nèi)部的一些成變量的值。因此,會經(jīng)常重寫description方法,覆蓋description方法 的默認(rèn)實現(xiàn)
-(NSString *) description { return [NSString stringWithFormat:@"age = %d", _age] }
不常用
)代表方法的簽名,在類對象的方法列表
中存儲著該簽名與方法代碼的對應(yīng)關(guān)系
每個方法都有一個與之對應(yīng)的 SEL類型的對象
- SEL 其實是對方法的一種包裝, 將方法包裝成一個 SEL 類型的數(shù)據(jù), 去找對應(yīng)的方法地址, 進而進行調(diào)用
- 注意:在這個操作過程中又緩存,第一次找的時候一個一個的找,非常耗性能,之后再用到的時候就直接使用
對象是否實現(xiàn)了某個方法
- - (BOOL) respondsToSelector: (SEL)selector 判斷實例是否實現(xiàn)這樣方法
- + (BOOL)instancesRespondToSelector:(SEL)aSelector; (類對象)
讓對象執(zhí)行某個方法
- (id)performSelector:(SEL)aSelector;
SEL 類型的定義 typedef struct objc_selector *SEL
//SEL 對象的創(chuàng)建
SEL s = @selector(test);
SEL s2 = NSSelectorFromString(@"test”);
// 將SEL對象轉(zhuǎn)為NSString對象
NSString *str = NSStringFromSelector(@selector(test));
Person *p = [Person new];
// 每個類都有以個_cmd 代表當(dāng)前方法
// 調(diào)用對象p的test方法
[p performSelector : @selector (test)];
- 每個OC對象都有自己的引用計數(shù)器,它是一個整數(shù),從字面上, 可以理解為”對象被引用的次數(shù)”
- 也可以理解為: 它表示有多少人正在用這個對象
占4個字節(jié)
什么是手動引用計數(shù)?
- 所有對象的內(nèi)容都需要我們手動管理, 需要程序員自己編寫release/retain等代碼
方法的基本使用
- retain : 計數(shù)器 +1 , 會返回對象本身
- release : 計數(shù)器 -1 , 沒有返回值(
release并不代表銷毀對象, 僅僅是計數(shù)器-1
) - retainCount : 獲取當(dāng)前的計數(shù)器
概念
- 僵尸對象: 所占用內(nèi)存已經(jīng)被回收的對象, 僵尸對象不能再使用
- 野指針: 指向僵尸對象(不可用內(nèi)存)的指針, 給野指針發(fā)送消息會報錯(EXC_BAD_ACCES)
- 空指針: 沒有指向任何東西的指針(儲存的東西是nil NULL 0), 給空指針發(fā)送消息不會報錯
- 基本數(shù)據(jù)類型: 直接復(fù)制
-(void)setAge:(int)age
{
_age = age;
}
```
- OC對象類型
```
-(void)setCar:(Car *)car
{
// 先判斷是不是新傳進來對象
if( car != _car)
{
// 對舊對象做一次release
[_car release]
// 對新對象做一次retain
_car = [car retain]
}
}
```
- 對self(當(dāng)前)所擁有的其他對象做一次release
- 當(dāng)一個對象要被回收的時候, 就會調(diào)用
- 一定要調(diào)用
[super dealloc]
, 這句調(diào)用放在最后面
- retain : release 舊值 , retain 新值 (適用于OC對象類型)
- assign : 直接賦值(默認(rèn), 適用于非OC對象類型)
- copy : release 舊值, copy 新值
- readwrite : 同時生成setter 和 getter的聲明, 實現(xiàn)(默認(rèn))
- readonly : 只會生成getter的聲明, 實現(xiàn)
- nonatomic : 性能高 (一般就用這個)
- atomic : 性能低(默認(rèn))
- setter : 決定了set方法的名稱, 一定要有個冒號 :
- getter : 決定了get方法的名稱(一般用在BOOL類型)
僅僅告訴編譯器,某個名稱是一個類
開發(fā)中引用一個類的規(guī)范
- 在.h 文件中用@class 來聲明類
- 在.m 文件中用#import 來包含類的所有東西
和#import 的區(qū)別(面試)
- import會包含引用類的所有信息(內(nèi)容),包括引用類的變量和方法
- @class僅僅是告訴編譯器有這么一個類, 具體這個類里有什么信息, 完全不知
總結(jié):
- 如果都在.h中import, 假如A拷貝了B, B拷貝了C , 如果C被修改了, 那么B和A都需要重新拷貝. 因為C修改了那么B就會重新拷貝, 而B重新拷貝之后相當(dāng)于B也被修改了, 那么A也需要重新拷貝. 也就是說如果都在.h中拷貝, 只要有間接關(guān)系都會重新拷貝
- 如果在.h中用@class, 在.m中用import, 那么如果一個文件發(fā)生了變化, 只有和這個文件有直接關(guān)系的那個文件才會重新拷貝
- 所以在.h中用@class可以提升編譯效率
兩端循環(huán)引用(面試)
retain
* 比如A對象retain了B對象,B對象retain了A對象,這樣會導(dǎo)致A對象和B對象永遠無法釋放。
* 當(dāng)兩端互相引用時,應(yīng)該一端用retain、一端用assign。
import
* 如果兩個類相互(#import<>
)拷貝, 例如A拷貝B, B拷貝A, 這樣會報錯
- 如何解決: 在.h中用@class, 在.m中用import
- 因為如果.h中都用import, 那么A拷貝B, B又拷貝A, 會形成死循環(huán)
- 如果在.h中用@class, 那么不會做任何拷貝操作, 而在.m中用import只會拷貝對應(yīng)的文件, 并不會形成死循環(huán)
- 不用關(guān)心對象釋放的時間
- 不用關(guān)心什么時候調(diào)用 release
- 占用內(nèi)存較大的對象不要隨便用 autorelease
- 占用內(nèi)存較小的對象使用 autorelease,沒有太大影響(影響:不能控制對象的釋放時間)
- alloc 之后調(diào)用了 autorelease ,又調(diào)用 release
Person *p = [[[Person alloc] init] autorelease];
[p release]; - 連續(xù)調(diào)用autorelease(野指針錯誤,每個autorelease 釋放時都會調(diào)用 release)
Person *p = [[[[Person alloc] init] auturelease] autorelease]
- 開發(fā)中經(jīng)常會提供一些類方法,快速創(chuàng)建一個已經(jīng)autorelease過的對象
- 創(chuàng)建對象時不要直接用類名,一般用 self
+ (id)person
{
return [[[self alloc] init] autorelease];
}
自動釋放池
在 IOS 程序運行中,會創(chuàng)建無數(shù)個池子。這些池子都是以“棧”結(jié)構(gòu)存在(先進后出,“杯子”)
當(dāng)一個對象調(diào)用 autorelease 方法時,會將這個對象放到棧頂?shù)尼尫懦?(”棧頂“ 相當(dāng)于杯子底)
集合對象的內(nèi)存管理
- 當(dāng)把一個對象添加到集合中時,這個對象會做了一次retain操作,計數(shù)器會+1
- 當(dāng)一個集合被銷毀時,會對集合里面的所有對象做一次release操作,計數(shù)器會-1
- 當(dāng)一個對象從集合中移除時,這個對象會一次release操作,計數(shù)器會-1
ARC 的判斷準(zhǔn)則:主要沒有強指針指向?qū)ο螅蜁尫艑ο?/p>
指針的2種類型
強指針:_strong 默認(rèn)情況,所有指針都是強指針
弱指針:_weak
- _weak Person *p = [[Person alloc] init] 錯誤寫法,沒有意義的寫法,一創(chuàng)建就釋放
ARC 特點
- 不允許調(diào)用 release、retain、retainCount
- 允許重寫 dealloc,但是不允許調(diào)用[super dealloc]
- @property 的參數(shù)
- strong : 成員變量是強指針(適用于 OC 對象類型)
- weak : 成員變量是弱指針(適用于 OC 對象類型)
- assign:適用于非 OC 對象
循環(huán)引用
一端用:strong,另一端用:weak
在Compiler Flags一列加上-fno-objc-arc就表示禁止這個.m文件的ARC
mrc 可以轉(zhuǎn) arc ,系統(tǒng)轉(zhuǎn)換
- 因為拷貝要求修改原來的對象不能影響到拷貝出來得對象
- 修改拷貝出來的對象也不能影響到原來的對象, 所以需要生成一個新的對象
互不影響
- 實現(xiàn)拷貝的方法有2個
- copy:返回不可變副本
- mutableCopy:返回可變副本
- 遵守NSCopying協(xié)議
- 實現(xiàn)-copyWithZone:方法
- 創(chuàng)建新對象
- 給新對象的屬性賦值
- block內(nèi)部可以訪問外面的變量
- 默認(rèn)情況下,block內(nèi)部不能修改外面的局部變量
- 給局部變量加上__block關(guān)鍵字,這個局部變量就可以在block內(nèi)部修改
typedef int (^MyBlock)(int, int);
// 以后就可以利用MyBlock這種類型來定義block變量
- 默認(rèn)情況下block存儲在棧中, 如果對block進行一個copy操作, block會轉(zhuǎn)移到堆中
- 如果block在棧中, block中訪問了外界的對象, 那么不會對對象進行retain操作
- 但是如果block在堆中, block中訪問了外界的對象, 那么會對外界的對象進行一次retain
- 如果在block中訪問了外界的對象, 一定要給對象加上__block, 只要加上了__block, 哪怕block在堆中, 也不會對外界的對象進行retain
- 如果是在ARC開發(fā)中就需要在前面加上__weak
Person *p = [Person new];
__weak Person *weakP = p;
void (^myBlock) () = ^{
weakP.age = 10;
};
myBlock();
NSLog(@"age = %li", p.age);
協(xié)議
@protocol 協(xié)議名稱 < NSObeject >
// 方法聲明列表....
@end
如何遵守協(xié)議
類遵守協(xié)議
@interface 類名 : 父類名 <協(xié)議名稱1, 協(xié)議名稱2>
@end
協(xié)議遵守協(xié)議
@protocol 協(xié)議名稱 <其他協(xié)議名稱1, 其他協(xié)議名稱2>
@end
協(xié)議中方法聲明的關(guān)鍵字
@required (默認(rèn)): 要求實現(xiàn),如果沒有實現(xiàn),會發(fā)出警告
@optional: 不要求實現(xiàn),不會有警告
定義一個變量的時候,限制這個變量保存的對象遵守某個協(xié)議
如果沒有遵守對應(yīng)的協(xié)議,編譯器會警告
類名<協(xié)議名稱> *變量名;
id<協(xié)議名稱> 變量名;
NSObject<MyProtocol> *obj;
id<MyProtocol> obj2;
@property中聲明的屬性也可用做一個遵守協(xié)議的限制
@property (nonatomic, strong) 類名<協(xié)議名稱> *屬性名;
@property (nonatomic, strong) id<協(xié)議名稱> 屬性名;
@property (nonatomic, strong) Dog<MyProtocol> *dog;
@property (nonatomic, strong) id<MyProtocol> dog2;
協(xié)議可用定義在單獨.h文件中,也可用定義在某個類中
如果這個協(xié)議只用在某個類中,應(yīng)該把協(xié)議定義在該類中
如果這個協(xié)議用在很多類中,就應(yīng)該定義在單獨文件中
分類可用定義在單獨.h和.m文件中,也可用定義在原來類中
一般情況下,都是定義在單獨文件
定義在原來類中的分類,只要求能看懂語法