EOC 筆記 二(1)

二、對(duì)象、消息、運(yùn)行期(1)

  • 『對(duì)象 object』是使用面向?qū)ο笳Z(yǔ)言編程時(shí)的『基本構(gòu)造單元』。
  • 開(kāi)發(fā)者可以通過(guò)對(duì)象存儲(chǔ)、傳遞數(shù)據(jù),對(duì)象之間傳遞數(shù)據(jù)并執(zhí)行任務(wù)的過(guò)程成為『消息傳遞 Messaging』。
  • Objective-C Runtime 是在程序運(yùn)行期間提供相關(guān)支持的代碼,它提供了使得對(duì)象之間能夠發(fā)送消息的重要函數(shù),并且包含類(lèi)對(duì)象創(chuàng)建的全部邏輯。

6、理解『屬性』這一概念

  • 『屬性 property』是 OC 用于封裝對(duì)象中的數(shù)據(jù)而提供的一個(gè)特性。OC 對(duì)象通常會(huì)把其需的數(shù)據(jù)保存為各種實(shí)例變量。實(shí)例變量一般通過(guò)『存取方法』來(lái)訪問(wèn)。getter方法用于讀取,setter 方法用于寫(xiě)入。
  • 在 OC 2.0 中引入屬性這一特性,開(kāi)發(fā)者可令編譯器自動(dòng)生成屬性的存取方法。此特性還引入了『點(diǎn)語(yǔ)法』是開(kāi)發(fā)者更容易訪問(wèn)對(duì)象中的數(shù)據(jù)。
  • 類(lèi)似 Java 和C++ 的寫(xiě)法,OC可以這樣定義一個(gè)類(lèi):
@interface EOCPerson : NSObject {
      @public
          NSString *_firstName;
          NSString *_lastName;
      @private
          NSString *_someInternalData;
}
@end;

這種寫(xiě)法的問(wèn)題是:對(duì)象布局在編譯器就已經(jīng)固定了。只要是訪問(wèn)_firstName變量的代碼,編譯器就把其替換為『偏移量 offset』,這個(gè)偏移量是『硬編碼』,表示該變量距離存放對(duì)象的內(nèi)存區(qū)域的起始位置有多遠(yuǎn)。這樣的話(huà),如果在_firstName前又添加一個(gè)變量NSDate *_dateOfBirth;,那么原來(lái)_firstName的偏移量現(xiàn)在會(huì)指向_dateOfBirth,會(huì)讀取到錯(cuò)誤的值。所以這種寫(xiě)法在修改了類(lèi)定義之后必須重新編譯,以確保編譯器計(jì)算正確的偏移量,倘若兩份代碼一份使用舊的類(lèi)定義,一份使用新的,就會(huì)出現(xiàn)不兼容的現(xiàn)象。

  • OC 的解決辦法是,把實(shí)例變量當(dāng)做一種存儲(chǔ)偏移量的特殊變量,交由『類(lèi)對(duì)象』管理,偏移量會(huì)在運(yùn)行期查找,如果類(lèi)定義變了,偏移量也會(huì)變化。這樣不僅可以確保偏移量的正確性,還可以在運(yùn)行期向類(lèi)中添加實(shí)例變量,這就是穩(wěn)固的『應(yīng)用程序二進(jìn)制接口 ABI』。得益于穩(wěn)固的 ABI,我們可以在類(lèi)的 class-continuation 分類(lèi)(即空名的分類(lèi))或?qū)崿F(xiàn)文件中定義實(shí)例變量。
  • 另一個(gè)解決辦法,就是不要直接訪問(wèn)實(shí)例變量,而是通過(guò)存取方法來(lái)訪問(wèn),這時(shí)『屬性』就派上用場(chǎng)了。通過(guò)屬性,可以訪問(wèn)封裝在對(duì)象里的數(shù)據(jù)。所以可以把屬性當(dāng)做是編譯器的一系列操作的簡(jiǎn)稱(chēng),因?yàn)槭褂昧藢傩赃@一特性后,編譯器會(huì)自動(dòng)生成一套存取方法以及對(duì)應(yīng)給定名稱(chēng)的實(shí)例變量。
