Objective-C之協議、代碼塊、分類

前言html

 

ObjC的語法主要基於smalltalk進行設計的,除了提供常規的面向對象特性外,還增長了不少其餘特性,這一節將重點介紹ObjC中一些經常使用的語法特性。固然這些內容雖然和其餘高級語言命名不同,可是咱們均可以在其中找到他們的影子,在文章中我也會對比其餘語言進行介紹,這一節的重點內容以下:編程

 

  1. 協議protocol
  2. 代碼塊block
  3. 分類category

協議protocol閉包

 

在ObjC中使用@protocol定義一組方法規範,實現此協議的類必須實現對應的方法。熟悉面向對象的童鞋都知道接口自己是對象行爲描述的協議規範。也就是說在ObjC中@protocol和其餘語言的接口定義是相似的,只是在ObjC中interface關鍵字已經用於定義類了,所以它不會再像C#、Java中使用interface定義接口了。框架

 

假設咱們定義了一個動物的協議AnimalDelegate,人員Person這個類須要實現這個協議,請看下面的代碼:異步

 

AnimalDelegate.h異步編程

 1 //
 2 //  AnimalDelegate.h
 3 //  Protocol&Block&Category
 4 //
 5 //  Created by Kenshin Cui on 14-2-2.
 6 //  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
 7 //
 8 
 9 
10 //定義一個協議
11 @protocol AnimalDelegate <NSObject>
12 
13 @required //必須實現的方法
14 -(void)eat;
15 
16 @optional //可選實現的方法
17 -(void)run;
18 -(void)say;
19 -(void)sleep;
20 
21 @end

Person.h函數

 1 //
 2 //  Person.h
 3 //  Protocol&Block&Category
 4 //
 5 //  Created by Kenshin Cui on 14-2-2.
 6 //  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
 7 //
 8 
 9 #import <Foundation/Foundation.h>
10 #import "AnimalDelegate.h"
11 
12 @interface Person : NSObject<AnimalDelegate>
13 
14 -(void)eat;
15 
16 @end

Person.mui

 1 //
 2 //  Person.m
 3 //  Protocol&Block&Category
 4 //
 5 //  Created by Kenshin Cui on 14-2-2.
 6 //  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
 7 //
 8 
 9 #import "Person.h"
10 
11 @implementation Person
12 
13 -(void)eat{
14     NSLog(@"eating...");
15 }
16 
17 @end

這裏須要說明幾點:atom

  1. 一個協議能夠擴展自另外一個協議,例如上面AnimalDelegate就擴展自NSObject,若是須要擴展多個協議中間使用逗號分隔;
  2. 和其餘高級語言中接口不一樣的是協議中定義的方法不必定是必須實現的,咱們能夠經過關鍵字進行@required和@optional進行設置,若是不設置則默認是@required(注意ObjC是弱語法,即便不實現必選方法編譯運行也不會報錯);
  3. 協議經過<>進行實現,一個類能夠同時實現多個協議,中間經過逗號分隔;
  4. 協議的實現只能在類的聲明上,不能放到類的實現上(也就是說必須寫成@interface Person:NSObject<AnimalDelegate>而不能寫成@implementation Person<AnimalDelegate>);
  5. 協議中不能定義屬性、成員變量等,只能定義方法;

事實上在ObjC中協議的更多做用是用於約束一個類必須實現某些方法,而從面向對象的角度而言這個類跟接口並不必定存在某種天然關係,多是兩個徹底不一樣意義上的事物,這種模式咱們稱之爲代理模式(Delegation)。在Cocoa框架中大量採用這種模式實現數據和UI的分離,並且基本上全部的協議都是以Delegate結尾。spa

如今假設須要設計一個按鈕,咱們知道按鈕都是須要點擊的,在其餘語言中一般會引入事件機制,只要使用者訂閱了點擊事件,那麼點擊的時候就會觸發執行這個事件(這是對象之間解耦的一種方式:代碼注入)。可是在ObjC中沒有事件的定義,而是使用代理來處理這個問題。首先在按鈕中定義按鈕的代理,同時使用協議約束這個代理(事件的觸發者)必須實現協議中的某些方法,當按鈕處理過程當中查看代理是否實現了這個方法,若是實現了則調用這個方法。

KCButton.h

 1 //
 2 //  KCButton.h
 3 //  Protocol&Block&Category
 4 //
 5 //  Created by Kenshin Cui on 14-2-2.
 6 //  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
 7 //
 8 
 9 #import <Foundation/Foundation.h>
10 @class KCButton;
11 
12 //一個協議能夠擴展另外一個協議,例如KCButtonDelegate擴展了NSObject協議
13 @protocol KCButtonDelegate <NSObject>
14 
15 @required //@required修飾的方法必須實現
16 -(void)onClick:(KCButton *)button;
17 
18 @optional //@optional修飾的方法是可選實現的
19 -(void)onMouseover:(KCButton *)button;
20 -(void)onMouseout:(KCButton *)button;
21 
22 @end
23 
24 @interface KCButton : NSObject
25 
26 #pragma mark - 屬性
27 #pragma mark 代理屬性,同時約定做爲代理的對象必須實現KCButtonDelegate協議
28 @property (nonatomic,retain) id<KCButtonDelegate> delegate;
29 
30 #pragma mark - 公共方法
31 #pragma mark 點擊方法
32 -(void)click;
33 
34 @end

