在 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等。
- 針對自定義類,對於大型而複雜的類,為提高可維護性,把相關的方法分組到多個單獨的檔案中。