// 通過(guò)屬性定義類(lèi)的的實(shí)例變量
@interface EOCPerson : NSObject {
@property NSString *firstName;
@property NSString *lastName;
}
@end;
// 對(duì)于使用者來(lái)說(shuō), 以上代碼等同于:
@interface EOCPerson : NSObject {
- (NSString*)firstName;
- (void)setFirstName:(NSString*)firstName;
- (NSString*)lastName;
- (void)setLastName:(NSString*)lastName;
}
@end;
  • 使用屬性的優(yōu)勢(shì):
    • 可以使用點(diǎn)語(yǔ)法訪問(wèn)屬性,和使用存取方法沒(méi)有差別。
    • 編譯器在編譯期會(huì)自動(dòng)編寫(xiě)訪問(wèn)屬性的存取方法,此過(guò)程叫『自動(dòng)合成』。
    • 編譯器在編譯期會(huì)自動(dòng)向類(lèi)中添加適當(dāng)類(lèi)型的實(shí)例變量,實(shí)例變量的名稱(chēng)為屬性名前加下劃線(xiàn)。
    • 我們可以使用@synthesize語(yǔ)法來(lái)自己指定實(shí)例變量的名稱(chēng)。使用@dynamic語(yǔ)法指定屬性不自動(dòng)生成存取方法和實(shí)例變量,即使編譯期時(shí)編譯器沒(méi)有發(fā)現(xiàn)存取方法也不會(huì)報(bào)錯(cuò),因?yàn)樗J(rèn)為這些方法在運(yùn)行期能找到。
  • 屬性特質(zhì):屬性的不同特質(zhì)會(huì)影響編譯器所生成的存取方法。屬性的特質(zhì)可以分四類(lèi):
    • 原子性:默認(rèn)情況下,由編譯器所生成的存取方法會(huì)通過(guò)添加同步鎖的機(jī)制確保屬性的原子性 atomic(在多線(xiàn)程中同一時(shí)間只能有一個(gè)線(xiàn)程執(zhí)行屬性的存取操作)。 如果屬性具備nonatomic,則不使用同步鎖。
    • 讀/寫(xiě)權(quán)限:
      • readwrite 屬性擁有settergetter方法;
      • readonly 屬性只有getter??梢允褂么颂刭|(zhì)把某個(gè)屬性對(duì)外公開(kāi)為只讀屬性,然后在 class-continuation分類(lèi)中將其重新定義為讀寫(xiě)屬性。
    • 內(nèi)存管理語(yǔ)義: 屬性用于封裝數(shù)據(jù),而數(shù)據(jù)應(yīng)具有『具體所有權(quán)語(yǔ)義』。下面一組特質(zhì)只會(huì)影響setter 方法。如果是自己編寫(xiě)的setter方法,則必須與屬性具備的特質(zhì)相符。
      • assign setter方法只會(huì)執(zhí)行針對(duì)『純量類(lèi)型』(scalar type 如 CGFloat, NSInteger 等基本數(shù)據(jù)類(lèi)型)的簡(jiǎn)單賦值操作。
      • strong 表明該屬性定義了一種『擁有關(guān)系』,為該屬性設(shè)置新值時(shí),setter方法會(huì)先保留新值,并釋放舊值,然后將新值設(shè)置上去。
      • weak 表明該屬性定義了一種『非擁有關(guān)系』,為該屬性設(shè)置新值時(shí),setter方法既不會(huì)保留新值,也不會(huì)釋放舊值,和 assign 類(lèi)似,只是簡(jiǎn)單的賦值。但是在該屬性所指對(duì)象被銷(xiāo)毀時(shí),屬性值也會(huì)被清空。
      • unsafe_unretained 和 assign 類(lèi)似,但是它適用于『對(duì)象類(lèi)型』,和 weak 類(lèi)似,也是定義一種『非擁有關(guān)系(unretained)』,但是當(dāng)目標(biāo)對(duì)象被銷(xiāo)毀時(shí),屬性值不會(huì)自動(dòng)清空(unsafe)。
      • copy 和 strong 類(lèi)似,但是setter方法不會(huì)保留新值,而是將其拷貝。多用在 NSString* 類(lèi)型,來(lái)保護(hù)其封裝性。因?yàn)閭鹘osetter方法的新值可能一個(gè) NSMutableString 類(lèi)型的可變字符串,其值可能會(huì)被修改。所以拷貝一份不可變的字符串,可以確保屬性對(duì)象的值不會(huì)遭人修改。一般來(lái)說(shuō),只要屬性所用的對(duì)象是可變的,就應(yīng)該在setter方法中對(duì)其進(jìn)行拷貝。
    • 方法名: 可以指定存取方法的方法名
      • getter=<name> 如 UISwitch 類(lèi)的的 on 屬性
