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 这几个开源类库(可能还有更多)。

时间和能力有限,只能挖掘出这些信息,但愿能带来些帮助。

相关文章
相关标签/搜索