iOS 開(kāi)發(fā)中的 Self-Manager 模式

Self-Manager?源于我們團(tuán)隊(duì)內(nèi)部的黑話,“誒?你剛?cè)サ膭?chuàng)業(yè)公司有幾個(gè) iOS 開(kāi)發(fā)啊?” “就我一個(gè)” “靠,你這是 Self-Manager 啊”

最近,這個(gè)思路被我們當(dāng)做了一種設(shè)計(jì)模式,即賦予一個(gè) Widget 更大的權(quán)利,讓其自己負(fù)責(zé)自己的事件。

舉個(gè)簡(jiǎn)單的栗子,這種負(fù)責(zé)展示頭像的視圖:

它的職責(zé)包括:

通過(guò)傳入的 URL,加載并展示頭像圖片

顯示一些附屬信息,比如大V的標(biāo)志

將用戶點(diǎn)擊頭像的事件傳遞給外層的 View Controller 跳轉(zhuǎn)到用戶信息頁(yè)面

于是乎這個(gè) Widget 的 API 可以長(zhǎng)這個(gè)樣子:

@interfaceFDAvatarView:UIView

// 假設(shè) VIPInfo 是某個(gè) Entity

- (void)configureWithAvatarURL:(NSURL*)URL VIPInfo:(id)info tapped:(void(^)(void))block;

@end

使用這個(gè)控件的人只需要調(diào)用這個(gè) configure 方法就可以配置入?yún)⒑褪录幚怼5S之而來(lái)的就是一些蛋疼的問(wèn)題:

configure 的調(diào)用者是 superview,上面的例子中也就是一個(gè) UITableViewCell,但 Cell 這層并不知道自己的 ViewController 是誰(shuí),于是乎還得向上一級(jí)傳遞這個(gè)點(diǎn)擊事件,直到能獲取到 NavigationController,然后 Push 一個(gè)用戶信息的頁(yè)面。

這個(gè) Avatar View 在 App 的各個(gè)地方都可能粗線,而且行為一致,那就意味著事件處理的 block,要散落在各個(gè)頁(yè)面中,同時(shí)也帶來(lái)了很多“只是為向上一層級(jí)轉(zhuǎn)發(fā)事件”的?“Middle Man”

為解決這個(gè)問(wèn)題,就需要給這個(gè) View 放權(quán),讓其自己 Handle 自己的事件,也就是?Self-Managed,為了不破壞 View 的純潔性,比較好的實(shí)踐是在 Category 中實(shí)現(xiàn):

@interfaceFDAvatarView(FDAvatarViewSelfManager)

- (void)selfManagedConfigureWithAvatarURL:(NSURL*)URL VIPInfo:(id)info uid:(NSString*)uid;

@end

實(shí)現(xiàn)時(shí)最好要調(diào)用 View 主類(lèi)提供的 API:

@implementationFDAvatarView(FDAvatarViewSelfManager)

// 為后一個(gè)頁(yè)面的創(chuàng)建增加了個(gè) UID 參數(shù)

- (void)selfManagedConfigureWithAvatarURL:(NSURL*)URL VIPInfo:(id)infoUID:(NSString*)UID{

[selfconfigureWithAvatarURL:URL VIPInfo:info tapped:^{

// 假設(shè) App 結(jié)構(gòu)是 Root -> TabBar -> Navigation -> ViewController

UITabBarController*tabBarControler = (id)[UIApplication.sharedApplication.delegate.window.rootViewController;

UINavigationController*navigationController = tabBarControler.selectedViewController;

// 創(chuàng)建用戶信息 View Controller

FDUserProfileViewController *profileViewController = [FDUserProfileViewController viewControllerWithUID:UID];

[navigationController pushViewController:profileViewController animated:YES];

? ? }];

}

@end

這里用到了類(lèi)似 AOP 的思路,添加了對(duì) App 層級(jí)的耦合,如果覺(jué)得這樣的耦合方式不妥的話,也可以封裝個(gè)全局方法去取到當(dāng)前頂層的 Navigation Controller。

這樣,F(xiàn)DAvatarView 的調(diào)用者只需要配置入?yún)?,其余的它自己全能搞定了,即?App 內(nèi)很多處出現(xiàn)頭像,邏輯代碼也只有一份。