@property (nonatomic, getter=isOn) BOOL on;
// 使用時(shí)
switch.isOn;
  - **setter=<name>** 不常見(jiàn)。
  • 在我們?yōu)轭?lèi)自定義了一個(gè)初始化方法時(shí),也要遵循屬性的特質(zhì)來(lái)為其賦值,如我們?yōu)轭?lèi)定義了一個(gè) 具有copy 特質(zhì)字符串,又定義了一個(gè)用該字符串初始化一個(gè)類(lèi)的實(shí)例的方法。
@property (copy) NSString *name;
- (id)initWithName:(NSString *)name;

初始化方法的實(shí)現(xiàn)可以這樣寫(xiě):

- (id)initWithName:(NSString *)name
{
        if(self = [super init]) {
            _name = [name copy];
        }
        return self;
}

為什么這里不直接調(diào)用setter方法呢?第7條會(huì)做詳細(xì)解釋。

  • 如果使用了readonly,編譯器只會(huì)創(chuàng)建 getter,即便如此,還是要寫(xiě)上內(nèi)存管理語(yǔ)義,以表明在初始化方法中設(shè)置這些屬性所用的方式。否則使用者可能會(huì)在初始化之后自行拷貝,這種操作是多余而且低效的。
  • 使用atomic 的好處是,如果有兩個(gè)線(xiàn)程讀寫(xiě)同一屬性,那么不論何時(shí),各個(gè)線(xiàn)程中總能得到有效的屬性值。如果使用nonaotmic,其中一個(gè)線(xiàn)程讀寫(xiě)時(shí),另一個(gè)線(xiàn)程突然闖入讀取或修改了尚未修改好的值,最后得到的值可能會(huì)不對(duì)。但是為什么通常開(kāi)發(fā)中都是用nonatomic,原因是,在 iOS 中使用同步鎖的開(kāi)銷(xiāo)較大,會(huì)帶來(lái)性能問(wèn)題。此外,就算使用了atomic也不一定能保證線(xiàn)程安全。例如,一個(gè)線(xiàn)程連續(xù)多次讀取某個(gè)屬性的值的過(guò)程中,有別的線(xiàn)程在同時(shí)改寫(xiě)該值,那么最后得到的值還是會(huì)可能出錯(cuò)。要保證線(xiàn)程安全,需要采用更深層的鎖定機(jī)制。不過(guò)在 macOS 中,使用atomic不會(huì)有性能問(wèn)題。

7、在對(duì)象內(nèi)部盡量直接訪問(wèn)實(shí)例變量

  • 直接訪問(wèn)和通過(guò)屬性訪問(wèn)的區(qū)別是:
    • 直接訪問(wèn)不經(jīng)過(guò) OC 的『方法派發(fā)』(見(jiàn)11條),速度會(huì)比較快,相當(dāng)于直接訪問(wèn)保存對(duì)象實(shí)例變量的內(nèi)存。
    • 直接訪問(wèn)不會(huì)調(diào)用setter方法,這會(huì)繞過(guò)為屬性定義的『內(nèi)存管理語(yǔ)義』,如在 ARC 下直接訪問(wèn)一個(gè) copy 屬性,并不會(huì)拷貝該屬性。
    • 直接訪問(wèn)不會(huì)觸發(fā)『鍵值觀察 KVO』通知。
    • 通過(guò)屬性訪問(wèn)可以給存取方法設(shè)置斷點(diǎn),這有助于排插錯(cuò)誤。
  • 作者建議在讀取實(shí)例變量時(shí)直接訪問(wèn),而在設(shè)置時(shí)通過(guò)屬性來(lái)做,以盡可能提高效率。這種做法需要注意的問(wèn)題:
    • 在初始化方法中應(yīng)該總是采用直接訪問(wèn),因?yàn)樽宇?lèi)可能會(huì)重寫(xiě)setter方法。但是,如果待初始化的實(shí)例變量聲明在超類(lèi)中,我們無(wú)法在子類(lèi)中直接訪問(wèn)此實(shí)例變量,就只能調(diào)用setter方法來(lái)初始化。
    • 如果某個(gè)屬性采用了懶加載,則必須通過(guò)存取方法訪問(wèn)屬性。否則,實(shí)例變量永遠(yuǎn)不會(huì)被初始化。

8、理解『對(duì)象等同性』這一概念

  • 使用 『==』操作符比較的是兩個(gè)指針本身是否相同,或者說(shuō)內(nèi)存地址是否相同。
  • 比較兩個(gè)對(duì)象的同等性應(yīng)當(dāng)使用 NSObject 協(xié)議中聲明的 isEqual:方法。
  • 有的類(lèi)提供了專(zhuān)門(mén)的比較方法,如 NSString 的isEqualToString:方法,應(yīng)當(dāng)優(yōu)先使用,這比調(diào)用isEqual方法快,因?yàn)?code>isEqual需要執(zhí)行額外的步驟來(lái)判斷比較對(duì)象的類(lèi)型。
  • NSObject 協(xié)議中用于判斷等同性的關(guān)鍵方法:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;

這兩個(gè)方法的默認(rèn)實(shí)現(xiàn)是:當(dāng)且僅當(dāng)其『指針值(或內(nèi)存地址)』完全相等時(shí),這兩個(gè)對(duì)象才相等。若要在自定義對(duì)象中重寫(xiě)這兩個(gè)方法,則必須保證:如果isEqual方法判斷兩個(gè)對(duì)象相等,則 hash方法也必須返回相同的值。但是如果hash返回相同的值,那么isEqual方法未必認(rèn)為兩個(gè)對(duì)象相等。一種較好的計(jì)算 hash 值的方式:

// 以前面提到的 EOCPerson 類(lèi)為例:
- (NSUInteger)hash {
        NSUInteger firstNameHash = [_firstName hash];
        NSUInteger lastNameHash = [_lastName hash];
        NSUInteger ageHash = _age;
        return firstNameHash ^ lastNameHash ^ ageHash;
}
  • hash方法返回的值對(duì) Set 集合的性能有很大的影響,因?yàn)?Set 在檢索哈希表時(shí),會(huì)用對(duì)象的哈希值做索引。Set 可能會(huì)根據(jù)哈希值把對(duì)象分裝到不同的數(shù)組,想 Set 中添加對(duì)象時(shí),要根據(jù)其哈希值找到對(duì)應(yīng)的數(shù)組,依次檢查其中的各個(gè)元素,判斷是否和新對(duì)象相等。所以,如果每個(gè)對(duì)象返回的 hash 值相同,那么 Set 需要將所有對(duì)象掃描一邊,會(huì)影響性能。
  • 特定的類(lèi)有特定的等同性判斷方法,如系統(tǒng)為 NSString、NSArray、NSDictionary 等提供了特別的判斷方法,但由于 OC 在編譯期不做強(qiáng)類(lèi)型檢查,傳入這些方法的參數(shù)類(lèi)型需要保證正確,否則會(huì)拋出異常??梢詾樽远x類(lèi)編寫(xiě)類(lèi)似的方法,并一并復(fù)寫(xiě)isEqual:方法,后者常見(jiàn)的實(shí)現(xiàn)方法是如果傳入的參數(shù)類(lèi)型正確就使用自己編寫(xiě)的判斷方法,否則交由超類(lèi)比較。
  • 等同性的執(zhí)行深度:在我們?yōu)樽远x類(lèi)實(shí)現(xiàn)判斷方法時(shí),可以根據(jù)不同需求針對(duì)部分或全部的字段做比較。如從數(shù)據(jù)庫(kù)讀取的數(shù)據(jù)可以只比較主鍵 id 是否相同。
  • 容器中如果存在可變類(lèi),可能會(huì)影響容器等同性的結(jié)果。例如,在 Set 里添加一個(gè)可變數(shù)組,再添加一個(gè)與之不同的可變數(shù)組,接著修改第二個(gè)可變數(shù)組同第一個(gè)相等,此時(shí) Set 里依然有兩個(gè)完全相同的數(shù)組,這與 Set 的語(yǔ)義相違背。并且,如果此時(shí)復(fù)制一份該 Set,得到的結(jié)果是只有一個(gè)數(shù)組的 Set。所以,要么保證添加到 Set 里的對(duì)象的 hash 值不是根據(jù)可變字段計(jì)算的,要么就保證這個(gè)對(duì)象是不可變的。

