日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

block在美团iOS的实践

發布時間:2024/7/5 编程问答 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 block在美团iOS的实践 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

說到block,相信大部分iOS開發者都會想到retain cycle或是__block修飾的變量。

但是本文將忽略這些老生常談的討論,而是將重點放在美團iOS在實踐中對block的應用,希望能對同行有所助益。

本文假設讀者對block有一定的了解。

從閉包說起

在Lisp這樣的語言中,有一個概念叫做閉包(closure1),指的是一個函數以及它所處的詞法作用域(lexical scope2)構成的整體。為了理解閉包,我們首先來看看什么是詞法作用域。

所謂詞法作用域,顧名思義,是指一個符號引用的是其詞法環境中的變量,而無關程序在運行時的狀態。這么說可能有點抽象,讓我來看一段Common Lisp3代碼:

(defvar printer (let ((x 42))(lambda () (format t "~a" x))))

這里我們定義了一個變量printer,它的值是一個函數,這個函數會打印詞法作用域中的變量x(其值為42)。

現在我們來調用這個函數:

CL-USER> (funcall printer) 42

可以看到,我們調用了printer中存放的函數之后,打印出來的數字是42,跟我們的預期相符。

接下來再讓我們看一個可能會出乎意料的結果:

CL-USER> (let ((x 1))(funcall printer)) 42

我們在調用之前把x設置為了1,但是打印的結果仍然是42。

為什么?因為printer中存放的函數在被調用時所引用的變量位于其詞法作用域中, 即該函數被定義時所處的詞法環境中,所以程序在運行時設置的變量x對函數不起作用。

前面我們講過,所謂閉包,就是函數及其詞法作用域的合稱,具體到上例,那么匿名函數和x就構成了一個閉包,它會為函數保存一種狀態,有點類似于全局變量,不過除了那個匿名函數,其他函數無法訪問到x。

說了這么多,似乎跟block毫無關系?事實上,block為C帶來了閉包。

Block

Apple從OS X 10.6和iOS 4以后開始支持block,讓我們用C把上面的例子重寫一下:

#include <stdio.h>int main () {int x = 42;void (^block)() = ^() {printf("%d\n", x);};block();x = 1;block();return 0; }

編譯運行后得到的輸出同樣是兩個42。

到了這里,相信讀者對閉包已經有一個直觀的認識了,但是它有什么用?有什么好處?

設想如下場景,我們要請求一個URL,并以block的形式傳入回調函數,并在回調函數中用到剛才這個URL:

NSURL *someURL = …; [SomeClass getURL:someURL finished:^(id responseObject) {// process responseObject with someURL }];

這里網絡請求是異步的,所以當block中代碼執行時,getURL:finished:方法調用所在的棧很可能已經不存在了,但是因為回調block和someURL構成了closure,所以即使棧不存在,block仍然可以引用到someURL。

可能你會說,“我在block中增加一個NSURL類型的參數,把someURL傳回來不也可以實現同樣的目的嗎?”不妨設想如果我們在block中要引用的對象有10個之多,用參數列表傳遞明顯不再現實,用容器類或者專門定義一個類來傳遞雖然可以,但是前者沒有編譯器為我們檢查錯誤,后者則相當繁瑣。而利用閉包,可以輕易達到靈活性和簡潔性的平衡。事實上,美團客戶端就大量利用了閉包,在UI層發出請求,在回調中更新某些UI組件。

函數式編程4

在Lisp中,函數是一等公民,可以隨時創建、作為參數傳遞、作為返回值返回,Objective C在沒有block之前,沒有類似的機制,有了block,Objective C也就具備了函數式編程的能力,block是對象,有自己的ISA指針,可以隨時創建,作為參數傳遞,作為返回值返回。

先來看看block的經典用法:

[UIView animateWithDuration:0.25 animations:^{self.view.alpha = 1.0f;}];

UIView的animateWithDuration:animations:方法的第二個參數是一個block,它把跟動畫相關的操作封裝起來傳遞進去,以實現動畫效果。

現在讓我們發掘一下類似的用法:

[SAKBaseModel comboRequest:^() {[dealModel fetchDealByID:123456withFields:nilcompletion:^(MTDeal *deal, NSError *error) {...}];[orderModel fetchOrderByID:654321withDealFields:nilcompletion:^(MTOrder *order, NSError *error) {...}]; }];

這里我們為SAKBaseModel設計了一個類似于UIView的接口叫comboRequest,它會接受一個block作為參數,在這個block中發出的請求都會作為combo請求的一部分。如果dealModel或者orderModel的任何一個請求不是出現在block中,那么它就是一個普通的請求。這樣做的好處是dealModel和orderModel的接口不需要關心自己是不是屬于一個combo請求,調用者則可以靈活地調整代碼。

那么怎么實現這樣的接口呢?還是從UIView上獲取靈感。我們知道UIView有個方法setAnimationsEnabled:,實際上SAKBaseModel也可以有這么一個方法:setComboRequestEnabled:,而在comboRequest方法的實現中,在調用傳進來的block之前先setComboRequestEnabled:YES,調用完后再恢復為原狀態。相應的,在實際的model接口中,檢查comboRequest是否為YES,如果是,則把自己作為一個combo請求的一部分,否則正常發出請求即可。

Think Big

Lisp最強大的特性之一是condition系統,它可以分離異常的檢測、異常的解決和異常解決方式的決策,看一段示例代碼:

(define-condition network-timeout-error (error)((url :initarg :url :accessor url)))(defun try-again (condition)(let ((restart (find-restart ‘try-again)))(when restart (invoke-restart restart))))(defun deal-requester (deal-id)(handler-bind ((network-timeout-error #’try-again))(request-from-url (format nil “http://api.mobile.meituan.com/deal/~a” deal-id)(lambda (deal error)(if error(format t “error: ~a”, error)(process-deal)))))) (defun request-from-url (url finished)(let ((callback (lambda (response error)(if (network-timeout-error-p error)(error ‘network-timeout-error :url url)(funcall finished (parse-deal response) error)))))(restart-bind((try-again (lambda () (http-request url callback))))(http-request url callback))))

可以看到,condition系統對于代碼的分層提供了良好的支持,請求超時的錯誤在底層代碼被檢測到,在發出請求前注冊一個restart,而在業務層去決定要不要調用restart。

一直以來,C語言要實現優雅的異常處理就是一件不簡單的事情,而Objective-C雖然加入了try-catch支持,但是蘋果并不鼓勵使用,那么能否實現類似于condition系統這樣的異常處理機制呢?

答案是能。讓我們來看看接口設計:

typedef void (^RESTART)(id userInfo); typedef void (^HANDLER)(id condition);void restart_bind(void (^body)(), NSString *restartName, RESTART restart, ...) NS_REQUIRES_NIL_TERMINATION;void handler_bind(void (^body)(), Class class, HANDLER handler, ...) NS_REQUIRES_NIL_TERMINATION;void notify(id condition);RESTART find_restart(NSString *restartName);

如下圖所示,handler_bind首先在棧中注冊好handler,而restart_bind則在handler有效的環境中注冊restart,當有異常發生時,notify函數會在當前環境中尋找handler,找到后,控制會轉移到上層的handler代碼中,這時handler可以用find_restart在棧中搜索restart,找到之后可以調用,從而實現異常的恢復,做完這一切,控制回到notify發生的點繼續向下執行。

完整的代碼敬請期待美團iOS的開源項目。

有了SAKCondition,我們可以實現任意底層代碼的邏輯穿透到上層代碼,比如網絡層和UI層,使得上層代碼可以在不了解下層代碼實現細節的情況下調用恢復機制。事實上,美團的iPhone客戶端就是利用SAKCondition實現了美團賬戶的安全解鎖功能。

總結

block給Objective C帶來了無窮的可能性。本文只討論了美團iOS在實踐中的一些用法,更多想法還在等待挖掘。

總結

以上是生活随笔為你收集整理的block在美团iOS的实践的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。