6舶治、理解“屬性”這一概念
Objective-C面向?qū)ο笳Z言編程分井。
對象就是“基本構(gòu)造單元”,開發(fā)者用對象存儲并傳遞數(shù)據(jù)霉猛。
對象之間傳遞數(shù)據(jù)并且執(zhí)行任務(wù)的過程就叫做“消息傳遞”
程序運(yùn)行起來尺锚。相關(guān)支持的代碼就叫做“Objective-C runtime”。“屬性”是Objective-C的一項(xiàng)特性韩脏,用于封裝對象中的數(shù)據(jù)缩麸。
Objective-C對象會把數(shù)據(jù)保存為各種實(shí)例變量铸磅。
實(shí)例變量通過“存取方法”(accessmethod)訪問赡矢。
getter 用于讀取變量值
setter用于寫入變量值@property
對象接口的定義中杭朱,可以使用屬性。 -> 標(biāo)準(zhǔn)的寫法
編譯器會自動(dòng)寫出一套存取方法吹散,用以訪問給定類型中具有給定名稱的變量弧械。
@property (nonatomic ,copy) NSString *nameString;
等同于下面的這種寫法
- (void)setNameString:(NSString *)nameString;
- (NSString *)nameString;
-
屬性優(yōu)勢:
- 編譯器自動(dòng)編寫與訪問這些屬性所需的方法 =“自動(dòng)合成”(autosynthesis)
這個(gè)過程是由編譯器在編譯時(shí)期執(zhí)行的,所以編輯器看不到這些“合成方法”的源代碼空民。 - 除了生成方法代碼之外刃唐,編譯器還會自動(dòng)向類中添加適當(dāng)類型的實(shí)例變量,并且添加下劃線作為實(shí)例變量的名字界轩。
比如上面的名稱就為_nameString画饥。
- 編譯器自動(dòng)編寫與訪問這些屬性所需的方法 =“自動(dòng)合成”(autosynthesis)
-
自己實(shí)現(xiàn)存取方法
- 使用@dynamic關(guān)鍵字 。告訴編譯器不讓自動(dòng)創(chuàng)建實(shí)例變量浊猾、存取方法抖甘!
-
屬性特質(zhì)
- 原子性
默認(rèn)情況下。編譯器合成的方法通過通過鎖定機(jī)制確實(shí)其原子性(atomicity)葫慎。
如果屬性具備nonatomic特質(zhì)衔彻,則不使用同步鎖。 - 讀/寫權(quán)限
readwrite(讀寫):具有setter和getter方法偷办!
readonly(只讀):僅有g(shù)etter方法艰额。 - 內(nèi)存管理
assign:針對CGFloat或者NSInteger等“純量類型”(基礎(chǔ)數(shù)據(jù)類型 和C數(shù)據(jù)類型)簡單賦值,不更改索引計(jì)算椒涯。
strong:為屬性設(shè)置新值時(shí)柄沮,保留新值,釋放舊值废岂,再將新值設(shè)置上去铡溪。
weak:既不保留新值,也不釋放舊值泪喊,同assign棕硫。
copy:與strong類似,但是并不保留新值袒啼。而是將其“拷貝”哈扮。
經(jīng)典案列:NSString 確保對象中的字符串值不會無意間改動(dòng)。
- 原子性
7蚓再、在對象內(nèi)部盡量直接訪問實(shí)例變量
- 使_XXX直接訪問實(shí)例變量滑肉。
- 速度快。不經(jīng)過Objective-C的“方法派發(fā)”(method dispatch)摘仅,編譯器直接訪問保存對象實(shí)例變量的那塊內(nèi)存
- 直接訪問實(shí)例變量靶庙,不調(diào)取“設(shè)置方法”(setter)這就繞過了內(nèi)存管理語義。
- 直接訪問實(shí)例變量娃属,也不會觸發(fā)“鍵值觀測”(Key-Value Observing六荒, KVO)
這樣做是否有問題還是取決于具體的對象行為护姆。
- 使用self.XXX來訪問
- 有助于排查與之相關(guān)的錯(cuò)誤,因?yàn)榭梢越o“setter”和“getter”設(shè)置斷點(diǎn)
- 懶加載也是需要通過“獲取方法”來訪問屬性掏击。否則卵皂。實(shí)例變量永遠(yuǎn)不會初始化。
- 建議:
除特殊情況砚亭。
在讀取實(shí)例變量的時(shí)候采用直接訪問的形式灯变,
在設(shè)置實(shí)例變量的時(shí)候通過屬性來做。
8捅膘、理解“對象等同性”這一概念
等同性(equality)
一般情況下相比較我們都是用的 “==” 但是比較出來的結(jié)果未必是我們想要的添祸。
因?yàn)椤?=”比較是指針本身,而不是其對象寻仗。-
NSObject中的“isEqual”
- 判斷兩個(gè)對象的等同性膝捞。
- NSObject協(xié)議中2個(gè)判斷等同性的關(guān)鍵方法
- (BOOL)isEqual:(id)object; @property (readonly) NSUInteger hash;
定義:如果“isEqual:”方法判斷兩個(gè)對象相等,那么其hash方法也必須返回同一個(gè)值愧沟。
如果兩個(gè)對象的hash方法返回同一個(gè)值蔬咬,“isEqual”未必會認(rèn)為兩者相等。
NSString實(shí)現(xiàn)了一個(gè)獨(dú)有的等同性判斷方法:isEqualToString
該方法比“isEqual”快沐寺,因?yàn)樵摲椒爝f對象規(guī)定為NSString林艘。而“isEqual”還要執(zhí)行額外步驟,因?yàn)椤癷sEqual”不知道受測對象類型混坞。NSArray與NSDictionary也有類似的特殊的等同性方法狐援。
“isEqualToArray”與“isEqualToDictionary”
如果檢測到受測對象不是數(shù)組或者字典就會拋出異常。-
自己實(shí)現(xiàn)等同性方法原理:
- 首先究孕,判斷兩個(gè)指針是否相等啥酱。相等則說明指向同一對象!
- 其次厨诸,比較兩個(gè)對象所屬的類(考慮到父類與子類的判斷)
- 然后镶殷,檢測每個(gè)屬性是否相等(不要盲目逐步檢查每條屬性,而是根據(jù)需求來定制)
- 最后微酬,實(shí)現(xiàn)hash方法:
等同性約定:若兩個(gè)對象相等绘趋,則哈希碼相等,但是兩個(gè)哈希碼相同的對象卻未必相等颗管。(應(yīng)使用計(jì)算速度快而且哈希碼碰撞幾率低的算法陷遮,否則會影響性能)
-
小技巧:
- 如果要重寫“isEqual”方法。
如果受測參數(shù)與接收消息對象屬于同一個(gè)類垦江,就調(diào)用自己寫的判定方法帽馋。否則就交給超類來判斷。 - 等同性的判斷深度。
如果判斷兩個(gè)對象的所有屬性是否相等绽族,這樣的叫“深度等同性判定”姨涡。
不過更多時(shí)候是根據(jù)其中部分?jǐn)?shù)據(jù)即可判斷二者是否等同。
- 如果要重寫“isEqual”方法。
9项秉、以“類族模式”隱藏實(shí)現(xiàn)細(xì)節(jié)
- 類族:把實(shí)現(xiàn)細(xì)節(jié)隱藏在一套簡單的公共接口后面绣溜。
- 例子:
- 系統(tǒng)框架中有很多類族慷彤。
比如UIButton創(chuàng)建的時(shí)候的類方法
UIButton *button = [UIButton buttonWithType:<#(UIButtonType)#>]
這樣該方法返回的對象娄蔼,決定傳入的按鈕類型。 - NSArray與可變類型NSMutableArray底哗。
有兩個(gè)抽象基類岁诉,一個(gè)不可變數(shù)組,一個(gè)可變數(shù)組跋选。 - 動(dòng)手創(chuàng)建一個(gè)類族:
- 需求:一個(gè)公司分為2種人:1涕癣、管理者。2前标、工人坠韩。分別干不同的事!
- 創(chuàng)建一個(gè)基于NSObject的類
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, SCUserType) {
SCUserTypeManager,
SCUserTypeWorker,
};
@interface SCUser : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
//創(chuàng)建
+ (SCUser *)scUserWithType:(SCUserType)type;
//做事情
- (void)doWork;
@end
- .m的實(shí)現(xiàn):
#import "SCUser.h"
#import "SCUserWorker.h"
#import "SCUserManager.h"
@implementation SCUser
+ (SCUser *)scUserWithType:(SCUserType)type {
switch (type) {
case SCUserTypeManager:
return [SCUserManager new];
break;
case SCUserTypeWorker:
return [SCUserWorker new];
break;
}
}
- (void)doWork {
}
@end
每個(gè)“實(shí)體子類” 都是從基類繼承來的炼列。比如:
#import "SCUser.h"
@interface SCUserManager : SCUser
@end
//.m的實(shí)現(xiàn)
#import "SCUserManager.h"
@implementation SCUserManager
- (void)doWork {
NSLog(@"管理者巡邏");
}
@end
10只搁、在既有類中使用關(guān)聯(lián)對象存放自定義數(shù)據(jù)
-
關(guān)聯(lián)對象(Associated Object)
為了解決某些情況(無法從對象所屬的類中繼承一個(gè)子類,然后用子類對象存放相關(guān)信息)
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
此方法可以給定的鍵和策略為某對象設(shè)置關(guān)聯(lián)對象值objc_getAssociatedObject(id object, const void *key)
此方法根據(jù)給定的鍵從某個(gè)對象中獲取相關(guān)對象值
objc_removeAssociatedObjects(id object)此方法移除指定對象的全部關(guān)聯(lián)對象俭尖。
項(xiàng)目中遇到的問題(runtime)
當(dāng)時(shí)是為了解決NSURL中有個(gè)URLWithString的方法會默認(rèn)把帶中文的鏈接給轉(zhuǎn)碼氢惋。因?yàn)槌绦蛑泻芏嗟胤蕉加玫搅诉@個(gè)方法,很明顯稽犁,一個(gè)一個(gè)的修改是很費(fèi)力氣的焰望。用到了runtime中的一個(gè)交換方法:
自己實(shí)現(xiàn)一個(gè)函數(shù),然后與系統(tǒng)的函數(shù)交換一下已亥。完美解決問題熊赖。
主要代碼:
#import <objc/runtime.h>
@implementation NSURL (Unicode)
+ (void)load {
/*
self:UIImage
誰的事情,誰開頭 1.發(fā)送消息(對象:objc) 2.注冊方法(方法編號:sel) 3.交互方法(方法:method) 4.獲取方法(類:class)
Method:方法名
獲取方法,方法保存到類
Class:獲取哪個(gè)類方法
SEL:獲取哪個(gè)方法
imageName
*/
// 獲取imageName:方法的地址
Method URLWithStringMethod = class_getClassMethod(self, @selector(URLWithString:));
// 獲取wg_imageWithName:方法的地址
Method sc_URLWithStringMethod = class_getClassMethod(self, @selector(sc_URLWithString:));
// 交換方法地址,相當(dāng)于交換實(shí)現(xiàn)方式2
method_exchangeImplementations(URLWithStringMethod, sc_URLWithStringMethod);
}
+ (NSURL *)sc_URLWithString:(NSString *)URLString {
NSString *newURLString = [self IsChinese:URLString];
return [NSURL sc_URLWithString:newURLString];
}
11虑椎、理解 objc_msgSend 的作用
-
Objective-C中給對象發(fā)消息
[object message:parameter];
原理:
objc_msgSend(id self, SEL cmd,...)
核心函數(shù)秫舌,這個(gè)是“參數(shù)個(gè)數(shù)可變的函數(shù)”,能接收兩個(gè)或者兩個(gè)以上的參數(shù)绣檬。
第一個(gè)參數(shù)代表接收者足陨。第二個(gè)參數(shù)代表方法的名字。
上面的OC代碼換成函數(shù)就為:
objc_msgSend(object, @selector(message:),parameter);
objc_msgSend函數(shù)根據(jù)接收者和方法名來調(diào)用適當(dāng)?shù)姆椒ā?br> 過程:
1.先到所屬的類尋找“方法列表”娇未,找到就跳轉(zhuǎn)
2.找不到墨缘,就會沿著繼承體系繼續(xù)向上查找,找到再跳轉(zhuǎn)。
3.實(shí)在找不到就執(zhí)行“消息轉(zhuǎn)發(fā)”
看起來調(diào)用一個(gè)方法需要很多步驟镊讼。但是objc_msgSend會將匹配結(jié)果緩存在“快速映射表”里宽涌。這樣子執(zhí)行就快了。 其他的方法:
// 待發(fā)送消息返回結(jié)構(gòu)體
objc_msgSend_stret
//消息返回的是浮點(diǎn)數(shù)
objc_msgSend_fpret
//給超類發(fā)消息 例如[super XXX];
objc_msgSendSuper
大家碼代碼時(shí)期能更多的了解一些底層的工作原理蝶棋。在調(diào)試的時(shí)候會幫助你很多卸亮。
12、理解消息轉(zhuǎn)發(fā)機(jī)制
- 消息轉(zhuǎn)發(fā):
因?yàn)镺bjective-C中玩裙,在編譯期向類發(fā)送無法解讀的消息并不會報(bào)錯(cuò)兼贸,因?yàn)樵谶\(yùn)行期還可以繼續(xù)向類添加方法。因此吃溅,編譯器在編譯時(shí)無法確定類中到底會不會有某個(gè)方法實(shí)現(xiàn)溶诞。
當(dāng)對象接受到無法解讀的消息后,就會啟動(dòng)“消息轉(zhuǎn)發(fā)”機(jī)制决侈,程序員可以由此告訴對象如何處理未知的消息螺垢。
大家在開發(fā)期間肯定見過這樣的錯(cuò)誤:
unrecognized selector sent to instance 0x610000026560
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SCUserWorker sss]: unrecognized selector sent to instance 0x610000026560'
錯(cuò)誤原因:是因?yàn)榻邮照邿o法理解sss的這個(gè)方法名,因此致使程序崩潰赖歌。
- 對象在接收到無法解讀的消息后枉圃,會依次調(diào)用下列方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel
在這個(gè)方法中庐冯,參數(shù)就是未知的方法名稱孽亲。在這里你可以解決問題。
- (id)forwardingTargetForSelector:(SEL)aSelector
這個(gè)是備援接收者肄扎,也就是給接收者第二次處理的機(jī)會墨林,如果可以找到備援對象則將其返回,若找不到就返回nil犯祠。 - 完整的消息轉(zhuǎn)發(fā)
- (void)forwardInvocation:(NSInvocation *)anInvocation
啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制旭等,首先要?jiǎng)?chuàng)建 NSInvocation 對象,把尚未處理的消息相關(guān)的細(xì)節(jié)全部封與其中(方法名衡载,目標(biāo)以及參數(shù))搔耕。
使用:只需改變調(diào)用目標(biāo),使消息在新目標(biāo)上得以調(diào)用就好了痰娱,和第二種方法“備援接收者”等效弃榨。
-
案列:使用class_addMethod動(dòng)態(tài)添加方法
假設(shè)我故意一個(gè)類只在.h聲明了方法 沒有在.m中實(shí)現(xiàn)該方法
結(jié)果就會報(bào)上面的錯(cuò),接收者無法解讀消息梨睁。讓你用runtime動(dòng)態(tài)添加方法你會怎么辦呢鲸睛?- 考慮
原因是因?yàn)闆]有實(shí)現(xiàn)該方法,所以無法解讀坡贺,那么我們要為其添加方法官辈。
那么這個(gè)方法添加到哪呢箱舞?該如何添加? - 動(dòng)手
首先找到?jīng)]有實(shí)現(xiàn)方法的那個(gè)類拳亿,在其.m添加
+ (BOOL)resolveInstanceMethod:(SEL)sel
這個(gè)方法晴股。上述講到過,無法解讀消息時(shí)會第一時(shí)間調(diào)用這個(gè)方法肺魁,我們可以在這來解決問題电湘。
接下來要用到runtime中的 class_addMethod 方法
class_addMethod(__unsafe_unretained Class cls, SEL name, IMP imp, const char *types)
1.Class cls : 參數(shù)表示添加新方法的類
2.SEL name : 表示方法名稱
3.IMP imp : 表示由編譯器生成的、指向?qū)崿F(xiàn)方法的指針鹅经。也就是說寂呛,這個(gè)指針指向的方法就是我們要添加的方法。
4.const char **types :最后一個(gè)參數(shù) *types 表示我們要添加的方法的返回值和參數(shù)瞬雹。
主要代碼在下面:
eat:只聲明沒有實(shí)現(xiàn)的方法昧谊。
SCUser:創(chuàng)建的類刽虹。
- 考慮
C語言函數(shù)的實(shí)現(xiàn)
記得導(dǎo)入
#import <objc/runtime.h>
void sayHello (id self,SEL _cmd) {
NSLog(@"Hello");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(eat)) {
class_addMethod([SCUser class], sel, (IMP)sayHello, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
};
- OC形式的實(shí)現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(eat)) {
class_addMethod([SCUser class], sel, class_getMethodImplementation(self, @selector(sayHello)), "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
};
- (void)sayHello {
NSLog(@"hahaha");
}
13酗捌、用“方法調(diào)配技術(shù)”調(diào)試“黑盒方法”
- 在第10條中有介紹過交換方法的案列。
- 交換方法:
- 在運(yùn)行期涌哲,向類中新增或者替換方法胖缤。
- 使用另一個(gè)方法替換一個(gè)方法可以向其添加新功能。
- 只有調(diào)試程序中才會用的運(yùn)行期修改方法阀圾,不宜濫用哪廓。
14、理解“類對象”的用意
- 看下面的代碼
NSString *nameStirng = @"小伙子";
可以理解:nameString 為存放內(nèi)存地址的變量初烘。而NSString自身的數(shù)據(jù)就存在地址中涡真。所有的Objective-C對象都是如此。 - 還可以這樣寫:
id nicknameStirng = @"牛";
對于通用的對象類型id肾筐,因?yàn)槠渥陨硪呀?jīng)是指針了哆料。所以可以這樣寫。 - 比較
兩者語法意義相同吗铐,
唯一區(qū)別:如果聲明的時(shí)候指定了具體類型东亦,那么在該類實(shí)例上調(diào)用沒有的方法時(shí),編譯器會發(fā)出警告信息唬渗。 - id的定義
typedef struct object {
Class isa;
} *id;
說明每個(gè)對象結(jié)構(gòu)體的首個(gè)成員是Class類的變量典阵。通常稱為“isa”指針。
- metaclass
metaclass就是isa指向的一個(gè)結(jié)構(gòu)體镊逝。 - 舉例
用一個(gè)例子說明:
大學(xué)期間壮啊,小明的輔導(dǎo)員要調(diào)查小明家里有沒有黨員。
首先撑蒜,輔導(dǎo)員通過身份證號找到小明的檔案歹啼,發(fā)現(xiàn)小明不是黨員充坑,從小明的檔案中發(fā)現(xiàn)小明父母的身份證號,通過小明父母的身份證號找到小明父母的檔案染突,發(fā)現(xiàn)小明父母都是黨員捻爷。
身份證號 = isa ,檔案 = metaclass份企。
(這是作者自己的粗淺理解也榄,如果不對,歡迎指出) - 檢測繼承體系
- isKindOfClass
isKindOfClass來確定一個(gè)對象是否是一個(gè)類的成員司志,或者是派生自該類的成員甜紫。 - isMemberOfClass
isMemberOfClass只能確定一個(gè)對象是否是當(dāng)前類的成員。
- isKindOfClass
接下來也將會繼續(xù)整理骂远。如果覺得有用請點(diǎn)個(gè)喜歡囚霸!
您的支持將是我繼續(xù)寫作的動(dòng)力!謝謝激才。
觀“編寫高質(zhì)量iOS與OC X代碼的52個(gè)有效方法”有感(一)· 熟悉Objective-C
觀“編寫高質(zhì)量iOS與OC X代碼的52個(gè)有效方法”有感(二)· 對象拓型、消息、運(yùn)行時(shí)
觀“編寫高質(zhì)量iOS與OC X代碼的52個(gè)有效方法”有感(三)· 接口與API設(shè)計(jì)
觀“編寫高質(zhì)量iOS與OC X代碼的52個(gè)有效方法”有感(四)· 協(xié)議與分類