KCButton.m

 1 //
 2 //  KCButton.m
 3 //  Protocol&Block&Category
 4 //
 5 //  Created by Kenshin Cui on 14-2-2.
 6 //  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
 7 //
 8 
 9 #import "KCButton.h"
10 
11 @implementation KCButton
12 
13 -(void)click{
14     NSLog(@"Invoke KCButton's click method.");
15     //判斷_delegate實例是否實現了onClick:方法(注意方法名是"onClick:",後面有個:)
16     //避免未實現ButtonDelegate的類也做爲KCButton的監聽
17     if([_delegate respondsToSelector:@selector(onClick:)]){
18         [_delegate onClick:self];
19     }
20 }
21 
22 @end

MyListener.h

 1 //
 2 //  MyListener.h
 3 //  Protocol&Block&Category
 4 //
 5 //  Created by Kenshin Cui on 14-2-2.
 6 //  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
 7 //
 8 
 9 #import <Foundation/Foundation.h>
10 @class KCButton;
11 @protocol KCButtonDelegate;
12 
13 @interface MyListener : NSObject<KCButtonDelegate>
14 -(void)onClick:(KCButton *)button;
15 @end

MyListener.m

 1 //
 2 //  MyListener.m
 3 //  Protocol&Block&Category
 4 //
 5 //  Created by Kenshin Cui on 14-2-2.
 6 //  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
 7 //
 8 
 9 #import "MyListener.h"
10 #import "KCButton.h"
11 
12 @implementation MyListener
13 -(void)onClick:(KCButton *)button{
14     NSLog(@"Invoke MyListener's onClick method.The button is:%@.",button);
15 }
16 @end

main.m

 1 //
 2 //  main.m
 3 //  Protocol&Block&Category
 4 //
 5 //  Created by Kenshin Cui on 14-2-2.
 6 //  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
 7 //
 8 
 9 #import <Foundation/Foundation.h>
10 #import "KCButton.h"
11 #import "MyListener.h"
12 
13 int main(int argc, const char * argv[]) {
14     @autoreleasepool {
15         
16         KCButton *button=[[KCButton alloc]init];
17         MyListener *listener=[[MyListener alloc]init];
18         button.delegate=listener;
19         [button click];
20         /* 結果:
21          Invoke KCButton's click method.
22          Invoke MyListener's onClick method.The button is:<KCButton: 0x1001034c0>.
23          */
24     }
25     return 0;
26 }

咱們經過例子模擬了一個按鈕的點擊過程,有點相似於Java中事件的實現機制。經過這個例子咱們須要注意如下幾點內容:

  1. id能夠表示任何一個ObjC對象類型,類型後面的」<協議名>「用於約束做爲這個屬性的對象必須實現該協議(注意:使用id定義的對象類型不須要加「*」);
  2. MyListener做爲事件觸發者,它實現了KCButtonDelegate代理(在ObjC中沒有命名空間和包的概念,一般經過前綴進行類的劃分,「KC」是咱們自定義的前綴)
  3. 在.h文件中若是使用了另外一個文件的類或協議咱們能夠經過@class或者@protocol進行聲明,而沒必要導入這個文件,這樣能夠提升編譯效率(注意有些狀況必須使用@class或@protocol,例如上面KCButton.h中上面聲明的KCButtonDelegate協議中用到了KCButton類,而此文件下方的KCButton類聲明中又使用了KCButtonDelegate,從而造成在一個文件中互相引用關係,此時必須使用@class或者@protocol聲明,不然編譯階段會報錯),可是在.m文件中則必須導入對應的類聲明文件或協議文件(若是不導入雖然語法檢查能夠經過可是編譯連接會報錯);
  4. 使用respondsToSelector方法能夠判斷一個對象是否實現了某個方法(須要注意方法名不是」onClick」而是「onClick:」,冒號也是方法名的一部分);

代碼塊Block

 

在C#異步編程時咱們常常進行函數回調,因爲函數調用是異步執行的,咱們若是想讓一個操做執行完以後執行另外一個函數,則沒法按照正常代碼書寫順序進行編程,由於咱們沒法獲知前一個方法何時執行結束,此時咱們常常會用到匿名委託或者lambda表達式將一個操做做爲一個參數進行傳遞。其實在ObjC中也有相似的方法,稱之爲代碼塊(Block)。Block就是一個函數體(匿名函數),它是ObjC對於閉包的實現,在塊狀中咱們能夠持有或引用局部變量(不由想到了lambda表達式),同時利用Block你能夠將一個操做做爲一個參數進行傳遞(是否是想起了C語言中的函數指針)。在下面的例子中咱們將使用Block實現上面的點擊監聽操做:

 

KCButton.h

 1 //
 2 //  KCButton.h
 3 //  Protocol&Block&Category
 4 //
 5 //  Created by Kenshin Cui on 14-2-2.
 6 //  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
 7 //
 8 
 9 #import <Foundation/Foundation.h>
10 @class KCButton;
11 typedef void(^KCButtonClick)(KCButton *);
12 
13 @interface KCButton : NSObject
14 
15 #pragma mark - 屬性
16 #pragma mark 點擊操做屬性
17 @property (nonatomic,copy) KCButtonClick onClick;
18 //上面的屬性定義等價於下面的代碼
19 //@property (nonatomic,copy) void(^ onClick)(KCButton *);
20 
21 #pragma mark - 公共方法
22 #pragma mark 點擊方法
23 -(void)click;
24 @end

KCButton.m

 1 //
 2 //  KCButton.m
 3 //  Protocol&Block&Category
 4 //
 5 //  Created by Kenshin Cui on 14-2-2.
 6 //  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
 7 //
 8 
 9 #import "KCButton.h"
10 
11 
12 @implementation KCButton
13 
14 -(void)click{
15     NSLog(@"Invoke KCButton's click method.");
16     if (_onClick) {
17         _onClick(self);
18     }
19 }
20 
21 @end

main.m

 1 //
 2 //  main.m
 3 //  Protocol&Block&Category
 4 //
 5 //  Created by Kenshin Cui on 14-2-2.
 6 //  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
 7 //
 8 
 9 #import <Foundation/Foundation.h>
10 #import "KCButton.h"
11 
12 
13 int main(int argc, const char * argv[]) {
14 
15     KCButton *button=[[KCButton alloc]init];
16     button.onClick=^(KCButton *btn){
17         NSLog(@"Invoke onClick method.The button is:%@.",btn);
18     };
19     [button click];
20     /*結果:
21      Invoke KCButton's click method.
22      Invoke onClick method.The button is:<KCButton: 0x1006011f0>.
23      */
24     
25     
26     return 0;
27 }

上面代碼中使用Block一樣實現了按鈕的點擊事件,關於Block總結以下:

  1. Block類型定義:返回值類型(^ 變量名)(參數列表)注意Block也是一種類型);
  2. Block的typedef定義:返回值類型(^類型名稱)(參數列表)
  3. Block的實現:^(參數列表){操做主體}
  4. Block中能夠讀取塊外面定義的變量可是不能修改,若是要修改那麼這個變量必須聲明_block修飾;

分類Category

當咱們不改變原有代碼爲一個類擴展其餘功能時咱們能夠考慮繼承這個類進行實現,可是這樣一來使用時就必須定義成新實現的子類才能擁有擴展的新功能。如何在不改變原有類的狀況下擴展新功能又能夠在使用時沒必要定義新類型呢?咱們知道若是在C#中可使用擴展方法,其實在ObjC中也有相似的實現,就是分類Category。利用分類,咱們就能夠在ObjC中動態的爲已有類添加新的行爲(特別是系統或框架中的類)。在C#中字符串有一個Trim()方法用於去掉字符串先後的空格,使用起來特別方便,可是在ObjC中卻沒有這個方法,這裏咱們不妨經過Category給NSString添加一個stringByTrim()方法:

NSString+Extend.h

 

 1 //
 2 //  NSString+Extend.h
 3 //  Protocol&Block&Category
 4 //
 5 //  Created by Kenshin Cui on 14-2-2.
 6 //  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
 7 //
 8 
 9 #import <Foundation/Foundation.h>
10 
11 @interface NSString (Extend)
12 -(NSString *)stringByTrim;
13 @end

 

NSString+Extend.m

 1 //
 2 //  NSString+Extend.m
 3 //  Protocol&Block&Category
 4 //
 5 //  Created by Kenshin Cui on 14-2-2.
 6 //  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
 7 //
 8 
 9 #import "NSString+Extend.h"
10 
11 @implementation NSString (Extend)
12 -(NSString *)stringByTrim{
13     NSCharacterSet *character= [NSCharacterSet whitespaceCharacterSet];
14     return [self stringByTrimmingCharactersInSet:character];
15 }
16 @end

main.m

 1 //
 2 //  main.m
 3 //  Protocol&Block&Category
 4 //
 5 //  Created by Kenshin Cui on 14-2-2.
 6 //  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
 7 //
 8 
 9 #import <Foundation/Foundation.h>
10 #import "NSString+Extend.h"
11 
12 
13 int main(int argc, const char * argv[]) {
14 
15     NSString *name=@" Kenshin Cui ";
16     name=[name stringByTrim];
17     NSLog(@"I'm %@!",name); //結果:I'm Kenshin Cui!
18     
19     return 0;
20 }

經過上面的輸出結果咱們能夠看出已經成功將@」 Kenshin Cui 」兩端的空格去掉了。分類文件名通常是「原有類名+分類名稱」,分類的定義是經過在原有類名後加上」(分類名)」來定義的(注意聲明文件.h和實現文件.m都是如此)。

 

原文連接:http://www.cnblogs.com/kenshincui/p/3869639.html#3285408

/文:崔江濤(KenshinCui)

相關文章
相關標籤/搜索