接下來(lái)再來(lái)個(gè)例子:

這個(gè)點(diǎn)贊的按鈕功能上有幾個(gè)職責(zé):

顯示已有的點(diǎn)贊數(shù)

點(diǎn)擊按鈕后執(zhí)行一個(gè)小動(dòng)畫(huà),點(diǎn)贊數(shù) +1,同時(shí)發(fā)送網(wǎng)絡(luò)請(qǐng)求。

若已經(jīng)點(diǎn)贊,點(diǎn)擊執(zhí)行反向操作

若網(wǎng)絡(luò)請(qǐng)求發(fā)送失敗,則回退成點(diǎn)擊前的狀態(tài)

這個(gè)控件的 API 可以設(shè)計(jì)成這樣:

@interfaceFDLikeButton:UIButton

- (void)configureLikeStatus:(BOOL)likeOrNot count:(NSInteger)count animated:(BOOL)animated;

@end

因?yàn)槔^承自 UIButton,所以外部可以直接設(shè)置其 action,就不增加 tappedHandler 的參數(shù)了。外部在點(diǎn)擊事件中需要調(diào)用這個(gè)配置方法,播放點(diǎn)贊動(dòng)畫(huà),緊接著發(fā)送一個(gè)網(wǎng)絡(luò)請(qǐng)求,若網(wǎng)絡(luò)請(qǐng)求失敗,可以再次調(diào)用這個(gè) API 的無(wú)動(dòng)畫(huà)版本回滾狀態(tài)。但像上一個(gè)例子一樣,網(wǎng)絡(luò)請(qǐng)求和事件處理邏輯相同,但代碼卻分部在各個(gè)頁(yè)面中,于是給這個(gè) View 增加 Self-Managed 模式的 Category:

@interfaceFDLikeButton(FDLikeButtonSelfManager)

- (void)selfManagedConfigureWithLikeStatus:(BOOL)likeOrNot count:(NSInteger)count;

@end

偽代碼的實(shí)現(xiàn)如下:

@implementationFDLikeButton(FDLikeButtonSelfManager)

- (void)selfManagedConfigureWithLikeStatus:(BOOL)likeOrNot count:(NSInteger)count {

[selfconfigureLikeStatus:likeOrNot count:count animated:NO];

[selfaddTarget:selfaction:@selector(likeButtonTapped:) forControlEvents:UIControlEventTouchUpInside];

}

- (void)likeButtonTapped:(id)sender {

// +1 or -1 with animation

// Network request ^(NSError *error) {

//? ? if (error) {

//? ? ? ? rollback

//? ? }

// }

}

@end

記得面試題的那篇文章里還調(diào)侃說(shuō) “面試的時(shí)候聊聊設(shè)計(jì)、架構(gòu)挺好的,但別整出個(gè)往 UIButton 的子類(lèi)里搞網(wǎng)絡(luò)請(qǐng)求的奇葩結(jié)構(gòu)就行”,結(jié)果就被自己打了個(gè)臉。不過(guò)從設(shè)計(jì)上,Self-Manager 模式并沒(méi)有破壞原有的 MVC 結(jié)構(gòu),上面兩個(gè)例子中的 View 依然可以不耦合具體業(yè)務(wù)邏輯的單拿出來(lái)用。使用 Category 的方式把應(yīng)該寫(xiě)在 ViewController 中的代碼移動(dòng)到 View 的文件中,讓功能更加的內(nèi)聚。

程序的復(fù)雜度并不會(huì)因哪種酷炫的設(shè)計(jì)模式所減少,能做到的只是對(duì)復(fù)雜度的切分和控制,即:

讓一大坨惡心的代碼變成幾小坨不那么惡心的代碼。

讓惡心的代碼只在一個(gè)地方惡心。

Self-Manager 模式我們實(shí)踐的時(shí)候?qū)懫饋?lái)很開(kāi)心,拋磚引玉一下,希望也能解決你的苦惱。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容