一、分類(類別):
背景:
在大型項目,企業(yè)級開發(fā)中多人同時維護同一個類,此時程序員A因為某項需求只想給當前類currentClass添加一個方法newMethod,那該怎么辦呢?
最簡單粗暴的方式是把newMethod添加到currentClass中,然后直接實現(xiàn)該方法就OK了。
但考慮到OC是單繼承的,子類可以擁有父類的方法和屬性。
如果把newMethod寫到currentClass中,那么currentClass的子類也會擁有newMethod。但真正的需求是只需要currentClass擁有newMethod,而currentClass的子類不會擁有。
蘋果為了解決這個問題,就引入了分類(Category)的概念。
分類運用場景舉例:
舉例:想要收集每個頁面的啟動時間。
問題1:
項目中已經(jīng)有上百個頁面了,如果一個一個的加,浪費時間不說,以后增加了新頁面,還需要添加方法。
解決方法:
我們可以發(fā)現(xiàn)頁面都繼承了UIViewController,想要在每個頁面都執(zhí)行的代碼,可以寫在這些頁面的父類中。我們可以把代碼寫在UIViewController中;出現(xiàn)問題2。
問題2:
UIViewController是官方類,我們只能調(diào)用期接口,并不能修改他的實現(xiàn)。
解決方法:使用分類(category)。
問題3:
本類是系統(tǒng)的類,這里是UIViewController,我們可以使用分類擴展他的方法,也可以重寫他的方法,可是我需要在調(diào)用的地方加頭文件,所有子類都寫頭文件和直接在子類寫方法沒有什么區(qū)別,怎么樣可以使得不寫頭文件,子類就能調(diào)用我們寫的代碼呢?
回答:
我們可以進行方法交換(這樣可以不必在調(diào)用的地方增加頭文件),從而使得在實現(xiàn)的時候調(diào)用重寫的方法。
概念
分類(Category)是OC中的特有語法,它是表示一個指向分類的結構體的指針。原則上它只能增加方法,不能增加成員(實例)變量。
作用
1.可以在不修改原來類的基礎上,為一個類擴展方法。
2最主要的用法:給系統(tǒng)自帶的類擴展方法。
注意:
1.分類中只能添加“方法”,不能增加成員變量,因為分類的結構體指針中,沒有屬性列表,只有方法列表;
如果分類中聲明了一個屬性,那么分類只會生成這個屬性的set、get方法聲明,也就是不會有實現(xiàn)(調(diào)用set方法給該屬性賦值會崩潰,調(diào)用get方法獲取屬性值也會崩潰);
原則上講它只能添加方法,不能添加屬性即成員變量,實際上可以通過其他方式(通過getter,setter手段即運行時進行添加)添加屬性;
2.分類中的可以寫@property, 但不會生成setter/getter方法, 也不會生成實現(xiàn)以及私有的成員變量(編譯時會報警告);
3.分類中可以訪問原來類中的成員變量,但是只能訪問@protect和@public形式的變量。如果想要訪問本類中的私有變量,分類和子類一樣,只能通過方法來訪問;
4.如果分類中有和原有類同名的方法, 會優(yōu)先調(diào)用分類中的方法, 就是說會忽略原有類的方法。所以同名方法調(diào)用的優(yōu)先級為分類 > 本類 > 父類。因此在開發(fā)中盡量不要覆蓋原有類;
5.如果多個分類中都有和原有類中同名的方法, 那么調(diào)用該方法的時候執(zhí)行誰由編譯器決定;編譯器會執(zhí)行最后一個參與編譯的分類中的方法。
1.分類不能添加屬性的實質(zhì)原因:
我們知道在一個類中用@property聲明屬性,編譯器會自動幫我們生成_成員變量和setter/getter,但分類的指針結構體中,根本沒有屬性列表。所以在分類中用@property聲明屬性,既無法生成_成員變量也無法生成setter/getter。 因此結論是:我們可以用@property聲明屬性,編譯和運行都會通過,只要不使用程序也不會崩潰。但如果調(diào)用了_成員變量和setter/getter方法,報錯就在所難免了。
如果調(diào)用了,報錯如下:
//普通聲明,不會自動生成setter/getter,
// ?programmer.nameWithoutSetterGetter = @"無setter/getter"; //調(diào)用setter,編譯成功,運行報錯為:(-[Programmer setNameWithSetterGetter:]: unrecognized selector sent to instance 0x7f9de358fd70')
// ?NSLog(@"%@",programmer.nameWithoutSetterGetter);? ? ? ? ? //調(diào)用getter,編譯成功,運行報錯為-[Programmer setNameWithSetterGetter:]: unrecognized selector sent to instance 0x7fe22be11ea0'
// ?NSLog(@"%@",_nameWithoutSetterGetter);? ? ? ? //這是調(diào)用_成員變量,錯誤提示為:(Use of undeclared identifier'_nameWithoutSetterGetter')
2.報錯的根本原因是使用了系統(tǒng)沒有生成的setter/getter方法,我們可以手動添加setter/getter來避免崩潰,完成調(diào)用;
由于OC是動態(tài)語言,方法真正的實現(xiàn)是通過runtime完成的,雖然系統(tǒng)不給我們生成setter/getter,但我們可以通過runtime手動添加setter/getter方法。
通過運行時手動添加:
#import
static NSString *nameWithSetterGetterKey = @"nameWithSetterGetterKey";//定義一個key值
@implementation Programmer (Category)?
//運行時實現(xiàn)setter方法
- (void)setNameWithSetterGetter:(NSString*)nameWithSetterGetter
?{?
?objc_setAssociatedObject(self, &nameWithSetterGetterKey, nameWithSetterGetter, OBJC_ASSOCIATION_COPY);
}
//運行時實現(xiàn)getter方法
- (NSString*)nameWithSetterGetter
?{
returnobjc_getAssociatedObject(self, &nameWithSetterGetterKey);
}
@end
運行:
//通過runtime實現(xiàn)了setter/getter
programmer.nameWithSetterGetter =@"有setter/getter";//調(diào)用setter,成功NSLog(@"%@",programmer.nameWithSetterGetter);//調(diào)用getter,成功 NSLog(@"%@",_nameWithSetterGetter); //這是調(diào)用_成員變量,錯誤提示為:(Use of undeclared identifier '_nameWithSetterGetter')
但是注意,以上代碼僅僅是手動實現(xiàn)了setter/getter方法,但調(diào)用_成員變量依然報錯。
二、類擴展:
Extension是Category的一個特例。類擴展與分類相比只少了分類的名稱,所以稱之為“匿名分類”。
作用:
能為某個類附加額外的屬性,成員變量,方法聲明;
一般的類擴展寫到.m文件中;
一般的私有屬性寫到類擴展;
三、分類和類擴展的區(qū)別:
1.分類格式:
@interface待擴展的類(分類的名稱)
@end
@implementation待擴展的名稱(分類的名稱)
@end
類擴展格式:
@interfaceXXX()
//私有屬性
//私有方法(如果不實現(xiàn),編譯時會報警,Method definition for 'XXX' not found)
@end
2.分類原則上只能添加“方法”,不能增加成員變量,但是可以通過runtime解決無setter/getter的問題,進而添加屬性;類擴展不僅可以增加方法,還可以增加實例變量(或者屬性),只是該實例變量默認是@private類型的;
3.類擴展中添加的新方法,一定要實現(xiàn),沒被實現(xiàn),編譯器會報警;categorygory中沒有這種限制,類別中的方法沒被實現(xiàn)編譯器是不會有任何警告的。這是因為類擴展是在編譯階段被添加到類中,而類別是在運行時添加到類中。
4.通常來講,分類定義在.h文件中,但也可以定義.m文件中,此時分類的方法就變成私有方法;
類擴展可以定義在.m文件中,這種擴展方式中定義的變量都是私有的,也可以定義在.h文件中,這樣定義的代碼就是共有的,類擴展在.m文件中聲明私有方法是非常好的方式。
5.類擴展不能像類別那樣擁有獨立的實現(xiàn)部分(@implementation部分),也就是說,類擴展所聲明的方法必須依托對應類的實現(xiàn)部分來實現(xiàn)。
四、關于繼承和分類的使用:
以下情況,使用繼承:
1)新擴展的方法與原方法同名,但是還需要使用父類的實現(xiàn)。
2)擴展類的屬性。

以下情況,使用類別:
1)針對系統(tǒng)特定類,例如:NSString,NSArray,NSNumber等。
2)針對自定義類,對于大型而復雜的類,為提高可維護性,把相關的方法分組到多個單獨的文件中。

