這是《objc與鴨子對象》的上半部分盼砍,《objc與鴨子對象(下)》中介紹了鴨子類型的進階用法次乓、依賴注入以及demo胜蛉。
我是前言
鴨子類型(Duck Type)即:“當看到一只鳥走起來像鴨子、游泳起來像鴨子扳碍、叫起來也像鴨子本今,那么這只鳥就可以被稱為鴨子”拆座,換成程序猿語言就是:“當調(diào)用者知道這個對象能調(diào)用什么方法時,管它這個對象到底是什么類的實例呢”诈泼。本文對objc中的鴨子類型對象進行簡單探究懂拾,并用一個“只用一個類實現(xiàn)Json Entity”的小demo實踐下這個思路的魔力煤禽。進階篇請看下半部分铐达。
objc與鴨子類型
id類型是個大鴨子
鴨子類型是動態(tài)語言的特性,編譯時并不決定函數(shù)調(diào)用關系檬果,說白了所有的類型聲明都是給編譯器看的瓮孙。objc在動態(tài)和靜態(tài)方面找到了不錯的平衡,既保留了嚴格的靜態(tài)檢查也沒破壞運行時的動態(tài)特性选脊。
我們知道杭抠,向一個objc對象(或Class)發(fā)消息,實際上就是沿著它的isa指針尋找真正函數(shù)地址恳啥,所以只要一個對象滿足下面的結構偏灿,就可以對它發(fā)送消息:
Objective-C
1
2
3structobjc_object{
Classisa;
}*id;
也就是熟知的id類型,objc在語言層面先天就支持了這個基本的鴨子類型钝的,我們可以將任意一個對象強轉為id類型從而向它發(fā)送消息翁垂,就算它并不能響應這個消息,編譯器也無從知曉硝桩。
正如這篇文章中對objc對象的簡短定義:The best definition for a Smalltalk or Objective-C "object" is "something that can respond to messages.object并非一定是某個特定類型的實例沿猜,只要它能響應需要的消息就可以了。
從@interface到@protocol
正如objc先天支持的動態(tài)的id類型碗脊,@protocol為鴨子類型提供了編譯時的強類型檢查啼肩,實現(xiàn)了Cocoa中經(jīng)典的鴨子類型使用場景:
Objective-C
1
2@property(nonatomic,assign)idUITableViewDataSource>dataSource;
@property(nonatomic,assign)idUITableViewDelegate>delegate;
利用鴨子類型設計的接口會給使用者更大的靈活度。同時@protocol可以用來建立偽繼承關系
Objective-C
1
2@protocolUIScrollViewDelegateNSObject>
@protocolUITableViewDelegateNSObject,UIScrollViewDelegate>
協(xié)議的存在一方面是給NSProxy這樣的其他根類使用衙伶,同時也給了鴨子協(xié)議類型一個根類型祈坠,正如給了大部分類一個NSObject根類一樣。說個小插曲矢劲,由于objc中Class也是id類型赦拘,形如id的鴨子類型是可以用Class對象來扮演的,只需要把實例方法替換成類方法卧须,如:
Objective-C
1
2
3
4
5
6
7
8@implementationDataSource
+(NSInteger)numberOfSectionsInTableView:(UITableView*)tableView{
return0;
}
+(NSInteger)tableView:(UITableView*)tableViewnumberOfRowsInSection:(NSInteger)section{
return0;
}
@end
設置table view的data source:
Objective-C
1
self.tableView.dataSource=(ClassUITableViewDataSource>)[DataSourceclass];
這種非主流寫法合法且運行正常另绩,歸功于objc中加號和減號方法在@selector中并未體現(xiàn)儒陨,在@protocol中也是形同虛設,這種代碼我相信沒人真的寫笋籽,但確實能體現(xiàn)鴨子類型的靈活性蹦漠。
[Demo]一個類實現(xiàn)Json Entity
Entity對象表示某個純數(shù)據(jù)的結構,如:
Objective-C
1
2
3
4
5
6@interfaceXXUserEntity: NSObject
@property(nonatomic,copy)NSString*name;
@property(nonatomic,copy)NSString*sex;
@property(nonatomic,assign)NSIntegerage;
// balabala....
@end
實際開發(fā)中這種類往往對應著server端返回的一個JSON串车海,如:
Objective-C
1
{"name":"sunnyxx","sex":"boy","age":24,...}
解析這些映射是個純重復工作笛园,建類、寫屬性侍芝、解析…如今已經(jīng)有JSONModel研铆,Mantle等不錯的框架幫忙。這個demo我們要用鴨子類型的思想去重新設計州叠,把這些Entity類簡化成一個鴨子類棵红。
由于上面的UserEntity類,只有屬性的getter和setter咧栗,這正對應了NSMutableDictionary的objectForKey:和setObjectForKey:逆甜,同時,JSON數(shù)據(jù)也會解析成字典致板,這就完成了巧妙的對接交煞,下面去實現(xiàn)這個類。
真正干活的是一個字典斟或,保證封裝性和純粹性素征,這個類直接使用NSProxy作為純代理類,只暴露一個初始化方法就好了:
Objective-C
1
2
3
4
5
6
7
8// XXDuckEntity.h
@interfaceXXDuckEntity: NSProxy
-(instancetype)initWithJSONString:(NSString*)json;
@end
// XXDuckEntity.m
@interfaceXXDuckEntity()
@property(nonatomic,strong)NSMutableDictionary*innerDictionary;
@end
NSProxy默認是沒有初始化方法的萝挤,也省去了去規(guī)避其他初始化方法的麻煩御毅,為了簡單直接初始化時就把json串解開成字典(暫不考慮json是個array):
Objective-C
1
2
3
4
5
6
7
8
9-(instancetype)initWithJSONString:(NSString*)json
{
NSData*data=[jsondataUsingEncoding:NSUTF8StringEncoding];
idjsonObject=[NSJSONSerializationJSONObjectWithData:dataoptions:NSJSONReadingAllowFragmentserror:nil];
if([jsonObjectisKindOfClass:[NSDictionaryclass]]){
self.innerDictionary=[jsonObjectmutableCopy];
}
returnself;
}
NSProxy可以說除了重載消息轉發(fā)機制外沒有別的用法,這也是它被設計的初衷平斩,自己什么都不干亚享,轉給代理對象就好。往這個proxy發(fā)消息是注定會走消息轉發(fā)的绘面,首先判斷下是不是一個getter或setter的selector:
Objective-C
1
2
3
4
5
6
7
8
9
10
11-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
{
SELchangedSelector=aSelector;
if([selfpropertyNameScanFromGetterSelector:aSelector]){
changedSelector=@selector(objectForKey:);
}
elseif([selfpropertyNameScanFromSetterSelector:aSelector]){
changedSelector=@selector(setObject:forKey:);
}
return[[self.innerDictionaryclass]instanceMethodSignatureForSelector:changedSelector];
}
簽名替換成字典的兩個方法后開始走轉發(fā)欺税,在這里設置參數(shù)和對內(nèi)部字典的真正調(diào)用:
Objective-C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21-(void)forwardInvocation:(NSInvocation*)invocation
{
NSString*propertyName=nil;
// Try getter
propertyName=[selfpropertyNameScanFromGetterSelector:invocation.selector];
if(propertyName){
invocation.selector=@selector(objectForKey:);
[invocationsetArgument:&propertyNameatIndex:2];// self, _cmd, key
[invocationinvokeWithTarget:self.innerDictionary];
return;
}
// Try setter
propertyName=[selfpropertyNameScanFromSetterSelector:invocation.selector];
if(propertyName){
invocation.selector=@selector(setObject:forKey:);
[invocationsetArgument:&propertyNameatIndex:3];// self, _cmd, obj, key
[invocationinvokeWithTarget:self.innerDictionary];
return;
}
[superforwardInvocation:invocation];
}
當然還有這兩個必不可少的從getter和setter中獲取屬性名的Helper:
Objective-C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19-(NSString*)propertyNameScanFromGetterSelector:(SEL)selector
{
NSString*selectorName=NSStringFromSelector(selector);
NSUIntegerparameterCount=[[selectorNamecomponentsSeparatedByString:@":"]count]-1;
if(parameterCount==0){
returnselectorName;
}
returnnil;
}
-(NSString*)propertyNameScanFromSetterSelector:(SEL)selector
{
NSString*selectorName=NSStringFromSelector(selector);
NSUIntegerparameterCount=[[selectorNamecomponentsSeparatedByString:@":"]count]-1;
if([selectorNamehasPrefix:@"set"]&?meterCount==1){
NSUIntegerfirstColonLocation=[selectorNamerangeOfString:@":"].location;
return[selectorNamesubstringWithRange:NSMakeRange(3,firstColonLocation-3)].lowercaseString;
}
returnnil;
}
一個簡單的鴨子Entity就完成了,之后所有的Entity都可以使用@protocol而非子類化的方式來定義揭璃,如:
Objective-C
1
2
3
4
5
6
7
8
9@protocolXXUserEntityNSObject>
@property(nonatomic,copy)NSString*name;
@property(nonatomic,copy)NSString*sex;
@property(nonatomic,strong)NSNumber*age;
@end
@protocolXXStudentEntityXXUserEntity>
@property(nonatomic,copy)NSString*school;
@property(nonatomic,copy)NSString*teacher;
@end
當數(shù)據(jù)從網(wǎng)絡層回來時晚凿,鴨子類型讓這個對象用起來和真有這么個類沒什么兩樣:
Objective-C
1
2
3-(void)requestFinished:(XXDuckEntity*)student{
NSLog(@"name: %@, school:%@", student.name, student.school);
}
至此,所有的entity被表示成了N個的.h文件加一個XXDuckEntity類瘦馍,剩下的就靠想象力了歼秽。
這個demo的源碼將在下半部分之后給出