9、以『類(lèi)族模式』隱藏實(shí)現(xiàn)細(xì)節(jié)

  • 使用『類(lèi)族』模式,可以隱藏『抽象基類(lèi)』的實(shí)現(xiàn)細(xì)節(jié)。OC 的系統(tǒng)框架中普遍使用了這種模式,如 UIButton,其提供的工廠方法buttonWithType:可以根據(jù)傳入的參數(shù)生成不同類(lèi)型的 button,但是這寫(xiě)不同類(lèi)型的 button 都繼承自基類(lèi) UIButton。
  • 由工廠方法返回的實(shí)例對(duì)象并不是一定是基類(lèi)的實(shí)例對(duì)象,所以[button isMemberOfClass:[UIButton class]];返回的是 NO。
  • 除了 UIButton,大部分的集合類(lèi)都是類(lèi)族,通過(guò) class 返回的類(lèi)型便可知道,如一個(gè) NSArray 對(duì)象執(zhí)行[array class];返回的類(lèi)可能是__NSArrayI、__NSArrayM、__NSArray0等,絕不可能返回 NSArray 類(lèi)本身。所以使用如下的代碼是錯(cuò)誤的。
if ([maybeAnArray class] == [NSArray class]) {
      // do someting
}

應(yīng)當(dāng)使用類(lèi)型信息查詢(xún)方法

if ([maybeAnArray isKindOfClass:[NSArray class]]) {
      // do someting
}
  • 編寫(xiě)類(lèi)族的子類(lèi)時(shí),有以下幾條規(guī)則:
    • 子類(lèi)應(yīng)該繼承自類(lèi)族中的抽象基類(lèi)。
    • 子類(lèi)應(yīng)該定義自己的數(shù)據(jù)存儲(chǔ)方式。因?yàn)橄?NSArry 本身只不過(guò)是包在其他隱藏對(duì)象外面的殼,僅僅定義了所有數(shù)據(jù)都需要具備的一些接口,并沒(méi)有定義數(shù)據(jù)的存儲(chǔ)方式。對(duì)于自定義的數(shù)組來(lái)說(shuō),可以用 NSArray 來(lái)保存它的實(shí)例。
    • 子類(lèi)應(yīng)當(dāng)覆寫(xiě)超類(lèi)文檔中明確指明需要覆寫(xiě)的方法。如 NSArray 的子類(lèi)必須覆寫(xiě)- count- objectAtIndex:方法。

10、在既有類(lèi)中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù)

  • 同過(guò)使用『關(guān)聯(lián)對(duì)象』的特性,可以在某個(gè)對(duì)象中存儲(chǔ)一些額外的數(shù)據(jù)。可以給某個(gè)對(duì)象關(guān)聯(lián)多個(gè)其他對(duì)象,這些對(duì)象通過(guò)『鍵』來(lái)區(qū)分。關(guān)聯(lián)時(shí)可以指明對(duì)象的內(nèi)存管理語(yǔ)義。
關(guān)聯(lián)類(lèi)型 等效@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ì)象:

// 設(shè)置對(duì)象value為object 的鍵為*key, 存儲(chǔ)策略為 policy 的關(guān)聯(lián)對(duì)象
- void objc_setAssociatedObject (id object, void *key, id value, objc_AssociatedPolicy policy) 
// 獲取object對(duì)象中的鍵為 *key 的關(guān)聯(lián)對(duì)象值.
- id objc_getAssociatedObject (id object, void *key) 
// 移除 object 的所有關(guān)聯(lián)對(duì)象
- void objc_removeAssociatedObject(id object)
  • 只有在其他辦法不可行時(shí)才去使用關(guān)聯(lián)對(duì)象,因?yàn)槿绻麨E用會(huì)令代碼失控,難于調(diào)試。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 蘋(píng)果官方文檔翻譯 《Objective-C語(yǔ)言編程》(Programming with Objective-C) ...
    fever105閱讀 26,324評(píng)論 19 129
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,569評(píng)論 19 139
  • 今天是怎樣的一個(gè)感覺(jué)? 感受到自己很差很差,但是很開(kāi)心。 看到了自己的不足,那么我將有目標(biāo)去前進(jìn)。 今天老大找我聊...
    熊芳菲閱讀 470評(píng)論 0 0
  • 四天的長(zhǎng)假對(duì)我而言好像就是瘋狂一天,休息一天的節(jié)奏,只因昨日和閨蜜聊天又聚一起小嗨了一下,頭暈了一天,如果問(wèn)我為什...
    飄在溫哥華閱讀 218評(píng)論 0 0
  • “嗨。”她也輕聲回了一句,嘴角顫抖著露出了微笑,“原來(lái)你在這里?!? “我在這里?!?/div>
    TRACYSTORYBOOK閱讀 324評(píng)論 0 0

友情鏈接更多精彩內(nèi)容