最近在看一些 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> 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 来实现的。

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

定义接口的过程也是梳理架构的过程,如果对架构理解不够深刻,是很难将接口恰当地抽象出来的。很多人放弃使用组合,光棍影院有一部分原因也是架构上的不合理。

组件的粒度也是个问题,过细会导致组件过多,组合的过程就会花去很多时间;过粗又导致组件臃肿,难以复用。

当组件的接口定义完之后,使用起来大概会是这样:

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

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

最新文章

  1. ADO.Net读取器获取数据库数据
  2. Schedule 学习
  3. 可嵌入式的动态http服务minihttp组件
  4. Android Content Provider基础
  5. codeforces Gym 100187J J. Deck Shuffling dfs
  6. PAT-乙级-1042. 字符统计(20)
  7. WCF编程系列(三)地址与绑定
  8. 史上最全的JavaScript工作笔记
  9. 2015暑假acm短训小结
  10. 自动红眼移除算法 附c++完整代码
  11. 如何發佈一個完整Node.js Module
  12. JS:onmouseover 、onmouseout
  13. SQLserver数据库反编译生成Hibernate实体类和映射文件
  14. Qt之QComboBox定制(二)
  15. Annotation 标注
  16. Learning Git by Animations
  17. 常见的几个js疑难点,match,charAt,charCodeAt,map,search
  18. List自定义排序
  19. webgote的例子(4)Sql注入(SelectGET)
  20. [BZOJ3142][HNOI2013]数列(组合数学)

热门文章

  1. ALTER AVAILABILITY GROUP (Transact-SQL)
  2. Python+selenium之测试报告(3)
  3. jacvaSe-LinkedList
  4. HDU 1007 Quoit Design最近点对( 分治法)
  5. 微信 Android版隐藏功能代码
  6. 使用脚本在Linux服务器上自动安装Kubernetes的包管理器Helm
  7. Memcached笔记之分布式算法
  8. 在DataGridView控件中启用换行
  9. python_104_面向对象总结
  10. 《队长说得队》【Alpha】Scrum meeting 4