標籤:
在ObjC中使用@protocol定義一組方法規範,實現此協議的類必須實現對應的方法。熟悉物件導向的童鞋都知道介面本身是對象行為描述的協議規範。也就是說在ObjC中@protocol和其他語言的介面定義是類似的,只是在ObjC中interface關鍵字已經用於定義類了,因此它不會再像C#、Java中使用interface定義介面了。
假設我們定義了一個動物的協議AnimalDelegate,人員Person這個類需要實現這個協議,請看下面的代碼:
AnimalDelegate.h
//// AnimalDelegate.h// Protocol&Block&Category//// Created by Kenshin Cui on 14-2-2.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.////定義一個協議@protocol AnimalDelegate <NSObject>@required //必須實現的方法-(void)eat;@optional //可選實現的方法-(void)run;-(void)say;-(void)sleep;@end
Person.h
//// Person.h// Protocol&Block&Category//// Created by Kenshin Cui on 14-2-2.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import <Foundation/Foundation.h>#import "AnimalDelegate.h"@interface Person : NSObject<AnimalDelegate>-(void)eat;@end
Person.m
//// Person.m// Protocol&Block&Category//// Created by Kenshin Cui on 14-2-2.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "Person.h"@implementation Person-(void)eat{ NSLog(@"eating...");}@end
這裡需要說明幾點:
- 一個協議可以擴充自另一個協議,例如上面AnimalDelegate就擴充自NSObject,如果需要擴充多個協議中間使用逗號分隔;
- 和其他進階語言中介面不同的是協議中定義的方法不一定是必須實現的,我們可以通過關鍵字進行@required和@optional進行設定,如果不設定則預設是@required(注意ObjC是弱文法,即使不實現必選方法編譯運行也不會報錯);
- 協議通過<>進行實現,一個類可以同時實現多個協議,中間通過逗號分隔;
- 協議的實現只能在類的聲明上,不能放到類的實現上(也就是說必須寫成@interface Person:NSObject<AnimalDelegate>而不能寫成@implementation Person<AnimalDelegate>);
- 協議中不能定義屬性、成員變數等,只能定義方法;
事實上在ObjC中協議的更多作用是用於約束一個類必須實現某些方法,而從物件導向的角度而言這個類跟介面並不一定存在某種自然關係,可能是兩個完全不同意義上的事物,這種模式我們稱之為代理模式(Delegation)。在Cocoa架構中大量採用這種模式實現資料和UI的分離,而且基本上所有的協議都是以Delegate結尾。
現在假設需要設計一個按鈕,我們知道按鈕都是需要點擊的,在其他語言中通常會引入事件機制,只要使用者訂閱了點擊事件,那麼點擊的時候就會觸發執行這個事件(這是對象之間解耦的一種方式:代碼注入)。但是在ObjC中沒有事件的定義,而是使用代理來處理這個問題。首先在按鈕中定義按鈕的代理,同時使用協議約束這個代理(事件的觸發者)必須實現協議中的某些方法,當按鈕處理過程中查看代理是否實現了這個方法,如果實現了則調用這個方法。
事實上在ObjC中協議的更多作用是用於約束一個類必須實現某些方法,而從物件導向的角度而言這個類跟介面並不一定存在某種自然關係,可能是兩個完全不同意義上的事物,這種模式我們稱之為代理模式(Delegation)。在Cocoa架構中大量採用這種模式實現資料和UI的分離,而且基本上所有的協議都是以Delegate結尾。
現在假設需要設計一個按鈕,我們知道按鈕都是需要點擊的,在其他語言中通常會引入事件機制,只要使用者訂閱了點擊事件,那麼點擊的時候就會觸發執行這個事件(這是對象之間解耦的一種方式:代碼注入)。但是在ObjC中沒有事件的定義,而是使用代理來處理這個問題。首先在按鈕中定義按鈕的代理,同時使用協議約束這個代理(事件的觸發者)必須實現協議中的某些方法,當按鈕處理過程中查看代理是否實現了這個方法,如果實現了則調用這個方法。
KCButton.h
//// KCButton.h// Protocol&Block&Category//// Created by Kenshin Cui on 14-2-2.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import <Foundation/Foundation.h>@class KCButton;//一個協議可以擴充另一個協議,例如KCButtonDelegate擴充了NSObject協議@protocol KCButtonDelegate <NSObject>@required //@required修飾的方法必須實現-(void)onClick:(KCButton *)button;@optional //@optional修飾的方法是可選實現的-(void)onMouseover:(KCButton *)button;-(void)onMouseout:(KCButton *)button;@end@interface KCButton : NSObject#pragma mark - 屬性#pragma mark 代理屬性,同時約定作為代理的對象必須實現KCButtonDelegate協議@property (nonatomic,retain) id<KCButtonDelegate> delegate;#pragma mark - 公用方法#pragma mark 點擊方法-(void)click;@end
KCButton.m
//// KCButton.m// Protocol&Block&Category//// Created by Kenshin Cui on 14-2-2.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCButton.h"@implementation KCButton-(void)click{ NSLog(@"Invoke KCButton‘s click method."); //判斷_delegate執行個體是否實現了onClick:方法(注意方法名是"onClick:",後面有個:) //避免未實現ButtonDelegate的類也作為KCButton的監聽 if([_delegate respondsToSelector:@selector(onClick:)]){ [_delegate onClick:self]; }}@end
MyListener.h
//// MyListener.h// Protocol&Block&Category//// Created by Kenshin Cui on 14-2-2.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import <Foundation/Foundation.h>@class KCButton;@protocol KCButtonDelegate;@interface MyListener : NSObject<KCButtonDelegate>-(void)onClick:(KCButton *)button;@end
MyListener.m
//// MyListener.m// Protocol&Block&Category//// Created by Kenshin Cui on 14-2-2.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "MyListener.h"#import "KCButton.h"@implementation MyListener-(void)onClick:(KCButton *)button{ NSLog(@"Invoke MyListener‘s onClick method.The button is:%@.",button);}@end
main.m
//// main.m// Protocol&Block&Category//// Created by Kenshin Cui on 14-2-2.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import <Foundation/Foundation.h>#import "KCButton.h"#import "MyListener.h"int main(int argc, const char * argv[]) { @autoreleasepool { KCButton *button=[[KCButton alloc]init]; MyListener *listener=[[MyListener alloc]init]; button.delegate=listener; [button click]; /* 結果: Invoke KCButton‘s click method. Invoke MyListener‘s onClick method.The button is:<KCButton: 0x1001034c0>. */ } return 0;}
我們通過例子類比了一個按鈕的點擊過程,有點類似於Java中事件的實現機制。通過這個例子我們需要注意以下幾點內容:
- id可以表示任何一個ObjC物件類型,類型後面的”<協議名>“用於約束作為這個屬性的對象必須實現該協議(注意:使用id定義的物件類型不需要加“*”);
- MyListener作為事件觸發者,它實現了KCButtonDelegate代理(在ObjC中沒有命名空間和包的概念,通常通過首碼進行類的劃分,“KC”是我們自訂的首碼)
- 在.h檔案中如果使用了另一個檔案的類或協議我們可以通過@class或者@protocol進行聲明,而不必匯入這個檔案,這樣可以提高編譯效率(注意有些情況必須使用@class或@protocol,例如上面KCButton.h中上面聲明的KCButtonDelegate協議中用到了KCButton類,而此檔案下方的KCButton類聲明中又使用了KCButtonDelegate,從而形成在一個檔案中互相參考關聯性,此時必須使用@class或者@protocol聲明,否則編譯階段會報錯),但是在.m檔案中則必須匯入對應的類聲明檔案或協議檔案(如果不匯入雖然語法檢查可以通過但是編譯連結會報錯);
- 使用respondsToSelector方法可以判斷一個對象是否實現了某個方法(需要注意方法名不是”onClick”而是“onClick:”,冒號也是方法名的一部分);
代碼塊Block
在C#非同步編程時我們經常進行函數回調,由於函數調用是非同步執行的,我們如果想讓一個操作執行完之後執行另一個函數,則無法按照正常代碼書寫順序進行編程,因為我們無法獲知前一個方法什麼時候執行結束,此時我們經常會用到匿名委託或者lambda運算式將一個操作作為一個參數進行傳遞。其實在ObjC中也有類似的方法,稱之為代碼塊(Block)。Block就是一個函數體(匿名函數),它是ObjC對於閉包的實現,在塊狀中我們可以持有或引用局部變數(不禁想到了lambda運算式),同時利用Block你可以將一個操作作為一個參數進行傳遞(是不是想起了C語言中的函數指標)。在下面的例子中我們將使用Block實現上面的點擊監聽操作:
KCButton.h
//// KCButton.h// Protocol&Block&Category//// Created by Kenshin Cui on 14-2-2.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import <Foundation/Foundation.h>@class KCButton;typedef void(^KCButtonClick)(KCButton *);@interface KCButton : NSObject#pragma mark - 屬性#pragma mark 點擊操作屬性@property (nonatomic,copy) KCButtonClick onClick;//上面的屬性定義等價於下面的代碼//@property (nonatomic,copy) void(^ onClick)(KCButton *);#pragma mark - 公用方法#pragma mark 點擊方法-(void)click;@end
KCButton.m
//// KCButton.m// Protocol&Block&Category//// Created by Kenshin Cui on 14-2-2.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCButton.h"@implementation KCButton-(void)click{ NSLog(@"Invoke KCButton‘s click method."); if (_onClick) { _onClick(self); }}@end
main.m
//// main.m// Protocol&Block&Category//// Created by Kenshin Cui on 14-2-2.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import <Foundation/Foundation.h>#import "KCButton.h"int main(int argc, const char * argv[]) { KCButton *button=[[KCButton alloc]init]; button.onClick=^(KCButton *btn){ NSLog(@"Invoke onClick method.The button is:%@.",btn); }; [button click]; /*結果: Invoke KCButton‘s click method. Invoke onClick method.The button is:<KCButton: 0x1006011f0>. */ return 0;}
上面代碼中使用Block同樣實現了按鈕的點擊事件,關於Block總結如下:
- Block類型定義:傳回值類型(^ 變數名)(參數列表)(注意Block也是一種類型);
- Block的typedef定義:傳回值類型(^類型名稱)(參數列表);
- Block的實現:^(參數列表){操作主體};
- Block中可以讀取塊外面定義的變數但是不能修改,如果要修改那麼這個變數必須聲明_block修飾;
分類Category
當我們不改變原有代碼為一個類擴充其他功能時我們可以考慮繼承這個類進行實現,但是這樣一來使用時就必須定義成新實現的子類才能擁有擴充的新功能。如何在不改變原有類的情況下擴充新功能又可以在使用時不必定義新類型呢?我們知道如果在C#中可以使用擴充方法,其實在ObjC中也有類似的實現,就是分類Category。利用分類,我們就可以在ObjC中動態為已有類添加新的行為(特別是系統或架構中的類)。在C#中字串有一個Trim()方法用於去掉字串前後的空格,使用起來特別方便,但是在ObjC中卻沒有這個方法,這裡我們不妨通過Category給NSString添加一個stringByTrim()方法:
NSString+Extend.h
//// NSString+Extend.h// Protocol&Block&Category//// Created by Kenshin Cui on 14-2-2.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import <Foundation/Foundation.h>@interface NSString (Extend)-(NSString *)stringByTrim;@end
NSString+Extend.m
//// NSString+Extend.m// Protocol&Block&Category//// Created by Kenshin Cui on 14-2-2.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "NSString+Extend.h"@implementation NSString (Extend)-(NSString *)stringByTrim{ NSCharacterSet *character= [NSCharacterSet whitespaceCharacterSet]; return [self stringByTrimmingCharactersInSet:character];}@end
main.m
//// main.m// Protocol&Block&Category//// Created by Kenshin Cui on 14-2-2.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import <Foundation/Foundation.h>#import "NSString+Extend.h"int main(int argc, const char * argv[]) { NSString *[email protected]" Kenshin Cui "; name=[name stringByTrim]; NSLog(@"I‘m %@!",name); //結果:I‘m Kenshin Cui! return 0;}
通過上面的輸出結果我們可以看出已經成功將@” Kenshin Cui ”兩端的空格去掉了。分類檔案名稱一般是“原有類名+分類名稱”,分類的定義是通過在原有類名後加上”(分類名)”來定義的(注意聲明檔案.h和實現檔案.m都是如此)。
iOS學習筆記-協議,代碼塊,分類