你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 讀 Facebook App 頭文件的一些收獲

讀 Facebook App 頭文件的一些收獲

編輯:IOS開發基礎

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

讓視圖組件可以方便地配置

這個在 Facebook 的演講中也提到過,自定義的 UI 組件在初始化時可以傳一些數值來表示想要呈現的效果,就像 HTML 和 CSS 一樣,Dom 結構表示這是什麼,CSS 對該結構進行個性化定制。 Facebook 是通過 Struct 來做這件事的,比如:

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

好處是減少了代碼量,而且直觀,方便復用。

盡量使用組合,適度使用繼承

如果過度使用繼承,尤其是繼承層次過深,往往會帶來更大的維護成本。有新需求或需求變更時,會花很多時間在「是否需要在基類/子類增加一個方法」,「是否需要新建一個子類」等設計相關的問題上。而組合則沒有這個問題,大不了換一個組件。

不過 Objective-C 對於組合並沒有特別的支持,所以實現起來會略麻煩

@interface People {}
@property id  veachle;
- (void)move;
@end
@implementation People
- (id)initWithVeachle: (id )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 來實現的。

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

定義接口的過程也是梳理架構的過程,如果對架構理解不夠深刻,是很難將接口恰當地抽象出來的。很多人放棄使用組合,有一部分原因也是架構上的不合理。

組件的粒度也是個問題,過細會導致組件過多,組合的過程就會花去很多時間;過粗又導致組件臃腫,難以復用。

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

@interface FBResponseHandler : NSObject @interface FBPhotoViewController : UIViewController

這樣一眼就大概能看出來這個 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 + (id )instanceForSession:(FBSession *)arg1 providerMap:(FBProviderMap *)arg2;
@property(readonly, nonatomic) NSArray *supportedURLSchemes;
@property(readonly, nonatomic) NSArray *supportedKeys;
@property(retain, nonatomic) id  activeMenuItem;
@property(readonly, nonatomic) NSString *defaultIcon;
@property(readonly, nonatomic) NSString *ID;                                                                                                                                                         
- (UIViewController *)viewControllerForMenuItem:(id )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 也采用了這種方式,有一個 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) 不知道哪些是需要在 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:(id)arg1 error:(id *)arg2;
@property(retain, nonatomic) NSError *syncError;
@property(nonatomic, getter=isSyncing) BOOL syncing;
- (unsigned int)parse:(id)arg1 request:(id )arg2 error:(id *)arg3;
- (id )request;
@end

所以實體都是可以被解析和同步的,還自帶了一個 Request。

再來看看 FBEntity

@protocol FBEntity                                                                                                                                                   
+ (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 )arg3 error:(id *)arg4;
+ (id )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 + (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 這幾個開源類庫(可能還有更多)。

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

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved