Facebook App 的頭文件會有更多的收穫

最近在看一些 App 架構相關的文章,也看了 Facebook 分享的兩個不一樣時期的架構(2013 和 2014),因而就想一窺 Facebook App 的頭文件,看看會不會有更多的收穫,確實有,還很多。因爲在選擇 ipa 上的失誤,下了個 7.0 版的 Facebook(最新的是 18.1),會稍有過期,不事後來又下了個 18.1 的看了下,發現變更其實不大。如下是我從頭文件中獲取到的一些信息(20多萬行,瀏覽起來仍是挺累的)react

讓視圖組件能夠方便地配置

這個在 Facebook 的演講中也提到過,自定義的 UI 組件在初始化時能夠傳一些數值來表示想要呈現的效果,就像 HTML 和 CSS 同樣,Dom 結構表示這是什麼,CSS 對該結構進行個性化定製。 Facebook 是經過 Struct 來作這件事的,好比shell

struct FBActionSheetButtonMetrics {
CDUnknownFunctionPointerType *_vptr$FBMetrics;
_Bool _initialized;
float leftMargin;
float textLeftMargin;
float bottomSeperatorSideMargin;
float bottomSeperatorHeight;
int detailMaxNumLines;
UIColor *titleColor;
//...
};

好處是減小了代碼量,並且直觀,方便複用。編程

儘可能使用組合,適度使用繼承

若是過分使用繼承,尤爲是繼承層次過深,每每會帶來更大的維護成本。有新需求或需求變動時,會花不少時間在「是否須要在基類/子類增長一個方法」,「是否須要新建一個子類」等設計相關的問題上。而組合則沒有這個問題,大不了換一個組件。api

不過 Objective-C 對於組合並無特別的支持,因此實現起來會略麻煩session

@interface People {}
@property id <Veachle> veachle;
- (void)move;
@end
@implementation People
- (id)initWithVeachle: (id <Veachle>)veachle {
if (self = [super init]) {
self.veachle = veachle;
}
return self;
}
- (void)move {
[self.veachle move];
}
@end

若是有不少相似 move 這樣須要交給外部的 object 來作的方法,就會顯得冗餘,儘管如此,比起繼承來仍是更方便維護的。架構

使用組合的話,通常會使用「依賴注入」,好比這裏的 Veachle,並不須要特別指出是 Bike 仍是 Car,只要有 move 方法就能夠,這樣就能夠很方便地替換,對於 People 來講不須要作任何改動。在 Objective-C 裏是經過 protocol 來實現的。app

因此 Facebook 定義了一大堆的接口,包括 Delegate, DataSource 和 Protocol,ViewController 有 Protocol,也有 Delegate(如 FBMediaGalleryViewControllerDelegate),View / Cell 也有 Delegate(如 FBMediaGalleryViewDelegate),還有各類零零碎碎的 Protocol,如 FBDiscoveryCardProtocol, FBEventProtocol等。ide

定義接口的過程也是梳理架構的過程,若是對架構理解不夠深入,是很難將接口恰當地抽象出來的。不少人放棄使用組合,光棍影院有一部分緣由也是架構上的不合理。模塊化

組件的粒度也是個問題,過細會致使組件過多,組合的過程就會花去不少時間;過粗又致使組件臃腫,難以複用。工具

當組件的接口定義完以後,使用起來大概會是這樣:

@interface FBResponseHandler : NSObject <FBTestable, FBReceivedDataBufferDelegate, FBResponseHandlerProtocol>
@interface FBPhotoViewController : UIViewController <FBPagingViewDelegate, FBPagingViewDataSource, FBPresentableViewController>

這樣一眼就大概能看出來這個 Class 大概會有哪些功能,若是某個組件要做調整,只需修改一處,就能夠全局通用。

適度使用繼承,能夠在易維護和便利上達到平衡,好比 FBTableViewController, FBDialog 等,自定義的組件能夠在它們的基礎上進行開發。繼承的層次通常不超過2層,好比 UITableViewController <- FBTableViewController <- FBFriendsNearbyTableViewController

依賴注入

前面講過,組合每每和依賴注入搭配使用,Facebook 主要是經過 FBProvider, FBProviderMapData, FBProviderMap 來實現依賴注入的。

Provider 會產生一個 Object,好比 CameraControllerProvider 調用 get 方法後,會生成一個 MNCameraController 的實例。同時 Provider 還有兩個子類 SingletonProvider 和 BlockProvider,前者用來生成一個單例,後者用在須要初始化參數的情景。

ProviderMap 跟 ProviderMapData 有些重複,它們之間的關係我也沒有捋清,感受 ProviderMap 像是一個 Manager,註冊了一堆 Provider,而後能夠經過 Provider 的 ID 來找到以前註冊的 Provider。

模塊化

不光是在 Cocoa 開發領域,其餘的編程領域也同樣,模塊化是一個理想的狀態,高內聚,低耦合。像 shell 命令同樣,接受參數或標準輸入,生成格式化的標準輸出,經過管道傳遞給其餘支持標準輸入的命令行工具。

但現實場景要複雜的多,模塊化的實現也更加困難。Facebook 有一個 FBAppModule 協議

@protocol FBAppModule <NSObject>
+ (id <FBAppModule>)instanceForSession:(FBSession *)arg1 providerMap:(FBProviderMap *)arg2;
@property(readonly, nonatomic) NSArray *supportedURLSchemes;
@property(readonly, nonatomic) NSArray *supportedKeys;
@property(retain, nonatomic) id <FBMenuItem> activeMenuItem;
@property(readonly, nonatomic) NSString *defaultIcon;
@property(readonly, nonatomic) NSString *ID;
- (UIViewController *)viewControllerForMenuItem:(id <FBMenuItem>)arg1;

初始化時傳入一個 FBSession (後面會講到) 和 ProviderMap,而後設置支持的 url schemes,keys(具體做用未知),對應的 menuItem,icon(用於在 menuItem 顯示) 和 ID

有了 Module ,天然還有 ModuleManager,它的做用是註冊 Module,當一個 url 過來時,能夠遍歷 Module,看看是否是有模塊能夠處理這個 url,有的話,就調用該 Module 的 openURL: 方法。固然也能夠根據 ModuleID 來獲取 Module。

FBAppModule 是一個 Protocol,FBNativeAppModule 是對該協議的實現,因此具體的模塊都繼承該類。

導航管理

通常來講系統的 UINavigationController 已經夠使用了,若是須要更大的自由度和更高的可定製性,能夠自定義一個導航管理器,Facebook 使用了 FBUINavigationController (Protocol) 來實現自定義導航的管理,屬性和方法跟系統的差很少。 它有多個實現:FBTariffedNavigationController, FBSwipeNavigationController, FBCustomNavigationController, FBNavigationController。前面講過繼承通常不超過2層,這裏是通常以外的狀況,有3層。

MVVM

MVVM 是解決 Massive View Controller 的一個有效方法,獨立出一個 ViewModel 做爲 View 的數據源,以及處理 View 的一些交互操做,而 VC 只須要將 ViewModel 和 View 關聯起來便可。通常會搭配某種綁定的實現,KVO 或 ReactiveCocoa 均可以,這樣 ViewModel 的數據有變化就能夠自動映射到 View 上。

Facebook 也採用了這種方式,www.bsck.org有一個 FBViewModel 基類

@interface FBViewModel : NSObject
// 省略了一些相關性不大的屬性和方法
@property __weak FBViewModelManager *viewModelManager; // @synthesize viewModelManager=_viewModelManager;
@property(nonatomic) unsigned int viewModelSource; // @synthesize viewModelSource=_viewModelSource;
@property(retain, nonatomic) FBViewModelConfiguration *viewModelConfiguration; // @synthesize viewModelConfiguration=_viewModelConfiguration;
@property(readonly, nonatomic) unsigned int viewModelVersion; // @synthesize viewModelVersion=_viewModelVersion;
@property(readonly, nonatomic) NSString *viewModelUUID; // @synthesize viewModelUUID=_viewModelUUID;
@property(retain) FBMemModelObject *memModel; // @synthesize memModel=_memModel;
- (void)setNilValueForKey:(id)arg1;
- (id)initWithViewModelUUID:(id)arg1 viewModelVersion:(unsigned int)arg2;
- (void)setViewModelVersion:(unsigned int)arg1;
- (id)humanDescription;
- (void)loadPermanentDataModelObjectIDFromDataModelObjectID:(id)arg1 block:(CDUnknownBlockType)arg2;
- (void)didUpdateWithChangedProperties:(id)arg1;
@property __weak FBViewModelController *modelController;
@property(nonatomic) int loadState;
@end

Facebook 本身實現了一套 ViewModel 的更新通知機制,由於 ViewModel 都是 Immutable 的,因此沒法改變,那麼就須要有一個地方去集中管理這些 ViewModel,有更新時能夠及時通知到, FBViewModelController 應該就是幹這事的,裏面有一個方法- (void)_notifyViewModel:(id)arg1 didUpdateWithChanges:(id)arg2;。但 FBViewModelManager 看起來更合適,兩者的功能沒有太理清楚。

FBViewModelController 還有一個 Delegate,主要有3個方法didUpdate[Delegate][Insert]ViewModel:,能夠作一些過後的操做。

Builder Pattern

在定義一個 ViewController 時,每每須要接收不少個參數,以initWith:這種形式出現不太合適,除非你能容忍一個10行的方法聲明。一般的作法是把這些參數聲明爲 property,而後在初始化 VC 後,對這些 property 賦值,而後在 ViewDidLoad 裏使用這些 property。這樣作有幾個問題:1) 不知道哪些是須要在新視覺影院6080 ViewDidLoad 前設置的,會出現忘了設置的現象。2) 這些屬性能夠在外部被改動。 3) 代碼不夠優雅。

Builder Pattern 就是用來解決這個問題的,它跟工廠模式有點像。Facebook 也用到了這個模式,好比有一個 FBMUserFetchStatus 類,該類初始化時須要一些參數,因而就有了 FBMUserFetchStatusBuilder 類

@interface FBMUserFetchStatusBuilder : NSObject
+ (id)aMUserFetchStatusFromExistingMUserFetchStatus:(id)arg1;
+ (id)aMUserFetchStatus;
- (id)withIdentifiers:(BOOL)arg1;
- (id)withImageUrls:(BOOL)arg1;
- (id)withHasVerifiedPhone:(BOOL)arg1;
- (id)withCanInstallMessenger:(BOOL)arg1;
- (id)withHasMessenger:(BOOL)arg1;
- (id)withIsFriend:(BOOL)arg1;
- (id)withNickname:(BOOL)arg1;
- (id)withPhoneticName:(BOOL)arg1;
- (id)withName:(BOOL)arg1;
- (id)withUserId:(BOOL)arg1;
- (id)build;
@end

最後的 build 方法會生成一個 FBMUserFetchStatus 實例,有了這個 Builder 就知道有哪些參數是能夠在初始化時進行設置的。

Data Manager

這是重頭戲,因此看起來略累,東西不少,極可能推斷錯誤。

先來看看實體類,首先是 FBEntityRequest

@protocol FBEntityRequestParse
@optional
+ (BOOL)canParse:www.90168.org(id)arg1 error:(id *)arg2;
@property(retain, nonatomic) NSError *syncError;
@property(nonatomic, getter=isSyncing) BOOL syncing;
- (unsigned int)parse:(id)arg1 request:(id <FBRequest>)arg2 error:(id *)arg3;
- (id <FBRequest>)request;
@end

因此實體都是能夠被解析和同步的,還自帶了一個 Request。

再來看看 FBEntity

@protocol FBEntity <FBEntityRequestParse, NSObject>
+ (NSURL *)entityURLForFBID:(NSString *)arg1;
@property(readonly, nonatomic) NSURL *entityURL;
@property(readonly, nonatomic, getter=isDataStale) BOOL dataStale;
@property(retain, nonatomic) NSDate *lastSyncTime;
@property(retain, nonatomic) NSString *fbid;
@optional
+ (unsigned int)collection:(FBEntityCollection *)arg1 parse:(id)arg2 request:(id <FBRequest>)arg3 error:(id *)arg4;
+ (id <FBRequest>)collectionRequest:(FBEntityCollection *)arg1;
@property(readonly, nonatomic) FBEntityDownloader *entityDownloader;
- (NSSet *)parentEdges;
- (NSSet *)parentCollections;
- (void)entityInitializeWithFBID:(NSString *)arg1;
@end

每一個 Entity 都有一個 entityURL,或許能夠用來同步? dataStale 應該是用來表示數據是否 dirty,若是是的話,可能須要同步。 還能夠請求 Collection。

FBEntityCollection 跟 FBEntity 相似,不過多了 syncAll / memberClass / allObjects 這些屬性/方法。

再來看看數據請求,首先是 FBRequest,不太明白這個 Class 的具體功能,由於沒有 URL,一個沒有 URL 的 Request 能作什麼? 而後看到了 FBRequester,這個看起來是一個數據請求類,有 URL, responseHandler, connection狀態, delegate等。但這只是單個的請求,如何對多個請求進行管理呢,這時看到了 FBNetworker,它有 +sharedNetworker, requestQueue, cancelRequests:, addRequest: 因此就是它了。等等,爲何下面還有一個 FBNetworkerRequest ?看起來像是 FBNetworker 的 Delegate,但不肯定。

爲了不 URI 散落在各處,Facebook 還專門爲 NSURL 寫了個 Category 來統一管理 URI。

@interface NSURL (FBFoundation)
+ (id)friendsNearbyURL;
+ (id)codeGeneratorURL;
+ (id)tagApprovalURLWithTagId:(id)arg1;
+ (id)tagApprovalURL;
+ (id)pokesURL;
+ (id)personExpandedAboutURLWithFBID:(id)arg1;
// ...

還有一個 URL 生成類,FBURLRequestGenerator,該類保存了 appSecret 和 appVersion,生成的 URL 會自動帶上這些屬性。

其實還有不少,實在看不下來了···

Smarter Views

咱們都知道 ViewController 自帶了一個 view,能夠直接在這個 view 上 addSubview,正是因爲這個便利性,不少建立 View 的代碼也擠在了 VC 裏,實在是不雅觀。

更好的方法是替換 VC 的 view 爲自定義的 View,而後把這個自定義 View 獨立出去。好比在-loadView時覆蓋 view

@implementation MyProfileViewController
- (void)loadView {
self.view = [MyProfileView new];
}

能夠同時重定義 view 的類型,如@property (nonatomic) MyProfileView *view,讓編譯器明白 view 的類型已經變了。

由於看到了很多 VC 中都有-loadView方法,因此推斷可能使用了這項技術。

FBSession

在 Web 開發領域,Session 是用來保存用戶相關的信息的,FBSession 天然也不例外,不過它保存的內容還真是多呢。

@interface FBSession : NSObject <FBInvalidating>
+ (void)setCurrentSession:(id)arg1;
+ (id)_globalSessionForDebugging;
+ (id)DO_NOT_USE_OR_YOU_WILL_BE_FIREDcurrentSession;
@property(readonly) FBAPISessionStore *apiSessionStore; // @synthesize apiSessionStore=_apiSessionStore;
@property(readonly) FBSessionDiskStore *sessionDiskStore; // @synthesize sessionDiskStore=_sessionDiskStore;
@property(readonly) FBStore *store; // @synthesize store=_store;
@property(readonly) NSString *appSecret; // @synthesize appSecret=_appSecret;
@property(readonly, nonatomic, getter=isValid) BOOL valid;
@property(readonly) BOOL hasUser;
@property(readonly) NSString *userFBID;
@property(retain) FBViewerContext *viewerContext;
@property(retain) FBUserPreferences *userPreferences;
@property(retain) FBPreferences *sessionPreferences;
- (void)updateAccessToken:(id)arg1;
- (id)updateActingViewer:(id)arg1;
- (void)clearPreferences;
- (void)invalidate;
- (id)DO_NOT_USE_OR_YOU_WILL_BE_FIREDvalueForKeyRequiresUser:(id)arg1 withInitializer:(CDUnknownBlockType)arg2;
- (id)valueForKey:(id)arg1 withInitializer:(CDUnknownBlockType)arg2;
- (id)valueForKey:(id)arg1;
- (id)initWithAppSecret:(id)arg1 store:(id)arg2 apiSessionStore:(id)arg3;
@property(readonly, nonatomic) FBReactionController *reactionController;
@property(readonly, nonatomic) FBLocationPingback *locationPingback;
@property(readonly, nonatomic) FBAppSectionManager *appSectionManager;
@property(readonly, nonatomic) FBBookmarkManager *bookmarkManager;
// and many more...

Session 是能夠保存到本地的,有一個狀態變量用來標識是否有效(valid),是否已登陸(hasUser),用戶的一些設置(這些設置會保存到本地),能夠更新 AccessToken,還帶了各類 Controller 和 Manager,因此東西仍是挺多的。

這裏有兩個特殊方法,使用後會被Fire···

Services

Service 顧名思義,提供某種服務,每每跟界面無關。從目錄層級上看,Service並不在Module裏面,也就是說這兩者是獨立的,好比 FBTimelineModule 並不包含 FBTimelineService。

Service 之間能夠有依賴,這裏是經過startAppServiceWithDependencies:來實現的,不過不清楚 Service 自身如何聲明依賴哪些其餘的 Services。

Style

App 的 Style 是一個容易被忽視的地方,開發每每看着設計圖就開始寫了,這樣很容易形成樣式不統一,且未來調整起來也不方便。

Facebook 是經過 Category 來自定義樣式的,舉個簡單的例子:

@interface UIButton (FBMediaKit)
+ (id)fb_buttonTypeSystemWithTitle:(id)arg1;
+ (id)fb_buttonWithNormalImage:(id)arg1 highlightedImage:(id)arg2 selectedImage:(id)arg3;
+ (id)fb_buttonWithTemplateImage:(id)arg1;
+ (id)fb_buttonWithStyle:(int)arg1 title:(id)arg2;
@end
@interface UIButton (FBUIKit)
+ (id)fb_moreOptionsNavBarButton;
+ (id)fb_backArrowButtonWithText;
+ (id)fb_backArrowButtonWithRightPadding:(float)arg1;
+ (id)fb_backArrowButton;
@end
@interface UIButton (MNLoginFormAppearanceHelpers)
+ (id)phoneFormHeaderButton;
+ (id)singleSignOnButton;
+ (id)skipButton;
+ (id)formFieldButtonInvertedColors;
@end

這樣也不用關心fontColor,margin,backgroundColor等,直接拿來用便可。

其餘

從目錄結構上來看,Facebook 有 FBUIKit, FBFoundation, FBAppKit, Module。其中 FBUIKit 和 FBFoundation 是業務無關的,能夠用在其餘 App 上,FBAppKit 和 Module 是業務相關的。

Module 自帶資源,能夠當作是一個 mini app。

使用了 EGODatabase, SDWebImage, SSZipArchive, CocoaLumberjack 這幾個開源類庫(可能還有更多)。

時間和能力有限,只能挖掘出這些信息,但願能帶來些幫助。

相關文章
相關標籤/搜索