天天看點

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

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

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

協定protocol

代碼塊block

分類category

概述

  1. 協定protocol
  2. 代碼塊block

在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
//  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
//  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
//  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
//  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都是如此)。

iOS開發系列--Objective-C之協定、代碼塊、分類
本作品采用知識共享署名 2.5 中國大陸許可協定進行許可,歡迎轉載,演繹或用于商業目的。但轉載請注明來自崔江濤(KenshinCui),并包含相關連結。

繼續閱讀