objc與鴨子對象(上)

原文出處:Sunnyxx(@我就叫Sunny怎么了)

這是《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的源碼將在下半部分之后給出

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市情组,隨后出現(xiàn)的幾起案子燥筷,更是在濱河造成了極大的恐慌箩祥,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肆氓,死亡現(xiàn)場離奇詭異袍祖,居然都是意外死亡,警方通過查閱死者的電腦和手機谢揪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門蕉陋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拨扶,你說我怎么就攤上這事凳鬓。” “怎么了患民?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵缩举,是天一觀的道長。 經(jīng)常有香客問我酒奶,道長蚁孔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任惋嚎,我火速辦了婚禮,結果婚禮上站刑,老公的妹妹穿的比我還像新娘另伍。我一直安慰自己,他們只是感情好绞旅,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布摆尝。 她就那樣靜靜地躺著,像睡著了一般因悲。 火紅的嫁衣襯著肌膚如雪椒丧。 梳的紋絲不亂的頭發(fā)上生均,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天,我揣著相機與錄音,去河邊找鬼谜酒。 笑死,一個胖子當著我的面吹牛佑吝,可吹牛的內(nèi)容都是我干的骨饿。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼顾翼,長吁一口氣:“原來是場噩夢啊……” “哼投放!你這毒婦竟也來了?” 一聲冷哼從身側響起适贸,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤灸芳,失蹤者是張志新(化名)和其女友劉穎涝桅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烙样,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡苹支,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了误阻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片债蜜。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖究反,靈堂內(nèi)的尸體忽然破棺而出寻定,到底是詐尸還是另有隱情,我是刑警寧澤精耐,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布狼速,位于F島的核電站,受9級特大地震影響卦停,放射性物質發(fā)生泄漏向胡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一惊完、第九天 我趴在偏房一處隱蔽的房頂上張望僵芹。 院中可真熱鬧,春花似錦小槐、人聲如沸拇派。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽件豌。三九已至,卻和暖如春控嗜,著一層夾襖步出監(jiān)牢的瞬間茧彤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工疆栏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留曾掂,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓承边,卻偏偏與公主長得像遭殉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子博助,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容