【原】談談對Objective-C中代理模式的誤解

【原】談談對Objective-C中代理模式的誤解

本文轉載請註明出處 —— polobymulberry-博客園html

1. 前言


這篇文章主要是對代理模式和委託模式進行了對比,我的認爲Objective-C中的delegate大部分用法屬於委託模式。全文有些摳概念,對實際開發沒有任何影響。ios

前段時間看到的一篇博客iOS開發——從一道題看Delegate,和這篇博客iOS APP 架構漫談解決的問題相似。兩篇blog都寫得很不錯,都是爲了解決兩個頁面之間的數據傳遞問題:git

A頁面中有一個UILabel *labelA,B頁面中有一個UITextField *textFieldB。從A頁面跳轉到B頁面後,更改textFieldB中數據再返回到A頁面,labelA顯示的將是textFieldB中更改後的數據,嗯,就是這麼簡單的一個數據傳遞場景。github

解決這個問題方法不少,好比使用一個DAO(data access object)去維護labelA和textFieldB所對應的數據。頁面的數據流向以下圖這樣:設計模式

image

可是這個場景不是很複雜,因此並不須要引入DAO這麼重的架構。架構

有時候咱們會陷入技術的細節不可自拔,不妨靜下來想想,這個問題本質在什麼?ide

這個問題的難點在於頁面B中textFieldB的數據變化後沒法通知頁面A中的labelA。若是頁面B中有labelA的引用就行了,這樣就能夠直接在頁面B的代碼中操做labelA。因而我在頁面B中添加了一個UILabel *labelARef,在A頁面push到B頁面時,將頁面A的labelA賦值給labelRef便可(親測能夠進行數據傳遞)。函數

上述方法確實可行,不過你們確定都以爲這樣設計也是太粗暴了。若是數據傳遞的業務比較多,那麼頁面B中就須要引用不少頁面A的屬性。固然咱們能夠直接引用頁面A做爲頁面B的屬性,即UIViewController *vcA。以下圖所示:atom

image

這樣設計其實沒啥問題。不過咱們此次主題是代理模式,那咱們說的這個問題到底和代理模式有什麼聯繫呢?spa

2.使用代理模式實現數據傳遞


咱們先看看GoF《設計模式:可複用面向軟件的基礎》中對代理模式的描述:爲其餘對象提供一種代理以控制對這個對象的訪問。咦,是否是和上面這個問題很像?爲頁面B提供一種代理以控制頁面A的訪問,能控制頁面A,那就能控制頁面A中的labelA。但是上面那種直接引用對象的方法也能夠提供對這個對象的訪問啊,爲何必定要經過代理呢?咱們來看下代理模式的UML圖:

image注意上圖中Proxy和RealSubject都實現了Subject這個接口,而且實現了相同的接口函數DoAction(),另外Proxy存有一份RealSubject的引用,即圖中的delegate。通常來講,Proxy在實現DoAction時,會調用RealSubject的DoAction,也就是利用所引用的delegate調用RealSubject的DoAction。按照我本身的理解,之因此會出現代理模式,是因爲用戶須要對RealSubject的DoAction功能進行擴展,又沒法對RealSubject中的DoAction直接進行修改(並且也違反了封閉-開放原則),因而使用了Proxy對RealSubject的DoAction進行了擴展,而擴展的內容都是DoAction,因此又將DoAction抽象出來,作成了接口。

回到上面那個案例,咱們能夠利用代理模式進行以下架構設計:

image

這裏介紹一個小技巧,即如何辨別誰是代理 —— 直接跟Client打交道的是代理,此處Client就是ViewControllerB的textFieldB控件,因此直接打交道的就是ViewControllerB,也就是說ViewControllerB是代理。

代碼以下:

// DataTransDelegate

// DataTransDelegate
@protocol DataTransDelegate <NSObject>
- (void)didTextFieldChanged:(UITextField *)textField;
@end

// ViewControllerA

// ViewControllerA.m
#import "ViewControllerA.h"
#import "ViewControllerB.h"
#import "DataTransDelegate.h"

