最近在看一些 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 是解决 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:,能够作一些过后的操做。
在定义一个 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 就知道有哪些参数是能够在初始化时进行设置的。
这是重头戏,因此看起来略累,东西不少,极可能推断错误。
先来看看实体类,首先是 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 会自动带上这些属性。
其实还有不少,实在看不下来了···
咱们都知道 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方法,因此推断可能使用了这项技术。
在 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···
Service 顾名思义,提供某种服务,每每跟界面无关。从目录层级上看,Service并不在Module里面,也就是说这两者是独立的,好比 FBTimelineModule 并不包含 FBTimelineService。
Service 之间能够有依赖,这里是经过startAppServiceWithDependencies:来实现的,不过不清楚 Service 自身如何声明依赖哪些其余的 Services。
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 这几个开源类库(可能还有更多)。
时间和能力有限,只能挖掘出这些信息,但愿能带来些帮助。