Category必知必會

Category(分類或類別)是Objective-C的基礎概念,也是面試必考點之一,我嘗試用簡單的語言解釋一下什麼是Category。面試

一、什麼是Category

利用Category,即便在沒法獲取一個類的源碼的狀況下,咱們也能夠爲一個類增長新的方法、屬性,聽從新的protocol。OC的Category概念與Swift的extension很是相似。bash

1.1 如何建立一個類的Category

假設咱們有一個類: Foo,咱們要爲Foo增長一個Category,Category的名稱叫作bar 。在Xcode中操做New file... -> Objective-C File。在出現的菜單中File Type選擇Category,而後填上分類的名字並選擇一個須要擴展的類就好了。架構

Category一樣有.h和.m文件,文件名稱的格式爲:本類名+Category名,好比上面的操做後,咱們會獲得Foo+bar.hFoo+bar.m兩個文件。app

文件內部是Category的聲明:ide

Foo+bar.h:函數

//Foo+bar.h
#import "Foo.h"
@interface Foo (bar)
@end
複製代碼

Foo+bar.m:優化

//Foo+bar.m
#import "Foo+bar.h"
@implementation Foo (bar)
@end
複製代碼

與類文件同樣,只不過名稱上加了(Category名)ui

1.2 增長屬性

咱們能夠在Category中爲一個類增長屬性,可是不會自動爲這個屬性在這個類裏建立對應的實例變量,也就不會自動建立getter和setterspa

Foo+bar.h:架構設計

//Foo+bar.h
#import "Foo.h"
@interface Foo (bar)
@property NSString *foo;
@end
複製代碼

你會獲得一個編譯錯誤,告訴你去實現一個getter和setter。你實現了getter和setter這個錯誤就會消失。

若是你熟悉Swift的中常見的計算屬性概念,這適用於爲一個類擴展一個計算屬性。這其實又與Swift中extension能夠爲類增長計算屬性是一致的。雖然語言不一樣,概念是一致的。

所謂計算屬性,其實並不存儲值,而是提供一個getter和setter間接操做其餘實例屬性。

好比有一個正方形類Square,它只有一個屬性就是邊長side

@interface Square
@property int side;
@end
複製代碼

咱們爲Square增長一個Category,並增長一個屬性面積size。getter裏將side平方返回,setter裏將size開方後設置給side。

@interface Square (size)
@property int size;
@end
複製代碼
@implementation Square (size)
- (int)size {
    return self.side * self.side;
}
- (void)setSize:(int)size {
    self.side = sqrt(size);
}
@end
複製代碼

1.3 利用關聯對象來實現增長實例變量

雖然Category的屬性在其主類裏沒有建立對應的實例變量,可是咱們能夠利用Objective-C的 關聯對象(Associated Objects) 特性來實現擴展實例變量。

仍是上面Foo+bar的例子,注意#import <objc/runtime.h>

Foo+bar.h:

//Foo+bar.h
#import "Foo.h"
@interface Foo (bar)
@property NSString *foo;
@end
複製代碼

Foo+bar.m:

//Foo+bar.m
#import <objc/runtime.h>
#import "Foo+bar.h"
@implementation Foo (bar)
- (NSString *)bar {
    return objc_getAssociatedObject(self, "SomeUniqueKey");
}

- (void)setBar:(NSString *)bar {
    objc_setAssociatedObject(self, "SomeUniqueKey", bar, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end

複製代碼

這其實算一種trick,你別無選擇的時候才這樣作。若是源碼你能改動,你應該經過修改類的設計來實現你的目標而不是以這種彆扭的方式。

1.4 增長方法

在Category中既能夠增長實例方法,也能夠增長類方法。這並無什麼須要特地強調的。

二、Category的應用場景

2.1 擴展系統或第三方庫

咱們最經常使用Category的場景恐怕就是擴展系統的類了,固然咱們也能夠擴展第三方看不到源碼的類。

2.2 用Category組織龐大的類

有些類隨着開發迭代,愈來愈臃腫。咱們能夠用Category來將這個類分割成更小的部分,方便管理。

咱們其實更應該經過遵照「單一職責」、「高內聚低耦合」等OO設計原則,優化架構設計等手段來保持你的類整潔短小。

固然,在MVC架構模式下,很容易就產生Massive Controller,這時用Category拆分Controller也許是個不錯的選擇。

另外,因爲iOS開發中代理模式的普遍應用,一個類要遵照幾個protocol的狀況比較常見。咱們也能夠嘗試使用Category分離不一樣protocol的實現。

三、Category同名方法衝突

Category方法衝突分兩種狀況,一是Category和本類衝突,二是不一樣Category之間衝突。

這兩種狀況,雖然會獲得編譯警告,但均可以編譯經過。很是容易就忽略。

運行時決議:

所謂運行時決議,是指Category中定義的方法,在運行時才被加入到類的方法列表當中的。

在將Category中的方法添加到類的方法列表當中的時候,並非在原來的方法列表後追加,而是在列表的頭部插入。

因此在合併後的方法列表中,Category中的方法在本類方法的前面。而運行時調用方法,遍歷方法列表,Category中的同名方法首先被找到。雖然本類的方法仍然在方法列表中,可是效果就像是被覆蓋。

而不一樣Category的方法,後編譯的Category先被加入方法列表。因此,不一樣Category的同名方法,後被編譯的Category中的方法「覆蓋」先被編譯Category的同名方法。

咱們能夠經過opensource.apple.com下載runtime的源碼。

source文件夾下->objc-runtime-new.mm文件->static void attachCategories函數

Project Headersobjc文件夾下 -> objc-runtime-new.h文件 -> attachLists方法 中看到完整的Category方法添加到類的方法列表中的過程。

四、分類(Category)與擴展(Extension)的區別

類擴展不只能夠增長方法,還能夠增長實例變量(或者屬性),只是該實例變量默認是私有的。

類擴展中聲明的方法沒被實現,沒法編譯經過,可是分類中的方法沒被實現能夠編譯經過。這是由於類擴展是在編譯階段被添加到類中,分類是在運行時添加到類中。

類擴展不能像類別那樣擁有獨立的實現部分(@implementation部分),也就是說,類擴展所聲明的方法必須依託對應類的實現部分來實現。

咱們一般使用類擴展來隱藏咱們的實現,或者增長私有實例變量。固然,這些私有實例變量仍然能夠經過KVC訪問。

相關文章
相關標籤/搜索