2.1 屬性
1. "對(duì)象"(object)就是 "基本構(gòu)造單元"(building block),開發(fā)者可以通過對(duì)象來存儲(chǔ)并傳遞數(shù)據(jù)。
在對(duì)象直接傳遞數(shù)據(jù)并執(zhí)行任務(wù)的過程就叫做 "消息傳遞"(Messaging)。
2. 程序運(yùn)行起來后,為其提供相關(guān)支持的代碼叫做"運(yùn)行期環(huán)境"(runtime),它提供一些使得對(duì)象之間能夠傳遞消息的重要函數(shù)。
理解運(yùn)行期環(huán)境,可以幫你寫出高效且易維護(hù)的代碼。
3. Oc編譯采用“應(yīng)用程序二進(jìn)制接口”(Application Binary Interface,ABI)
把實(shí)例變量當(dāng)作一種存儲(chǔ)偏移量所用的 “特殊變量”(speacial variable),交由 “類對(duì)象”(class object)保管。偏移量會(huì)在運(yùn)行期查找,這樣子總能找到正確的偏移量,這是穩(wěn)固。
如果對(duì)象布局在編譯器就固定了,訪問變量時(shí),編譯器會(huì)使用 “偏移量”(offset)來計(jì)算,這個(gè)偏移量是 “硬編碼”(hardcode),表示該變量距離存放對(duì)象的內(nèi)存區(qū)域的起始地址有多遠(yuǎn)。 存在一個(gè)問題:如果代碼使用了編譯期計(jì)算出來的偏移量,那么修改類定義之后必須重新編譯,否則就會(huì)出錯(cuò)。
@property
1. 用于聲明屬性,自動(dòng)添加實(shí)例變量,以下劃線開頭,自動(dòng)實(shí)現(xiàn)屬性的讀寫方法。
2. 在實(shí)現(xiàn)文件中可以通過@synthesize 語法來指定實(shí)例變量的名字
@implementation EOCPerson
@synthesize name = _myName;
@end
3. @dynamic 關(guān)鍵字會(huì)告訴編譯器:不要自動(dòng)創(chuàng)建實(shí)現(xiàn)屬性所用的實(shí)例變量,也不要為其創(chuàng)建存取方法
屬性特質(zhì)
原子性、讀寫權(quán)限、內(nèi)存管理語義、方法名、其他。
1、原子性
-
atomic -默認(rèn)
- 原子性,會(huì)生成讀寫鎖,讀寫安全(線程不一定安全),占用資源、效率一般。
-
nonatomic
- 非原子、效率高、讀寫不安全
2、讀寫權(quán)限
-
readwrite -默認(rèn)
- 讀寫
-
readonly
- 只讀
3、內(nèi)存管理
MRC時(shí),有assign、retain、copy,ARC加入了strong、weak
-
assign -值類型默認(rèn)
- 簡(jiǎn)單賦值、用于值類型,如CGFloat、NSInteger等
-
strong (同retain -MRC) -引用類型默認(rèn)
強(qiáng)引用、用于引用類型
賦值時(shí),保留新值,新值引用計(jì)數(shù)+1,釋放舊值(引用計(jì)數(shù)-1)。
用于所有的實(shí)例變量和局部變量、其他常規(guī)對(duì)象引用。
注意:可變對(duì)象應(yīng)該使用strong,如NSMultiString,NSMultiArray
-
copy
復(fù)制、用于引用類型
賦值時(shí),拷貝新值(新對(duì)象引用計(jì)數(shù)為1),釋放舊值(引用計(jì)數(shù)-1),不改變新值(引用計(jì)數(shù)不變)。
copy的本質(zhì)為復(fù)制該內(nèi)存所存儲(chǔ)的內(nèi)容,重新創(chuàng)建一個(gè)對(duì)象賦給其相同的內(nèi)容,對(duì)于實(shí)現(xiàn)了NSCopying協(xié)議的對(duì)象有效。
用于不可變對(duì)象:NSString、block、NSArray、NSDictionary等
注意:用于可變對(duì)象時(shí),設(shè)置值后,變?yōu)椴豢勺儗?duì)象
-
weak
弱引用、用于引用類型
賦值時(shí)、單純的引用新對(duì)象地址,不改變新對(duì)象(引用計(jì)數(shù)不變),不改變舊對(duì)象(引用計(jì)數(shù)不變)
當(dāng)引用對(duì)象釋放后,其值置為nil
-
__unsafe_unretained
類似assign、適用于引用類型、不安全的弱引用
功能類似于weak、對(duì)象摧毀后,不置nil、不安全,可用weak代替
4、方法名
-
getter = methodname
@property (nonatomic, getter = isOn) BOOL on; -
setter = methodname
@property (nonatomic, setter = setOnState) BOOL on;
5、其他
nonnull, null_resettable, nullable
2.2 對(duì)象訪問
1. 對(duì)象訪問有兩種、一種是實(shí)例訪問、一種是屬性的讀寫方法訪問。
一般可以這樣做:讀取時(shí),通過實(shí)例讀取、寫入時(shí),通過屬性方法寫入。初始化時(shí),都用實(shí)例。
一般外部訪問時(shí),通過屬性訪問。
內(nèi)部訪問時(shí)、無特殊情況,通過實(shí)例訪問。
2. 具體情況應(yīng)該根據(jù)他們的特點(diǎn)來定:
實(shí)例訪問不通過屬性方法派發(fā)、效率高。
實(shí)例訪問、不觸發(fā)“鍵值觀測(cè)”,無法滿足某些場(chǎng)景。
初始化方法中,盡量實(shí)例訪問,避免子類重寫設(shè)置方法,導(dǎo)致出錯(cuò)。
如果待初始化的實(shí)例聲明在超類中,而我們又無法在子類直接訪問此實(shí)例變量的話,那么就需要調(diào)用 “設(shè)置方法” 了。
在 “惰性初始化”(lazy initialization),必須通過 “獲取方法” 來訪問屬性,不然實(shí)例變量永遠(yuǎn)不會(huì)初始化。
-(EOCBrain *)brain{
if(!_brain){
_brain = [EOCBrain new];
}
return _brain;
}
2.3 對(duì)象同等性
1. == 與 isEqual
-
==
== 用于值對(duì)象時(shí),可以直接判斷值是否相等。
== 用于引用對(duì)象時(shí),是判斷兩個(gè)對(duì)象的指針是否相等(為同一個(gè)對(duì)象),不能判斷其內(nèi)容等同。
-
isEqual
- 用于引用類型、判斷內(nèi)容是否等同、常需要ovewrite該方法。
2. NSObject 協(xié)議中有兩個(gè)用于判斷等同性的關(guān)鍵方法:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
如果 “isEqual” 方法判定兩個(gè)對(duì)象相等,那么其hash 方法也必須返回同一個(gè)值。
但是,如果兩個(gè)對(duì)象的hash 方法返回同一個(gè)值,那么 “isEqual” 方法未必會(huì)認(rèn)為兩者相等。
hash方法實(shí)現(xiàn)的一些情況:
// 1-固定值
- (NSUInteger)hash {
return 12312312;
}
// 這種會(huì)對(duì)collection使用這個(gè)對(duì)象產(chǎn)生性能問題。因?yàn)樵赾ollection 在檢索哈希表的時(shí),會(huì)用對(duì)象的哈希碼來做索引,在set 集合中,會(huì)根據(jù)哈希碼把對(duì)象分裝到不同的數(shù)組里面,在添加新對(duì)象的時(shí)候,要根據(jù)其哈希碼找對(duì)與之對(duì)應(yīng)的數(shù)組,依次檢查其中各個(gè)元素,看數(shù)組已有的對(duì)象是否和將要添加的新對(duì)象相等,如果相等,就說明添加的對(duì)象已經(jīng)在set 集合中了,是添加失敗的。(如果所有對(duì)象的hash 值對(duì)一樣,這樣子set 集合只會(huì)有一個(gè)數(shù)組,所有數(shù)據(jù)都在一起了,每次插入數(shù)據(jù)都會(huì)遍歷這個(gè)數(shù)組,這樣子就會(huì)出現(xiàn)性能問題)
// 2-組合值
- (NSUInteger)hash {
NSString *stringToHash = [NSString stringWithFormat@"%@:%@",_firstName,_lastNmae];
return [stringToHash hash];
}
//這樣子能在一定情況下保證返回不同的哈希碼,但是這里會(huì)存在創(chuàng)建字符串的開銷,會(huì)比返回單一值要慢
// 3-位運(yùn)算
- (NSUInteger)hash {
return [self.firstName hash] ^ [self.lastNmae hash];
}
// ^為逐位邏輯運(yùn)算符,它表示逐位非或(如果只有一個(gè)位為1,那么結(jié)果為1;否則為0。)。
//這樣子可以保存較高的效率,又不會(huì)過于頻繁的重復(fù)
3. 特定類所具有的等同性判定方法
isEqualToString、isEqualToArray、isEqualToDictionary
4. 容器中可變類的等同性
- 如果要把某個(gè)對(duì)象放入colloection ,其 hash 方法的生成策略就應(yīng)該保證在放入colloection 后,hash 值不再改變。不然會(huì)出現(xiàn)問題。
2.4 類族模式
1. “類族” (class cluster)是一種很有用的模式(pattern),可以隱藏 “抽象基類” (abstract base class)背后的實(shí)現(xiàn)細(xì)節(jié)。
2. 用戶無須自己創(chuàng)建子類實(shí)例,只需要調(diào)用基類方法來創(chuàng)建即可。
3. 如何創(chuàng)建類族
每個(gè) “實(shí)體子類” 都從基類繼承而來,“工廠模式” 是創(chuàng)建類族的辦法之一,調(diào)用基類方法返回子類實(shí)例。
如果對(duì)象所屬的類位于某個(gè)類族中,那么查詢其類型信息要注意,你可能覺得自己創(chuàng)建了某個(gè)類的實(shí)例,然后實(shí)際上創(chuàng)建的卻是其子類的實(shí)例。
-(BOOL) isKindOfClass: classObj; //判斷是否是這個(gè)類或者這個(gè)類的子類的實(shí)例
-(BOOL) isMemberOfClass: classObj; //判斷是否是這個(gè)類的實(shí)例
2.5 關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù)
1. 關(guān)聯(lián)對(duì)象
可以給某個(gè)對(duì)象關(guān)聯(lián)許多其他對(duì)象,這些對(duì)象通過“鍵”來區(qū)分。存儲(chǔ)對(duì)象值的時(shí)候,可以指明“存儲(chǔ)策略”,用以維護(hù)相應(yīng)的“內(nèi)存管理語義”。
OBJC_ASSOCIATION_ASSIGN --- assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC --- nonatomic, retain
OBJC_ASSOCIATION_COPY_NONATOMIC --- nonatomic, copy
OBJC_ASSOCIATION_RETAIN --- retain
OBJC_ASSOCIATION_COPY --- copy
下列方法可以管理關(guān)聯(lián)對(duì)象:
void objc_setAssociatedObject (id object, void *key, id value, objc_AssociationPolicy policy)
// 此方法以給定的鍵和策略為某對(duì)象設(shè)置關(guān)聯(lián)對(duì)象值
id objc_getAssociatedObject(id object, void *key)
// 此方法根據(jù)給定的鍵從某個(gè)對(duì)象中獲取相應(yīng)的關(guān)聯(lián)對(duì)象值
void objc_removeAssociatedObject(id object)
// 此方法移除指定對(duì)象的全部關(guān)聯(lián)對(duì)象
若想令兩個(gè)健匹配到相同的一個(gè)值,則二者必須是完全相同的指針才行。所以,在設(shè)置關(guān)聯(lián)對(duì)象值時(shí):通常使用靜態(tài)全局變量做鍵。
2.6 消息
一. 消息傳遞(objc_msgSend)
1. 調(diào)用對(duì)象方法,在Objective-C 中叫做 “傳遞消息”(pass a message),消息有 “名稱”(name)或“選擇子”(selector),可以接受參數(shù),而且可能還有返回值。
objc_megSend 的原型:
// 方法原型
// messageName 叫做 selector(選擇子),選擇子和參數(shù)合起來稱為"消息"。
id returnValue = [receiveObject messageName:parameter];
// 所有方法都是普通的 C 語言函數(shù),方法轉(zhuǎn)為標(biāo)準(zhǔn)的 C 語言函數(shù)如下:
// 是一個(gè) “參數(shù)個(gè)數(shù)可變的函數(shù)”,能夠接受兩個(gè)或兩個(gè)以上的參數(shù),
// 第一個(gè)參數(shù)代表接收者,第二個(gè)參數(shù)代表選擇子,后續(xù)參數(shù)就是參數(shù)。
void objc_msgSend(id self,SEL cmd,...)
2. objc_megSend 函數(shù)會(huì)依據(jù)接收者和選擇子來調(diào)用適當(dāng)?shù)姆椒ǎ?/strong>
- 在接收者所屬的類搜尋其 “方法列表”
- 找不到的話,就沿著繼承體系繼續(xù)向上查找
- 最終還是找不到相符的方法就執(zhí)行 “消息轉(zhuǎn)發(fā)”
3. 每個(gè)類里都有一張函數(shù)表,選擇子的名稱則是表的 “鍵”,對(duì)應(yīng)的值都是指向函數(shù)的指針。objc_msgSend 等函數(shù)就是通過這個(gè)函數(shù)表來尋找應(yīng)該執(zhí)行的方法并執(zhí)行跳轉(zhuǎn)的。
4. objc_msgSend 會(huì)將匹配結(jié)果緩存在 “快速映射表”(fast map)里面,每個(gè)類都有這樣子的一塊緩存,接下來還向該類發(fā)送一樣的消息,那么執(zhí)行起來就很快了。
5. 這里有些特殊情況,需要由Objective-C 運(yùn)行環(huán)境的另外一些函數(shù)來處理:
- objc_msgSend_stret :如果待發(fā)送的消息要返回結(jié)構(gòu)體,那么可以交由此函數(shù)處理。只有當(dāng)CPU 寄存器能夠容納得下消息返回類型時(shí),這個(gè)函數(shù)才能處理此消息。若是返回值無法容納于CPU 寄存器(比如說返回的結(jié)構(gòu)體太大了),那么就由另外一個(gè)函數(shù)執(zhí)行派發(fā)。此時(shí),那個(gè)函數(shù)會(huì)通過分配在棧上的某個(gè)變量來處理消息所返回的結(jié)構(gòu)體。
- objc_msgSend_fpret:如果消息返回的是浮點(diǎn)數(shù),可以交由此函數(shù)處理。這個(gè)函數(shù)是為了處理x86 等架構(gòu)CPU 中某些令人驚訝的奇怪狀況。
- objc_msgSendSuper:如果要給超類發(fā)消息,那么就交由此函數(shù)處理。
6. 如果某函數(shù)的最后一項(xiàng)操作是調(diào)用另外一個(gè)函數(shù),那么就可以運(yùn)用 “尾調(diào)用優(yōu)化” 技術(shù)。編譯器會(huì)生成跳轉(zhuǎn)至另外一個(gè)函數(shù)所需的指令碼,而且不會(huì)向調(diào)用棧推入新的 “棧幀”。
二、 消息轉(zhuǎn)發(fā)
當(dāng)對(duì)象接收到無法解讀的消息后,就會(huì)啟動(dòng) “消息轉(zhuǎn)發(fā)”(message forwarding)機(jī)制,程序員可經(jīng)由此過程告訴對(duì)象應(yīng)該如何處理未知消息。
消息轉(zhuǎn)發(fā)分為兩大階段:
1. 動(dòng)態(tài)方法解析
第一階段選征詢接收者,所屬的類,看其是否能動(dòng)態(tài)添加方法,以處理當(dāng)前這個(gè) “未知的選擇子“(unknown seletor),這叫做 ”動(dòng)態(tài)方法解析“(dynamic method resolution)。
//表示這個(gè)類是否能新增一個(gè)方法來處理此選擇子
+ (BOOL)resolveClassMethod:(SEL)sel
+ (BOOL)resolveInstanceMethod:(SEL)sel
2. 完整的消息轉(zhuǎn)發(fā)機(jī)制
第二階段涉及 ”完整的消息轉(zhuǎn)發(fā)機(jī)制“(full forwarding mechanism)。
如果運(yùn)行期系統(tǒng)已經(jīng)把第一階段執(zhí)行完了,那么接收者自己就無法再以動(dòng)態(tài)新增方法的手段來響應(yīng)包含該選擇子的消息了。這里的第二階段又分為下面兩小步:
-
1) 備援的接收者
首先,請(qǐng)接收者看看有沒其他對(duì)象能處理這條消息;若有,則運(yùn)行期系統(tǒng)會(huì)把消息轉(zhuǎn)給那個(gè)對(duì)象,于是消息轉(zhuǎn)發(fā)過程結(jié)束,一切正常。
- (id)forwardingTargetForSelector:(SEL)aSelector
-
2) 若沒有 ”備援的接收者“(replacement receiver),則啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制。
運(yùn)行期系統(tǒng)會(huì)把與消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation 對(duì)象中,再給接受者最后一次機(jī)會(huì),令其設(shè)法解決當(dāng)前還未處理的這條消息。
- (void)forwardInvocation:(NSInvocation *)anInvocation
2.7 方法調(diào)配
1. 不需要源代碼,也不需要通過繼承子類來覆寫方法就能改變這個(gè)類本身的功能,新功能在本類的所有實(shí)例都生效,此方案稱為 “方法調(diào)配”(method swizzling)。
2. 每個(gè)類有個(gè)方法列表(函數(shù)指針 IMP),各自映射到自己的方法實(shí)現(xiàn),只要我們能操作這個(gè)函數(shù)指針的指向,我們就可以動(dòng)態(tài)的增加替換原有的方法。
3. 互換兩個(gè)已經(jīng)寫好的方法實(shí)現(xiàn):
// 獲取方法實(shí)現(xiàn):
Method class_getInstanceMethod(Class cls, SEL name)
// 調(diào)換方法實(shí)現(xiàn)
void method_exchangeImplementations(Method m1, Method m2)
4. 為已有方法增加新功能:
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
2.8 理解 “類對(duì)象” 的用意
1. 每個(gè)Objective-C 對(duì)象實(shí)例都是指向某塊內(nèi)存數(shù)據(jù)的指針。
2. Objective-C 對(duì)象所用的數(shù)據(jù)結(jié)構(gòu)
struct objc_object {
Class isa;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
每個(gè)對(duì)象結(jié)構(gòu)體首個(gè)成員是Class 類的變量,定義了對(duì)象所屬的類,通常稱為 “is a” 指針。
3. Class 對(duì)象的數(shù)據(jù)結(jié)構(gòu)定義
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
Class 首個(gè)變量也是isa 指針,說明Class 本身也是Objective-C 對(duì)象,指向 “元類”(meta class)。
4. 在類繼承體系中查詢類型信息
// 判斷對(duì)象是否為某個(gè)特定類的實(shí)例,不能判定 super 類
isMemberOfClass
// 判斷出對(duì)象是否為某類或其派生類的實(shí)例
isKindOfClass