iOS分類(category)、類擴充套件(extension)、繼承的區別

yangcg_掘金發表於2018-08-08

在 iOS 開發中,或多或少接觸過這三個東西,但是以前只是知道使用它們,但是它們之間有什麼區別或每個有什麼不可替代的功能,今天來總結一波。

分類(category)

iOS在2.0就已經推出分類(Category),它允許開發者在不改動原有類的情況下,對該類進行擴充套件使用。分類(Category)是OC中的特有語法,它是表示一個指向分類的結構體的指標。原則上它只能增加方法,不能增加成員(例項)變數,具體原因看原始碼:


Category
Category 是表示一個指向分類的結構體的指標,其定義如下:
typedef struct objc_category *Category;
struct objc_category {
  char *category_name                          OBJC2_UNAVAILABLE; // 分類名
  char *class_name                             OBJC2_UNAVAILABLE; // 分類所屬的類名
  struct objc_method_list *instance_methods    OBJC2_UNAVAILABLE; // 例項方法列表
  struct objc_method_list *class_methods       OBJC2_UNAVAILABLE; // 類方法列表
  struct objc_protocol_list *protocols         OBJC2_UNAVAILABLE; // 分類所實現的協議列表
}

複製程式碼

從中我們可以看出,這個結構體主要包含了分類定義的例項方法與類方法,其中instance_methods列表是objc_class中方法列表的一個子集,而class_methods列表是元類方法列表的一個子集。但這個結構體裡面根本沒有屬性列表

要點:

  • 分類是用於給原有類新增方法的,因為分類的結構體指標中,沒有屬性列表,只有方法列表。所以< 原則上講它只能新增方法, 不能新增屬性(成員變數),實際上可以通過其它方式新增屬性>。
  • 分類中的可以寫@property, 但不會生成setter/getter方法, 也不會生成實現以及私有的成員變數(編譯時會報警告)。
  • 可以在分類中訪問原有類中.h中的屬性。
  • 如果分類中有和原有類同名的方法, 會優先呼叫分類中的方法, 就是說會忽略原有類的方法。所以同名方法呼叫的優先順序為 `分類 > 本類 > 父類。因此在開發中儘量不要覆蓋原有類。
  • 如果多個分類中都有和原有類中同名的方法, 那麼呼叫該方法的時候執行誰由編譯器決定;編譯器會執行最後一個參與編譯的分類中的方法。

分類格式:

// .h檔案
@interface 待擴充套件的類(分類的名稱)
@end

// .m檔案
@implementation 待擴充套件的名稱(分類的名稱)
@end
複製程式碼

具體實現:

// UIView+Category.h檔案中
@interface UIView (Category)

@property(nonatomic,copy) NSString *viewName;   //不設定setter/getter方法的屬性(注意是可以寫在這,而且編譯只會報警告,執行不報錯)

- (void)setBackground;    //分類方法

@end


// UIView +Category.m檔案中
@ implementation UIView (Category)

//分類方法的實現
- (void)setBackground {
	self.backgroundColor = [UIColor redColor];
}

@end

複製程式碼

那麼問題來了:

  • 為什麼在分類中宣告屬性時,執行不會出錯呢?
  • 既然分類不讓新增屬性,那為什麼我寫了@property仍然還以編譯通過呢?

現在我們來看一下分類不能新增屬性的實質原因:

  • 我們知道在一個類中用@property宣告屬性,編譯器會自動幫我們生成_成員變數setter/getter,但分類的指標結構體中,根本沒有屬性列表。所以在分類中用@property宣告屬性,既無法生成_成員變數也無法生成setter/getter
  • 因此結論是:我們可以用@property宣告屬性,編譯和執行都會通過,只要不使用程式也不會崩潰。但如果呼叫了_成員變數setter/getter方法,報錯就在所難免了。

進行思考:

  • 既然報錯的根本原因是使用了系統沒有生成的setter/getter方法,可不可以在手動新增setter/getter來避免崩潰,完成呼叫呢?
  • 其實是可以的。由於OC是動態語言,方法真正的實現是通過runtime完成的,雖然系統不給我們生成setter/getter,但我們可以通過runtime手動新增setter/getter方法。

實現方式:

// UIView+Category.h檔案中
@interface UIView (Category)

@property(nonatomic,copy) NSString *viewName; 

@end


// UIView +Category.m檔案中
#import "UIView + Category.h"
#import <objc/runtime.h>

static NSString *viewNameKey = @"viewNameKey";   //定義一個key值

@ implementation UIView (Category)

//執行時實現getter方法
- (NSString *)viewName {
    //如果屬性值是非id型別,可以通過屬性值先構造OC的id物件,再通過物件獲取非id型別屬性
    return objc_getAssociatedObject(self, viewNameKey);
}
 
//執行時實現setter方法
- (void)setViewName:(NSString *)viewName{
    //如果屬性值是非id型別,可以通過屬性值先構造OC的id物件,再通過物件獲取非id型別屬性
    objc_setAssociatedObject(self, viewNameKey, viewName, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 


@end
複製程式碼

實際呼叫:

UIView *myView = [UIView new];
myView.viewName = @"新增屬性成功";

NSLog(@"%@", myView.viewName);
複製程式碼

注意:

  • 以上程式碼僅僅是手動實現了setter/getter方法,但呼叫_成員變數依然報錯。

類擴充套件(extension)

Extension是Category的一個特例。類擴充套件與分類相比只少了分類的名稱,所以稱之為“匿名分類”。 其實開發當中,我們幾乎天天在使用。對於有些人來說像是最熟悉的陌生人。在我們平常的 .m 檔案中的 @interface XXX() 到 @end 這部分就屬於這個類的擴充套件。

類擴充套件格式:


@interface XXX ()
//私有屬性
//私有方法(如果不實現,編譯時會報警告,Method definition for 'XXX' not found)
@end

複製程式碼

說明:

  • 為一個類新增額外的原來沒有變數,方法和屬性
  • 一般的類擴充套件寫到.m檔案中
  • 一般的私有屬性寫到.m檔案中的類擴充套件中

分類與類擴充套件的區別

  • 分類中原則上只能增加方法(能新增屬性的的原因只是通過runtime解決無setter/getter的問題而已);
  • 類擴充套件不僅可以增加方法,還可以增加例項變數(或者屬性),只是該例項變數預設是@private型別的( 作用範圍只能在自身類,而不是子類或其他地方);
  • 類擴充套件中宣告的方法沒被實現,編譯器會報警告,但是分類中的方法沒被實現編譯器是不會有任何警告的。這是因為類擴充套件是在編譯階段被新增到類中,而分類是在執行時新增到類中。
  • 類擴充套件不能像分類那樣擁有獨立的實現部分(@implementation部分),也就是說,類擴充套件所宣告的方法必須依託對應類的實現部分來實現。
  • 定義在 .m 檔案中的類擴充套件方法為私有的,定義在 .h 檔案(標頭檔案)中的類擴充套件方法為公有的。類擴充套件是在 .m 檔案中宣告私有方法的非常好的方式。

繼承

對於繼承,應該都是很熟悉了,網上詳細的資料也是非常多,在這裡就不再贅述,主要介紹它與分類的區別。

分類與繼承的區別

以下情況使用繼承:
  • 新擴充套件的方法與原方法同名,但是還需要使用父類的實現。
  • 擴充套件類的屬性。
以下情況使用分類:
  • 針對系統特定類,例如:NSString,NSArray,NSNumber等。
  • 針對自定義類,對於大型而複雜的類,為提高可維護性,把相關的方法分組到多個單獨的檔案中。

參考了這裡這裡

相關文章