Recently looking at some of the app architecture related articles, but also looked at the two different times of the Facebook architecture (2013 and 2014), so I would like to see the Facebook App's header file, there will be more gains, indeed, there are many. Due to the mistakes in the selection of IPA, the next 7.0 version of Facebook (the latest is 18.1), will be slightly outdated, but then a 18.1 to see, found that the change is not very big. Here are some of the information I get from the file (more than 200,000 rows, which is pretty tiring to browse)
Make the view components easy to configure
As also mentioned in the Facebook speech, the custom UI component can pass some numeric values during initialization to represent the effect it wants to render, just like HTML and CSS, where the DOM structure represents what this is, and the CSS customizes the structure. Facebook does this through a Struct, like
struct Fbactionsheetbuttonmetrics {cdunknownfunctionpointertype *_vptr$fbmetrics;_bool _initialized;float Leftmargin;float textleftmargin;float bottomseperatorsidemargin;float bottomseperatorheight;int detailMaxNumLines; Uicolor *titlecolor;//...};
The advantage is that it reduces the amount of code and is intuitive and easy to reuse.
use combinations as much as possible, use inheritance moderately
Excessive use of inheritance, especially if the inheritance hierarchy is too deep, often leads to greater maintenance costs. When there are new requirements or changes in demand, it can take a lot of time to design related issues such as "Whether you need to add a method to a base class/subclass and whether you need to create a new subclass." But the combination does not have this problem, the big deal changes a component.
But Objective-c has no special support for the combination, so it's a little cumbersome to implement.
@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
If there are a lot of methods like move that need to be handed out to an external object, it will appear redundant, although it is easier to maintain than inheritance.
The use of combination, the general will use "dependency injection", such as the Veachle here, do not need to specifically point out whether it is Bike or Car, as long as there is a move method can be, so it can be easily replaced, for people do not need to make any changes. In the objective-c is achieved through the protocol.
So Facebook defines a whole bunch of interfaces, including Delegate, DataSource and Protocol,viewcontroller with Protocol, and Delegate (such as Fbmediagalleryviewcontrollerdelegate), View/cell also has Delegate (such as Fbmediagalleryviewdelegate), and a variety of bits and pieces of Protocol, such as Fbdiscoverycardprotocol, Fbeventprotocol and so on.
The process of defining an interface is also a process of grooming the architecture, and it is difficult to abstract the interface properly if it is not understood deeply enough. Many people give up the use of the combination, the single theater part of the reason is also unreasonable structure.
The granularity of the component is also a problem, too much can lead to too many components, the combination of the process will take a lot of time, too coarse and cause the component bloated, difficult to reuse.
When the interface of a component is defined, it will probably work like this:
@interface Fbresponsehandler:nsobject <fbtestable, Fbreceiveddatabufferdelegate, fbresponsehandlerprotocol>@ Interface Fbphotoviewcontroller:uiviewcontroller <fbpagingviewdelegate, Fbpagingviewdatasource, Fbpresentableviewcontroller>
At this glance, you can probably see what this Class will probably do, and if a component is to be adjusted, it can be generalized globally by simply modifying one.
Moderate use of inheritance, can be easily maintained and convenient to achieve balance, such as Fbtableviewcontroller, Fbdialog, etc., custom components can be developed on their basis. The hierarchy of inheritance generally does not exceed 2 layers, such as Uitableviewcontroller <-Fbtableviewcontroller <-Fbfriendsnearbytableviewcontroller
Dependency Injection
As mentioned earlier, combinations are often used in conjunction with dependency injection, and Facebook is mainly through Fbprovider, Fbprovidermapdata, Fbprovidermap to implement dependency injection.
Provider generates an Object, such as when Cameracontrollerprovider calls the Get method, a Mncameracontroller instance is generated. Provider also has two subcategories of Singletonprovider and Blockprovider, which are used to generate a singleton, which is used in scenarios that require initialization of parameters.
Providermap and Providermapdata Some repetition, the relationship between them I did not clear, feel providermap like a Manager, registered a bunch of Provider, and then you can use the Provider ID to find the previously registered Provider.
Modular
Not only in the field of Cocoa development, but also in other programming fields, modularity is an ideal state, cohesion, low-coupling. Like a shell command, accept parameters or standard input, generate formatted standard output, and pass the pipeline to other command-line tools that support standard input.
But the reality of the scene is much more complex, modular implementation is more difficult. Facebook has a fbappmodule agreement
@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;
When initialized, pass in a fbsession (referred to later) and Providermap, then set the supported URL Schemes,keys (which is unknown), corresponding Menuitem,icon (for display in MenuItem) and ID
With module, nature and Modulemanager, its role is to register the module, when a URL comes over, you can traverse the module, to see if there are modules can handle this URL, if so, call the module's OpenURL: Method. Of course, you can also get the Module according to ModuleID.
Fbappmodule is a protocol,fbnativeappmodule is the implementation of the Protocol, so the specific modules inherit the class.
Navigation Management
In general, the uinavigationcontroller of the system is already in use, and if you need more freedom and a higher degree of customization, you can customize a navigation manager, and Facebook uses the Fbuinavigationcontroller ( PROTOCOL) for the management of custom navigation, the properties and methods are similar to the system. It has several implementations: Fbtariffednavigationcontroller, Fbswipenavigationcontroller, Fbcustomnavigationcontroller, Fbnavigationcontroller. The above-mentioned inheritance generally does not exceed 2 layers, here is the general situation, there are 3 layers.
MVVM
MVVM is an effective way to solve Massive view Controller, separate out a ViewModel as the data source of view, and handle some of the interaction of the view, and VC only need to associate ViewModel and view together. Usually with some kind of binding implementation, KVO or Reactivecocoa can, so ViewModel data changes can be automatically mapped to the View.
Facebook also uses this approach,www.bsck.org has a Fbviewmodel base class
@interface fbviewmodel:nsobject//omitted some properties and methods of little relevance @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 itself implemented a set of ViewModel update notification mechanism, because ViewModel are immutable, so can not change, then need to have a place to centrally manage these ViewModel, updates can be timely notification, Fbviewmodelcontroller should be doing this, there is a method-(void) _notifyviewmodel: (ID) arg1 didupdatewithchanges: (ID) arg2;. But Fbviewmodelmanager looks more appropriate, and the function of both is not too clear.
Fbviewmodelcontroller also has a Delegate, there are 3 main methods Didupdate[delegate][insert]viewmodel:, can do some post-mortem operations.
Builder Pattern
When defining a viewcontroller, it is often necessary to receive many parameters to Initwith: This form is not appropriate unless you can tolerate a 10-line method declaration. It is common practice to declare these parameters as property and then, after initializing the VCS, assign values to the properties and then use them in the viewdidload. There are a few questions: 1) do not know which is required in the new Visual theater 6080 Viewdidload before the setting, will appear to forget the settings of the phenomenon. 2) These attributes can be changed externally. 3) The code is not elegant enough.
Builder pattern is used to solve this problem, which is a bit like the factory model. Facebook also uses this pattern, such as having a fbmuserfetchstatus class that requires some parameters to initialize, and then has the Fbmuserfetchstatusbuilder class
@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
The final build method generates a Fbmuserfetchstatus instance, and with this builder you know which parameters can be set at initialization time.
Data Manager
This is the big play, so it looks slightly tired, a lot of things, it is possible to infer errors.
First, let's look at the entity class, first 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
So entities can be parsed and synchronized, with a Request.
And look at 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
Each Entity has a entityurl that might be used for synchronization? Datastale should be used to indicate whether the data is dirty, and if so, it may need to be synchronized. You can also request Collection.
Fbentitycollection is similar to fbentity, but many syncall/memberclass/allobjects these properties/methods.
Take a look at the data request, first of all, fbrequest, do not understand the specific function of this Class, because there is no URL, a Request without a URL can do? Then see the fbrequester, this looks like a data request class, has URL, ResponseHandler, connection state, delegate, etc. But this is only a single request, how to manage multiple requests, then saw Fbnetworker, it has +sharednetworker, Requestqueue, cancelrequests:, addrequest: So it is. Wait, why is there another fbnetworkerrequest down there? It looks like Fbnetworker's Delegate, but not sure.
To avoid URIs scattered around, Facebook also wrote a Category for Nsurl to unify the URI management.
@interface Nsurl (fbfoundation) + (ID) friendsnearbyurl;+ (ID) codegeneratorurl;+ (ID) Tagapprovalurlwithtagid: (ID) arg1 ; + (ID) tagapprovalurl;+ (ID) pokesurl;+ (ID) personexpandedabouturlwithfbid: (ID) arg1;//...
There is also a URL generation class, Fburlrequestgenerator, which holds Appsecret and appversion, and the resulting URL automatically takes these attributes.
In fact, there are many, I really can't see it down ...
Smarter views
We all know that Viewcontroller has a view, you can directly in this view on the Addsubview, it is because of this convenience, many create view of the code is also squeezed in the VC, it is really unsightly.
A better approach is to replace the VC view with a custom view and then separate the custom view. For example, covering the view when-loadview
@implementation Myprofileviewcontroller-(void) Loadview {self.view = [myprofileview new];}
You can redefine the type of view at the same time, such as @property (nonatomic) Myprofileview *view, so that the compiler understands that the type of view has changed.
Because there are-loadview methods in many VCs, it is possible to infer that this technique is used.
Fbsession
In the field of Web development, the Session is used to save user-related information, fbsession nature is no exception, but it is still a lot of content to save.
@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 ...
The Session can be saved locally, there is a status variable to identify whether it is valid (valid), is logged in (Hasuser), some settings of the user (these settings are saved locally), can be updated Accesstoken, with various Controller and Manager, so things are still quite a lot.
Here are two special methods that will be fire after use
Services
Services, as the name implies, provide some kind of service that is often irrelevant to the interface. At the directory level, the service is not in the module, which means that the two are independent, such as Fbtimelinemodule does not contain fbtimelineservice.
There can be dependencies between Services, and this is done through Startappservicewithdependencies: But it's unclear how the service itself declares what others depend on.
Style
The style of the App is a place that is easily overlooked, and development tends to look at the design and start writing, so it is easy to create a style that is not uniform and is not easy to adjust in the future.
Facebook defines styles by Category, with a simple example:
@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
So do not care about Fontcolor,margin,backgroundcolor, etc., directly to use can.
Other
In terms of directory structure, Facebook has Fbuikit, fbfoundation, Fbappkit, Module. Where Fbuikit and fbfoundation are business-agnostic and can be used on other apps, Fbappkit and Module are business-related.
Module comes with resources that can be seen as a mini app.
Using Egodatabase, Sdwebimage, ssziparchive, cocoalumberjack these open source class libraries (and possibly more).
Time and ability are limited, can only dig out this information, hope to bring some help.
Facebook App's header file will have more to gain