@interface ViewControllerA () <DataTransDelegate>
@property (strong, nonatomic) UILabel *labelA;
@property (strong, nonatomic) UIButton *buttonA;
@end

@implementation ViewControllerA

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.view addSubview:self.labelA];
    [self.view addSubview:self.buttonA];
    
    [self.buttonA addTarget:self action:@selector(pushVC) forControlEvents:UIControlEventTouchUpInside];
}

- (void)pushVC
{
    ViewControllerB *vcB = [[ViewControllerB alloc] init];
    vcB.delegate = self;
    [self.navigationController pushViewController:vcB animated:NO];
}

- (void)didTextFieldChanged:(UITextField *)textField
{
    self.labelA.text = textField.text;
}

- (UILabel *)labelA
{
    if (_labelA == nil) {
        _labelA = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 50)];
        _labelA.text = @"顯示vcB中的textField內容";
    }
    return _labelA;
}

- (UIButton *)buttonA
{
    if (_buttonA == nil) {
        _buttonA = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 100, 50)];
        _buttonA.backgroundColor = [UIColor blueColor];
        [_buttonA setTitle:@"進入vcB" forState:UIControlStateNormal];
    }
    return _buttonA;
}

@end

// ViewControllerB

// ViewControllerB.h
@protocol DataTransDelegate;

@interface ViewControllerB : UIViewController
@property (nonatomic, weak) id<DataTransDelegate> delegate;
@end

// ViewController.m
#import "ViewControllerB.h"
#import "DataTransDelegate.h"

@interface ViewControllerB () <UITextFieldDelegate, DataTransDelegate>
@property (strong, nonatomic) UITextField *textFieldB;
@end

@implementation ViewControllerB

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.view addSubview:self.textFieldB];
    self.textFieldB.delegate = self;
}

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    [self didTextFieldChanged:textField];
}

- (void)didTextFieldChanged:(UITextField *)textField
{
    [self.delegate didTextFieldChanged:textField];
}

- (UITextField *)textFieldB
{
    if (_textFieldB == nil) {
        _textFieldB = [[UITextField alloc] initWithFrame:CGRectMake(100, 100, 100, 50)];
        _textFieldB.text = @"輸入文字";
        _textFieldB.backgroundColor = [UIColor redColor];
    }
    return _textFieldB;
}

@end

效果以下:

1

3.關於代理模式誤解


其實到目前爲止並無什麼異樣。關鍵是在你們對Objective-C的protocol使用上,通常是結合delegate使用的。大多數咱們稱這種模式是代理模式,可是我以爲delegate更像是一種委託模式,而非真正意義上的代理,代理是proxy,而委託是delegate。另外,代理模式中代理和被代理者都須要繼承並實現同一個接口Subject,而咱們使用delegate通常只須要讓其中一個類繼承並實現對應接口便可。

委託模式是軟件設計模式中的一項基本技巧。在委託模式中,有兩個對象參與處理同一個請求,接受請求的對象將請求委託給另外一個對象來處理。其實上面的viewControllerB包含了viewControllerA的引用這種作法就是委託模式。

好比咱們最爲熟知的UITableView,就是一個典型的委託模式,它將tableView的中不變的部分封裝起來,將常常變化的部分委託給用戶本身處理,因此說UITableView就是一個delegator,而遵循UITableViewDelegate的那個類就是delegate,因此咱們常常會在一個UIViewController中使用相似self.tableView.delegate = self這樣的表達;

你們可能會疑惑爲何還須要使用UITableViewDelegate這種相似於Java中的interface?我我的理解是由於這樣方便統一接口,接口統一了,方便了用戶,由於只須要實現這幾個接口就能夠了。

因此咱們能夠看到最開始提到的兩篇博客其實藉助了Objective-C中的protocol實現了的實際上是委託模式。

若是非要說委託模式和代理模式什麼關係的話,我以爲代理模式應該算是一種特殊的委託模式。

相關文章
相關標籤/搜索