iOS開發系列--Objective-C之協議、代碼塊、分類

概述

ObjC的語法主要基於smalltalk進行設計的,除了提供常規的面向對象特性外,還增加了很多其他特性,這一節將重點介紹ObjC中一些常用的語法特性。當然這些內容雖然和其他高級語言命名不一樣,但是我們都可以在其中找到他們的影子,在文章中我也會對比其他語言進行介紹,這一節的重點內容如下:

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

協議protocol

在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

這裏需要說明幾點:

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

事實上在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中事件的實現機制。通過這個例子我們需要注意以下幾點內容:

  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:”,冒號也是方法名的一部分);

屬性中的(nonatomic,retain)不是這篇文章的重點,在接下來的文章中我們會具體介紹。

代碼塊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總結如下:

  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

//
//  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 *name=@" Kenshin Cui ";
    name=[name stringByTrim];
    NSLog(@"I'm %@!",name); //結果:I'm Kenshin Cui!
    
    return 0;
}

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

知識共享許可協議 本作品採用知識共享署名 2.5 中國大陸許可協議進行許可,歡迎轉載,演繹或用於商業目的。但轉載請註明來自崔江濤(KenshinCui),幷包含相關鏈接。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章