對(duì)象 --- 在Objective-C等面向?qū)ο笳Z(yǔ)言編程時(shí)肉迫,“對(duì)象”(object)就是“基本構(gòu)造單元”,開發(fā)者可以通過對(duì)象來存儲(chǔ)并傳遞數(shù)據(jù)稿黄。
消息 --- 在對(duì)象之間傳遞數(shù)據(jù)并執(zhí)行任務(wù)的過程就叫做“消息傳遞”(Messaging)
運(yùn)行期 --- 當(dāng)應(yīng)用程序運(yùn)行起來以后喊衫,為其提供相關(guān)支持的代碼叫做“Objective-C運(yùn)行期環(huán)境”(Objectice-C runtime),它提供了一些使得對(duì)象之間能夠傳遞消息的重要函數(shù)杆怕,并且包含創(chuàng)建類實(shí)例所用的全部邏輯族购。
六、理解“屬性”這一概念
屬性(property)是Objective-C的一項(xiàng)特性陵珍,用戶封裝對(duì)象中的數(shù)據(jù)寝杖,iOS開發(fā)中最常用最方便的變量聲明方式,允許我們用點(diǎn)語(yǔ)法來訪問對(duì)象的實(shí)例變量互纯。實(shí)質(zhì)上類似如下
屬性 = 成員變量 + set方法 + get方法瑟幕。
屬性特質(zhì)
@property(nonatomic,readwrite,copy,setter=<name>) NSString *firstName;
原子性
如果屬性具備noatomic特質(zhì),則不使用同步鎖留潦,iOS由于性能原因只盹,都是使用noatomic的,MacOS則多用atomic兔院,使用同步鎖殖卑。讀寫權(quán)限
擁有readwrite特質(zhì)的屬性擁有獲取方法getter和設(shè)置方法setter。
擁有readonly特質(zhì)的屬性僅擁有獲取方法坊萝》趸可以在.h頭文件中對(duì)外公開為只讀屬性,然后再.m的class-continuation分類中將其重新定義為讀寫屬性十偶。-
內(nèi)存管理語(yǔ)義
屬性用于封裝數(shù)據(jù)菩鲜,而數(shù)據(jù)則要有“具體的所有權(quán)語(yǔ)義。內(nèi)存管理語(yǔ)義這個(gè)特質(zhì)僅會(huì)影響設(shè)置方法惦积。assign 設(shè)置方法只會(huì)執(zhí)行針對(duì)純量類型(非OC對(duì)象)的簡(jiǎn)單賦值操作接校,如CGFloat,NSInteger
strong 定義了一種擁有關(guān)系荣刑。為這種屬性設(shè)置新值時(shí)馅笙,設(shè)置方法會(huì)先保留新值,并釋放舊值厉亏,然后再將新值設(shè)置上去董习。對(duì)象引用計(jì)數(shù)+1,功能等價(jià)于MRC里面的retain
weak 非擁有關(guān)系爱只,為這種屬性設(shè)置新值時(shí)皿淋,既不保留新值,也不釋放舊值恬试。然而在屬性值所指對(duì)象遭到摧毀時(shí)窝趣,屬性值也會(huì)被清空為nil。weak是為打破循環(huán)引用而生的
unsafe_unretained 類似weak训柴,但是區(qū)別于在當(dāng)目標(biāo)對(duì)象遭到摧毀時(shí)哑舒,屬性值不會(huì)自動(dòng)清空,這是不安全的幻馁。不建議使用洗鸵!
-
copy 擁有關(guān)系,實(shí)質(zhì)上是設(shè)置為傳入對(duì)象的copy對(duì)象的指針。設(shè)置完后仗嗦,與傳入的對(duì)象無(wú)關(guān)聯(lián)膘滨。
@property (nonatomic, copy) NSString *stringCopy -(void)setStringCopy:(NSString *)stringCopy{ [_stringCopy release]; _stringCopy = [stringCopy copy]; }
當(dāng)屬性類型為NSString *時(shí),經(jīng)常用此特質(zhì)來保護(hù)其封裝性
-
方法名
@property(nonatomic,getter=isOn)Bool on; //getter=<name> 指定獲取方法的方法名 //setter=<name>,這種用法一般不常用稀拐,沒必要
特別注意:
如果想在其他方法里設(shè)置屬性值火邓,那么同樣要遵守屬性定義中所宣稱的語(yǔ)義。如下代碼:
@interface EOCPerson:NSManagedObject
@property(copy,readonly) NSString *firstName;
@property(copy,readonly) NSString *lastName;
-(id)initWithFirstName:(NSString *)firstName lastName:(NSString*)lastName;
@end
//.m文件中
-(id)initWithFirstName:(NSString *)firstName lastName:(NSString*)lastName{
if((self = [super init])){
_firstName = [firstName copy];
_lastName = [lastName copy];
}
}
在實(shí)現(xiàn)這個(gè)自定義初始化方法時(shí)德撬,一定要遵循屬性定義中宣稱的”copy“語(yǔ)義铲咨,因?yàn)閷傩远x就相當(dāng)于類和待設(shè)置的屬性值之間達(dá)成的契約。
七砰逻、在對(duì)象內(nèi)部盡量直接訪問實(shí)例變量
- "A:在對(duì)象內(nèi)部讀取數(shù)據(jù)時(shí)鸣驱,應(yīng)該直接通過實(shí)例變量來讀。B:寫入數(shù)據(jù)時(shí)蝠咆,則應(yīng)該通過屬性來寫踊东。" 這種方案的優(yōu)點(diǎn)是:既能提高讀取操作的速度,又能控制對(duì)屬性的寫入操作刚操,使得相關(guān)屬性的內(nèi)存管理語(yǔ)義得以貫徹闸翅。
- 針對(duì)1中A讀取內(nèi)部數(shù)據(jù)的例外情況是,在使用懶加載初始化技術(shù)配置某個(gè)數(shù)據(jù)的話菊霜,應(yīng)該通過屬性來讀取數(shù)據(jù)
- 針對(duì)1中B坚冀,寫入數(shù)據(jù)的例外情況是,初始化方法和dealloc方法中鉴逞,總是應(yīng)該直接通過實(shí)例變量來讀寫數(shù)據(jù)
八记某、理解"對(duì)象等同性"這一概念
根據(jù)“等同性”(equality)來比較對(duì)象是一個(gè)非常有用的功能司训。按照==操作符比較出來的結(jié)果未必是我們想要的,應(yīng)為該操作比較的是兩個(gè)指針本身液南,而不是其所指的對(duì)象壳猜。 我們應(yīng)該使用NSObject協(xié)議中聲明的"isEqual":方法來判斷兩個(gè)對(duì)象的等同性。
-(BOOL)isEqual:(id)object{
if(self == object)
return YES;
if([self class] != [object class])
return NO;
EOCPerson *otherPerson = (EOCPerson *)object;
if(![_firstName isEqualToString:otherPerson.firstName])
return NO;
if(![_lastName isEqualToString:otherPerson.lastName])
return NO;
if(_age != otherPerson.age)
return NO;
return YES;
}
-(NSUInterger)hash {
NSUInteger firstNameHash = [_firstName hash];
NSUInteger lastNameHash = [_lastName hash];
NSUInteger ageHash = _age;
return firstNameHash ^ lastNameHash ^ ageHash;
}
- 若想檢測(cè)對(duì)象的等同性滑凉,請(qǐng)?zhí)峁癷sEqual:”與hash方法
- 相同的對(duì)象必須具有相同的哈希碼统扳,但是兩個(gè)哈希碼相同的對(duì)象卻未必相同
- 不要盲目地逐個(gè)檢測(cè)每條屬性,而是應(yīng)該依照具體需求來定制檢測(cè)方案畅姊。
- 編寫hash方法時(shí)咒钟,應(yīng)該使用計(jì)算速度快而哈希碼碰撞幾率低的算法
九、以“類族模式”隱藏實(shí)現(xiàn)細(xì)節(jié)
創(chuàng)建如NSArray類族的子類時(shí)若未,需要遵守幾條規(guī)則:
- 子類應(yīng)該繼承自類族中的抽象基類
- 子類應(yīng)該定義自己的數(shù)據(jù)存儲(chǔ)方式
- 子類應(yīng)當(dāng)覆寫超類文檔中指明需要覆寫的方法
要點(diǎn):
- 類族模式可以把實(shí)現(xiàn)細(xì)節(jié)隱藏在一套簡(jiǎn)單的公共接口后面
- 系統(tǒng)框架中經(jīng)常使用類族
- 從類族的公共抽象基類中繼承子類時(shí)要當(dāng)心朱嘴,若有開發(fā)文檔,則應(yīng)首先閱讀
十陨瘩、在既有類中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù)
“關(guān)聯(lián)對(duì)象”(Associated Object)是一個(gè)黑科技腕够,在某些類無(wú)法繼承出子類來存放額外信息時(shí),可以用關(guān)聯(lián)對(duì)象的方法舌劳,來增加鍵值對(duì)帚湘,達(dá)到給既定類存放額外信息的目的。
關(guān)聯(lián)類型 | 等效的@property屬性 |
---|---|
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)
//該方法以給定的鍵和策略為某對(duì)象設(shè)置關(guān)聯(lián)對(duì)象值
id objc_getAssociatedObject(id object, void *key)
//此方法根據(jù)給定的鍵從某對(duì)象中獲取相應(yīng)的關(guān)聯(lián)對(duì)象值
void objc_removeAssociatedObjects(id object)
//此方法移除指定對(duì)象的全部關(guān)聯(lián)對(duì)象
特別注意: 設(shè)置關(guān)聯(lián)對(duì)象時(shí)用的鍵key是個(gè)不透明的指針甚淡。設(shè)置關(guān)聯(lián)對(duì)象值時(shí),通常使用靜態(tài)全局變量做鍵大诸。
staic void *EOCMyALertViewKey = "EOCMyAlertViewKey";
//關(guān)聯(lián)對(duì)象,綁定block
objc_setAssociatedObject(alertView,EOCMyAlertViewKey,block,OBJC_ASSOCIATION_COPY);
//取值贯卦,取出block
void(^block)(NSInteger) = objc_getAssociatedObject(alertView,EOCMyAlertViewKey);
- 可以通過“關(guān)聯(lián)對(duì)象”機(jī)制來把兩個(gè)對(duì)象連起來
- 定義關(guān)聯(lián)對(duì)象時(shí)可以指定內(nèi)存管理語(yǔ)義资柔,用以模仿定義屬性時(shí)所采用的“擁有關(guān)系”與“非擁有關(guān)系”
- 只有在其他做法不可行時(shí)才應(yīng)選用 關(guān)聯(lián)對(duì)象,因?yàn)檫@種做法通常會(huì)引入難以查找的bug撵割,如循環(huán)引用
十一贿堰、理解objc_msgSend的作用
id returnValue = [someObject messageName:parameter];
someObject叫做"接收者"(receiver),messageName叫做“選擇子”(selector),選擇子與參數(shù)合起來稱為“消息”(message)啡彬。 編譯器看到此消息后羹与,將其轉(zhuǎn)換為一條標(biāo)準(zhǔn)的c語(yǔ)言函數(shù)調(diào)用,所調(diào)用的函數(shù)乃是消息傳遞機(jī)制中的核心函數(shù)庶灿,叫做objc_msgSend,其原型如下
void objc_msgSend(id self,SEL cmd, ...)
編譯器會(huì)把上面例子中的消息轉(zhuǎn)換為如下函數(shù):
id returnValue = objc_msgSend(someObject,@selector(messageName:),parameter);
objc_msgSend函數(shù)會(huì)依據(jù)接收者與選擇子的類型來調(diào)用適當(dāng)?shù)姆椒ㄗ莞椋瑸榱送瓿纱瞬僮鳎摲椒ㄐ枰诮邮照咚鶎俚念愔兴褜て洹胺椒斜怼保╨ist of methods)往踢,如果能找到與選擇子名稱相符的方法腾誉,就跳至其實(shí)現(xiàn)代碼。若是找不到,那就沿著繼承體系繼續(xù)向上查找利职,等找到合適的方法再跳轉(zhuǎn)趣效,如果最終還是找不到相符的方法,那就執(zhí)行消息轉(zhuǎn)發(fā)(message forwarding)操作猪贪。
objc_msgSend會(huì)將匹配結(jié)果緩存在“快速映射表”(fast map)里面英支,每個(gè)類都有這樣一塊緩存,若是稍后還向該類發(fā)送與選擇子相同的消息哮伟,那么執(zhí)行就會(huì)很快了。
Objective-C運(yùn)行環(huán)境中的另一些函數(shù)
- objc_msgSend_stret 如果待發(fā)送的消息要返回結(jié)構(gòu)體妄帘,就交由該函數(shù)處理
- objc_msgSend_fpret 如果消息返回的是浮點(diǎn)數(shù)楞黄,則交由此函數(shù)處理
- objc_msgSendSuper 如果要給超類發(fā)消息,例如[super message:parameter],那么就交由次函數(shù)處理
如果某函數(shù)的最后一項(xiàng)操作是調(diào)用另外一個(gè)函數(shù)抡驼,那么就可以運(yùn)用“尾調(diào)用技術(shù)”鬼廓,編譯器會(huì)生成調(diào)轉(zhuǎn)至另一函數(shù)所需的指令碼,而且不會(huì)向調(diào)用堆棧中推入新的棧幁致盟。
要點(diǎn):
- 消息由接收者碎税、選擇子以及參數(shù)構(gòu)成。給某對(duì)象“發(fā)送消息"馏锡,也就相當(dāng)于在該對(duì)象上"調(diào)用方法"
- 發(fā)給某對(duì)象的全部消息都要由"動(dòng)態(tài)消息派發(fā)系統(tǒng)"來處理雷蹂,該系統(tǒng)會(huì)查出對(duì)應(yīng)的方法,并執(zhí)行其代碼
十二杯道、理解消息轉(zhuǎn)發(fā)機(jī)制
當(dāng)對(duì)象接收無(wú)法解讀的消息后匪煌,就會(huì)啟動(dòng)"消息轉(zhuǎn)發(fā)"(message forwarding)機(jī)制,程序員可經(jīng)由此過程告訴對(duì)象應(yīng)該如何處理未知消息党巾。
消息轉(zhuǎn)發(fā)分為兩大階段:
- 第一階段先征詢接收者萎庭,所屬的類,看其是否能 動(dòng)態(tài)添加方法齿拂,以處理當(dāng)前這個(gè)"未知的選擇子"驳规,這叫做"動(dòng)態(tài)方法解析"(dynamic method resolution)
- 第二階段涉及"完整的消息轉(zhuǎn)發(fā)機(jī)制"(full forwarding mechanism),細(xì)分為兩步:首先,請(qǐng)接收者看看有沒有其他對(duì)象能處理這條消息署海,若有吗购,則運(yùn)行期系統(tǒng)會(huì)把消息轉(zhuǎn)個(gè)那個(gè)對(duì)象,消息轉(zhuǎn)發(fā)過程結(jié)束叹侄。其次,若沒有備援的接收者(replacement receiver),則啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制巩搏,運(yùn)行期系統(tǒng)會(huì)把與消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation對(duì)象中,再給接收者最后一次機(jī)會(huì)趾代,令其設(shè)法解決當(dāng)前未處理的這條消息
動(dòng)態(tài)方法解析
對(duì)象在收到無(wú)法解讀的消息后贯底,首先調(diào)用其所屬類的下列方法:
+(BOOL)resolveInstanceMethod:(SEL)selector
該方法的參數(shù)就是那個(gè)未知的選擇子,令其返回值為Boolean類型,<u>表示這個(gè)類是否能新增一個(gè)實(shí)例方法用于處理此選擇子</u>禽捆。在繼續(xù)往下執(zhí)行轉(zhuǎn)發(fā)機(jī)制之前笙什,本類有機(jī)會(huì)新增一個(gè)處理此選擇子的方法。假如未實(shí)現(xiàn)的方法不是實(shí)例方法而是類方法胚想,那么運(yùn)行期系統(tǒng)就會(huì)調(diào)用另一個(gè)方法琐凭,叫做"resolveClassMethod:" 。
<u>使用動(dòng)態(tài)方法解析的前提是:相關(guān)方法的實(shí)現(xiàn)代碼已經(jīng)寫好浊服,只等著運(yùn)行的時(shí)候動(dòng)態(tài)插在類里面就可以了</u>统屈。 此方案常用來實(shí)現(xiàn)@dynamic屬性,比方說牙躺,要訪問CoreData框架中NSManagedObjects對(duì)象的屬性時(shí)就可以這么做愁憔,因?yàn)閷?shí)現(xiàn)這些屬性所需的存取方法在編譯期就能確定。
id autoDictionaryGetter(id self,SEL _cmd);
void autoDictionarySetter(id self,SEL _cmd,id value);
+(BOOL)resolveInstanceMethod:(SEL)selector{
NSString *selectorStirng = NSStringFormSelector(selector);
if(/* selector is from a @dynamic property */){
if([selectorString hasPrefix:@"set"]){
class_addMethod(self,selector,(IMP)autoDictionarySetter,@"v@:@");
}else{
class_addMethod(self,selector,(IMP)autoDictionaryGetter,@"@@:"
}
return YES;
}
return [super resolveInstanceMethod:selector];
}
備援接收者
當(dāng)前接收者還有第二次機(jī)會(huì)能處理未知的選擇子孽拷,在這一步中吨掌,運(yùn)行期系統(tǒng)會(huì)問它:能不能把這條消息轉(zhuǎn)給其他接收者來處理。
-(id)forwardingTargetForSelector:(SEL)selector
方法參數(shù)代表未知的選擇子脓恕,若當(dāng)前接收者能找到備援對(duì)象膜宋,則將其返回,否則就返回nil炼幔。
值得注意的是秋茫,我們無(wú)法操作經(jīng)由這一步所轉(zhuǎn)發(fā)的消息,若是想在發(fā)送給備援接收者之前修改消息內(nèi)容乃秀,那就得通過完整的消息轉(zhuǎn)發(fā)機(jī)制來做了学辱。
完整的消息轉(zhuǎn)發(fā)
如果轉(zhuǎn)發(fā)算法已經(jīng)來到這一步的話,那么唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機(jī)制了环形。
首先創(chuàng)建NSInvocation對(duì)象策泣,把尚未處理的那條消息有關(guān)的全部細(xì)節(jié)都封于其中。此對(duì)象包含選擇子抬吟、目標(biāo)(target)及參數(shù)萨咕。在觸發(fā)NSInvocation對(duì)象時(shí),"消息派發(fā)系統(tǒng)"(message-dispatch system)將親自出馬火本,把消息指派給目標(biāo)對(duì)象危队。
-(void)forwardInvocation:(NSInvocation *)invocation
消息轉(zhuǎn)發(fā)全流程
要點(diǎn)
- 若對(duì)象無(wú)法響應(yīng)某個(gè)選擇子,則進(jìn)入消息轉(zhuǎn)發(fā)流程
- 通過運(yùn)行期的動(dòng)態(tài)方法解析功能钙畔,我們可以在需要用到某個(gè)方法時(shí)再將其加入類中
- 對(duì)象可以把其無(wú)法解讀的某些選擇子轉(zhuǎn)交給其他對(duì)象來處理
- 經(jīng)過上述兩步之后茫陆,如果還是沒有辦法處理選擇子,那就啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制
十三擎析、用"方法調(diào)配技術(shù)"
類的方法列表會(huì)把選擇子的名稱映射到相關(guān)的方法實(shí)現(xiàn)之上簿盅,使得"動(dòng)態(tài)消息派發(fā)系統(tǒng)"能夠據(jù)此找到應(yīng)該調(diào)用的方法.這些方法均以函數(shù)指針的形式來表示,這種指針叫做IMP。其原型如下:
id (*IMP)(id,SEL,...)
可以通過以下c函數(shù)互換兩個(gè)方法實(shí)現(xiàn)
void method_exchangeImplementations(Method m1,Method m2)
//此函數(shù)的兩個(gè)參數(shù)表示待交換的兩個(gè)方法實(shí)現(xiàn)桨醋,而方法實(shí)現(xiàn)則可通過下列函數(shù)獲得
Method class_getInstanceMethod(Class class,SEL aSelector)
//此函數(shù)根據(jù)給定的選擇子從類中取出與之相關(guān)的方法
可以用自定義的類中方法交換某個(gè)目標(biāo)類中方法棚瘟,如下:
@implement NSString (EOCMyAdditions)
-(NSString *)eoc_myLowercaseString{
NSString *lowercase = [self eoc_myLowercaseString];
NSLog(@"%@ => %@",self,lowercase);
return lowercase;
}
@end
Method originalMethod = class_getInstanceMethod([NSString class],@selector(lowercaseString)];
Method swappedMethod = class_getInstanceMethod([NSString class],@selector(eoc_myLowercaseString));
method_exchangeImplementations(originalMethod,swappedMethod);
通過此方案,開發(fā)者可以為那些"完全不知道其具體實(shí)現(xiàn)的"黑盒方法增加日志記錄功能喜最,這非常有助于程序調(diào)試偎蘸,然后此做法只在調(diào)試的時(shí)候有用。若是濫用瞬内,會(huì)令代碼變得不易讀懂且難于維護(hù)迷雪。
要點(diǎn)
- 在運(yùn)行期,可以向類中新增或替換選擇子所對(duì)應(yīng)的方法實(shí)現(xiàn)
- 使用另一份實(shí)現(xiàn)來代替原有的方法實(shí)現(xiàn)虫蝶,這道工序叫做"方法調(diào)配"振乏,開發(fā)者常用此技術(shù)向原有實(shí)現(xiàn)中添加新功能
- 一般來說,只有調(diào)試程序的時(shí)候才需要在運(yùn)行期修改方法實(shí)現(xiàn)秉扑,這種方法不宜濫用
十四、理解"類對(duì)象"的用意
類型信息查詢: "在運(yùn)行期檢視對(duì)象類型"這一操作叫做類型信息查詢(introspection,內(nèi)实飨蕖)舟陆,這個(gè)強(qiáng)大而有用的特性內(nèi)置于Foundation框架的NSObject協(xié)議里,凡是由公共根類(common root class,即NSObject與NSProxy)繼承而來的對(duì)象都要遵從此協(xié)議耻矮。
Class對(duì)象也定義在運(yùn)行期程序庫(kù)的頭文件中:
typedef struct objc_class *Class;
struct objc_class{
Class isa;
Class superClass;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
}
其中super_class 指針確立了繼承關(guān)系秦躯,而isa指針描述了實(shí)例所屬的類。
"isMemberOfClass:"能夠判斷出對(duì)象是否為某個(gè)特定類的實(shí)例裆装;而"isKindOfClass:"則能夠判斷出是否為某類或其派生類的實(shí)例
要點(diǎn):
- 每個(gè)實(shí)例都有一個(gè)指向Class對(duì)象的指針isa踱承,用以表明其類型,而這些Class對(duì)象則構(gòu)成了類的繼承體系
- 如果對(duì)象類型無(wú)法在編譯期確定哨免,那么就應(yīng)該使用類型信息查詢方法來探知
- 盡量使用類型信息查詢方法來確定對(duì)象類型茎活,而不要直接比較類對(duì)象,因?yàn)槟承?duì)象可能實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)功能