代碼部分
為了理解和區(qū)分分類和拓展的區(qū)別治力,我們一共需要5個(gè)文件:
基類:Person.h、Person.m
擴(kuò)展:Person_Extension.h
分類:Person+Category.h勃黍、Person+Category.m
首先我們創(chuàng)建一個(gè)類(Person)
#import <Foundation/Foundation.h>
@interface Person : NSObject
//對(duì)外暴露的公共方法
-(void)eat;
@end
#import "Person.h"
//寄存于.m文件中的擴(kuò)展
@interface Person ()
//在.m文件中的擴(kuò)展會(huì)自動(dòng)為其中的屬性添加getter宵统、setter方法
@property (nonatomic,assign)int happyInt;
//聲明私有方法
-(void)sleep;
@end
@implementation Person
//在.h文件中對(duì)外暴露,公共方法
-(void)eat{
self.happyInt += 10;
NSLog(@"使用技能覆获,吃");
}
//私有方法實(shí)現(xiàn)
-(void)sleep{
self.happyInt -= 10;
NSLog(@"使用技能马澈,睡覺");
}
//方法實(shí)現(xiàn),默認(rèn)是一個(gè)私有方法
-(void)walk{
NSLog(@"使用技能锻梳,行走");
}
@end
創(chuàng)建一個(gè)類擴(kuò)展 Person_Extension.h
代碼如下:
#import "Person.h"
@interface Person ()
//暴露Person中的happyInt箭券,并使之成為 readonly
@property (nonatomic,assign,readonly)int happyInt;
//可調(diào)用,但編譯無法通過疑枯,person中未實(shí)現(xiàn)
//單獨(dú)創(chuàng)建的擴(kuò)展辩块,基類不會(huì)對(duì)其中的屬性實(shí)現(xiàn)其getter或setter方法
//想讓該屬性能使用有兩種方法:
//1.在person中創(chuàng)建同名屬性sadInt;2.在person中手動(dòng)實(shí)現(xiàn)對(duì)應(yīng)getter荆永、setter方法
@property (nonatomic,assign)int sadInt;
//在Person中存在對(duì)應(yīng)私有方法的實(shí)現(xiàn)废亭,可正常調(diào)用
-(void)walk;
//在Person中存在對(duì)應(yīng)私有方法的實(shí)現(xiàn),可正常調(diào)用
-(void)sleep;
//在Person中不存在對(duì)應(yīng)私有方法的實(shí)現(xiàn)具钥,編譯通過豆村,但無法使用
-(void)read;
@end
創(chuàng)建一個(gè)分類Person+Category
Person+Category.h 代碼
#import "Person.h"
@interface Person (Category)
//Category中寫可以重寫同名屬性,但實(shí)際上這中方式并不規(guī)范骂删,不推薦
//Category原則上是不可以添加任何屬性的
//@property (nonatomic,assign,readwrite)int happyInt;
//非要在Category中添加屬性掌动,需要用runtime在.m中實(shí)現(xiàn)getter和setter方法
@property (nonatomic,copy)NSString *lucklyStr;
//實(shí)現(xiàn)額外的方法(基類本身未實(shí)現(xiàn)的方法)
-(void)fly;
@end
Person+Category.m代碼
#import "Person+Category.h"
#import "Person_Extension.h"
#import <objc/runtime.h>
static NSString *lucklyStrKey = @"lucklyStrKey"; //定義一個(gè)key值
@implementation Person (Category)
-(void)fly{
//可以使用 readonly修飾 的 happyInt 的值
if(self.happyInt > 50){
NSLog(@"使用技能,飛行");
}else{
NSLog(@"使用技能宁玫,失敗");
}
}
//runtime手動(dòng)實(shí)現(xiàn)lucklyStr屬性對(duì)應(yīng)的setter方法
- (void)setLucklyStr:(NSString *)lucklyStr{
objc_setAssociatedObject(self, &lucklyStrKey, lucklyStr,OBJC_ASSOCIATION_COPY);
}
//runtime手動(dòng)實(shí)現(xiàn)lucklyStr屬性對(duì)應(yīng)的getter方法
- (NSString *)lucklyStr {
return objc_getAssociatedObject(self, &lucklyStrKey);
}
/*
系統(tǒng)警告:
Category is implementing a method which will also be implemented by its primary class
實(shí)際調(diào)用會(huì)以該方法內(nèi)部實(shí)現(xiàn)為準(zhǔn)
*/
-(void)walk{
NSLog(@"Category粗恢,使用技能,行走");
}
@end
然后在ViewController中使用Person對(duì)象
#include <objc/runtime.h>
#import "ViewController.h"
#import "Person.h"
//不導(dǎo)入無法使用happyInt屬性欧瘪、walk方法眷射、sleep方法
#import "Person_Extension.h"
//不導(dǎo)入無法使用luckStr屬性、fly方法
#import "Person+Category.h"
/*
注:
通過打印Person的屬性和方法可知:
即使不導(dǎo)入Person_Extension和Person+Category
luckStr屬性佛掖、fly方法也會(huì)出現(xiàn)在Peron對(duì)象中妖碉,推測(cè)是編譯后即加入其中了
*/
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
//公共方法
[person eat];
//由于readonly所以只能讀不能寫
NSLog(@"當(dāng)前的開心程度 %d",person.happyInt);
//由Person+Category添加的額外屬性
person.lucklyStr = @"幸運(yùn)數(shù)字:3";
//由Person_Extension暴露,Person負(fù)責(zé)實(shí)現(xiàn)
[person sleep];
//由Person_Extension暴露芥被,Person+Category覆寫
//注:如果你打印Person的方法列表欧宜,會(huì)發(fā)現(xiàn)它有兩個(gè)walk方法
[person walk];
//由Person+Category暴露并實(shí)現(xiàn)
[person fly];
//會(huì)發(fā)生報(bào)錯(cuò)
//person.sadInt = 10;
//會(huì)發(fā)生報(bào)錯(cuò)
//[person read];
/***** 利用動(dòng)態(tài)特性訪問屬性或方法 *****/
//用kvc的方式對(duì)happyInt屬性賦值,成功(KVC在iOS中就是個(gè)BUG)
[person setValue:@100 forKey:@"happyInt"];
//輸出結(jié)果:100
NSLog(@"當(dāng)前的開心程度:%d",person.happyInt);
//用performSelector方法調(diào)用 fly 拴魄,輸出飛行成功
//這種方法甚至不用#import "Person+Category.h"
[person performSelector:NSSelectorFromString(@"fly")];
}
控制臺(tái)輸出結(jié)果
2020-06-09 13:00:32.405 Test2[3886:109041] 使用技能鱼鸠,吃
2020-06-09 13:00:32.405 Test2[3886:109041] 當(dāng)前的開心程度 10
2020-06-09 13:00:32.406 Test2[3886:109041] 使用技能猛拴,睡覺
2020-06-09 13:00:32.406 Test2[3886:109041] Category,使用技能蚀狰,行走
2020-06-09 13:00:32.406 Test2[3886:109041] 當(dāng)前的開心程度:100
2020-06-09 13:00:32.406 Test2[3886:109041] 使用技能,飛行
上述的代碼內(nèi)容演示了iOS中擴(kuò)展和分類的基礎(chǔ)特性和用法职员。
分類和拓展的特性
擴(kuò)展:
1.可以在擴(kuò)展中聲明私有屬性麻蹋。僅可聲明,不可進(jìn)行實(shí)現(xiàn)焊切;(詳見誤區(qū)解析Q2)
2.擴(kuò)展可以把基類(Person)的私有屬性和方法進(jìn)行暴露扮授;
3.擴(kuò)展可以改變基類中私有屬性的修飾符。如能達(dá)到對(duì)外只讀专肪,而對(duì)內(nèi)可修改刹勃。但如果對(duì)內(nèi)是readonly,那么擴(kuò)展中的修飾符就不能是readwhite;(詳見誤區(qū)解析Q3)
分類:
1.分類的意義在于解決一些不能用繼承來處理的問題嚎尤;
2.分類主要作用是增加基類(Person)中沒有的方法荔仁;
3.分類的結(jié)構(gòu)體中沒有屬性列表,只有方法列表芽死。所以分類原則上無法添加屬性乏梁,但通過runtime可以實(shí)現(xiàn)屬性的添加,并會(huì)被編譯器承認(rèn)关贵;
4.分類中有和基類同名的方法, 會(huì)優(yōu)先調(diào)用分類中的方法遇骑。所以同名方法調(diào)用的優(yōu)先級(jí)為 分類 > 本類 > 父類;
5.分類在運(yùn)行時(shí)與基類想合并揖曾;
6.分類中的屬性和方法落萎,均可以被KVC這種動(dòng)態(tài)訪問的方式所訪問;
注:分類(Category)其實(shí)遵循了【裝飾者】設(shè)計(jì)模式炭剪。
分類和類擴(kuò)展的異同
- 一個(gè)類可以擁有多個(gè)不同的擴(kuò)展练链,也可以擁有多個(gè)不同的分類。
- 擴(kuò)展本質(zhì)上是讓私有方法暴露的一個(gè)特殊渠道念祭。
- 擴(kuò)展不能去實(shí)現(xiàn)基類沒有的方法兑宇。(屬性本身也是方法)
- 基類中有的,擴(kuò)展可以使之對(duì)外暴露粱坤;基類中沒有的隶糕,擴(kuò)展無能為力。
- 分類本質(zhì)上是為了解決不能用繼承所解決的問題站玄,可以看做是一中輕量級(jí)的繼承方案枚驻。(繼承還擁有父類所有的公共方法)
- 分類可以實(shí)現(xiàn)基類中沒有的額外方法。
- 基類中有的株旷,分類會(huì)覆寫(覆蓋)它再登;基類中沒有的尔邓,分了可以聲明并實(shí)現(xiàn)它。
誤區(qū)解析:
Q1.是否可認(rèn)為擴(kuò)展(Extension)常常是一個(gè)匿名的分類(Category)锉矢?
A:不可以梯嗽。
擴(kuò)展只有隱藏和顯示基類中私有屬性和方法的作用,擴(kuò)展沒有能力開辟新屬性和方法沽损。
而分類灯节,主要起到為基類增加方法這一作用,如有必要绵估,也可以配合runtime增加屬性炎疆。
Q2.擴(kuò)展是否有創(chuàng)建(聲明+實(shí)現(xiàn))私有屬性的功能?
A:沒有国裳。擴(kuò)展只有聲明私有屬性的功能形入,沒有實(shí)現(xiàn)私有屬性getter、setter方法的功能缝左。
在Person_Extension中的 sadInt 屬性就只有聲明亿遂,沒有實(shí)現(xiàn),所以不管是給其賦值還是想獲取其值都會(huì)發(fā)生編譯錯(cuò)誤盒使。
在Person.m中的擴(kuò)展中聲明 happyInt 會(huì)自動(dòng)進(jìn)行實(shí)現(xiàn)崩掘,是因?yàn)閮牲c(diǎn):一、OC是自上而下的進(jìn)行編譯的少办;二苞慢、.m文件中還有 @implementation關(guān)鍵字,它才是實(shí)現(xiàn)類對(duì)象所有屬性的關(guān)鍵英妓。
Q3.Person中的happyInt如果是readonly修飾符挽放,那么在擴(kuò)展中是否可以用readwrite?
A:不可以蔓纠。如果基類中使用readonly辑畦,那么對(duì)于happyInt屬性,編譯器只會(huì)為其生成getter方法腿倚,而沒有setter方法的實(shí)現(xiàn)纯出。所以即使你在Person_Extension中用 readwrite 修飾符,happyInt依然沒有setter方法敷燎,不能進(jìn)行賦值暂筝。