Lead: The evolution of the architecture is to serve the continuous development of the business, the architecture can not be separated from the business, this is the most basic starting point. 58 City IOS clients as business volume and user volume continue to grow, architecture is also constantly challenged, what kind of architecture to adapt to these changes, the technical staff is also a big test. The architecture of the app has undergone a total of four phases: pure Native, introduction of the Hybrid framework, component of the underlying service, component of the business line, and the whole app component. First edition APP architecture
As early as 2010 58 with the birth of the first version of the IOS client, according to the traditional MVC pattern to design, pure Native page, the function is simpler, the architecture is the same, from top to bottom into the UI display, business logic, data access three layer, as shown in Figure 1. Like other companies in the same period, APP's starting point is to quickly seize the market and adopt a "Chanping" approach to development. Pure Native apps can meet business needs in the early days when the volume of business is not too large.
Fig. 1 Early APP architecture
Second Edition architecture Hybrid Framework Requirements
Because the Apple audit cycle is longer, business needs are increasing, some business if the development with Native, the workload of large input staff, also can not dynamically update, such as the broad categories of App, list, details page. In this case, using HTML5 is a more popular solution, resulting in a second version of the architecture, as shown in Figure 2, where the HTML5 page and Hybrid interaction Framework are added to the UI layer.
Figure 2 Architecture with Hybrid
At the time of the App designed to load HTML5 component is UIWebView, can only use this (there is no wkwebview), but the implementation of a number of problems that need to be resolved: How to Solve Hybrid Web and Native interaction problems, such as users click on a class No, you can adjust some of the Native methods to perform related page jump or write log. How to improve the loading speed of HTML5 pages, HTML5 page load to download some JavaScript, CSS and picture resources, is more time-consuming. Setting up caching
In order to facilitate the description, this article first describes how to improve the HTML5 page loading speed problem.
For some of the more frequently accessed pages, such as the large class list details, we used early in the HTML5 page. To speed up the rendering of these pages, you need to find ways to increase the load of resources. So how to achieve it. The first thing to think about is using caching, where we can build the resources of these pages into the APP with the release.
Because UIWebView will go nsurlcache this way when sending a request:
-(Nullable nscachedurlresponse*) Cachedresponseforrequest: (nsurlrequest *) request;
We can derive subclasses from Nsurlcache Wbhybrid
Component, Replication cachedresponseforrequest: Method, in which the built-in resources of the App are loaded, and the concrete loading strategy is shown in Figure 3.
Figure 3 Cache processing Process
Among them, H5viewcontroller for the HTML5 carrier page, Wbcachehandler for specialized processing of built-in resource classes for loading, locating, downloading, and saving built-in resources. In query in the URL, set the version number parameter cachevers as the identity of the resource cache, whose value is a numeric type, assuming that CACHEV1 is compared to the version number in the built-in resource, if cachev2>= cachev1, Represents the latest data in a built-in resource, returning data directly to the request, otherwise downloading the new built-in resources and judging by the cachev1-cachev2 difference, such as setting a threshold x, if the difference is greater than x, the built-in resource is old, returning nil to the request, otherwise the built-in data is returned. Let the request use the cached data first, and then use the new data the next time you start.
The built-in data is a bundle package, as shown in Figure 4, Cacheresources.bundle is the built-in package name, which contains an index file and several built-in data files in which each item in the index file is formatted as a key, version number, and file name.
Figure 4 Cache Packet Structure
To use a custom Nsurlcache, you must initialize the Wbhybridcomponent at App startup and set it up to replace the default Cache, note: This setting must be done before all requests, otherwise the settings will fail, but instead use the default Nsurlcache Example, we once trod this pit.
Urlcache initialization
Wbhybridcomponent *hybridcomp = [[Wbhybridcomponent alloc] Initwithmemorycapacity:mem_capacity Diskcapacity:disk_capacity Diskpatch:nil];
[Nsurlcache Setsharedurlcache:hybridcomp]
AJAX-based Hybrid framework
For the first question listed above, we are going to design a web/native Hybrid framework. The interaction mainly includes two parts, one is Native call the Web, this relatively simple, directly through the UIWebView stringbyevaluatingjavascriptfromstring: Execute a JS script, and return the execution result, this article mainly shares The method of Web tuning Native.
For Web tuning Native interaction, we use asynchronous AJAX to create a XMLHttpRequest object that executes send () to make an asynchronous request, Native intercept.
Xmlhttp.onreadystatechange = function () {
if (xmlhttp.readystate = 4 && Xmlhttp.status = =) {
//processing return number According to
}
;
Xmlhttp.open ("Get", "nativechannel://?paras= ...", true);
Xmlhttp.send ();
Because the XMLHttpRequest way is to do page partial refresh, and cannot be uiwebviewdelegate proxy-(BOOL) WebView: (UIWebView) WebView Shouldstartloadwithrequest: (nsurlrequest) Request Navigationtype: (Uiwebviewnavigationtype) NavigationType method intercept to, There's a new problem with the design, how to get Native to intercept AJAX requests.
After some research, we found the Nsurlcache for caching, and all the requests in UIWebView (including AJAX requests) would go nsurlcache. Therefore, we decided to use the wbhybridcomponent in the reuse cache to intercept AJAX requests, and the interactive design of the specific WEB tuning Native as shown in Figure 5.
Fig. 5 Hybrid Frame Process flow chart
Among them, H5viewcontroller is the HTML5 carrier page, Wbwebview is the UIWebView derived class. Asynchronous requests made by AJAX in the Wbwebview are intercepted in the wbhybridcomponent, then the corresponding Wbactionanalysis object is found by the DIC table in Wbhybridjshandler, and then in Wbactionan Alysis in the analysis of the asynchronous request passed over the protocol, take out the action field, and then according to the action value to find delegate that H5viewcontroller in the corresponding method.
AJAX request we agreed to: Nativechannel://?paras=<json protocol >,wbhybridcomponent at the time of interception to determine whether the URL is a nativechannel protocol header, if it is the Web Adjust the Native operation, need to carry on the follow-up Native processing, otherwise let go for other processing. <json Protocol > Simplified format as shown in Figure 6, this is used car large class page to click on the Web-used category of Native when the AJAX transfer of the protocol.
Figure 6 Web Tuning Native Transport protocol
Improved Hybrid framework
We have designed the Hybrid framework, by creating XMLHttpRequest objects to send AJAX requests to achieve the purpose of Web Native, but also to meet business needs, in a period of play an important role. But over time, the Hybrid framework exposes some problems, as shown below.
We found that there are a lot of memory leaks in the App, the culprit is UIWebView. Research found that uiwebview in the implementation of XMLHttpRequest asynchronous request, there will be memory leaks, the internet has been discussed this issue, reference blog: http://blog.techno-barje.fr//post/2010/10/04/ uiwebview-secrets-part1-memory-leaks-on-xmlhttprequest/.
Hybrid interaction and caching use Nsurlcache derived classes wbhybridcomponent to perform interception, which is also intended for caching purposes. Our Hybrid framework brings the two together, which can be a lot of pitfalls for later development and performance optimization work.
We maintained a Hybrid when we were interacting.
Create an IFRAME element
variframe= document.createelement ("iframe");
Set up an iFrame-loaded page link
iframe.src= "Nativechannel://?paras=<json protocol >";
Adds an IFRAME element to the DOM tree to trigger the request
Document.body.AppendChild (IFRAME);
After the request is triggered, remove the iframe
iFrame.parentNode.removeChild (iframe);
IFrame = Null;</json protocol >
Because the IFRAME is the entire page refresh, you can perform the Uiwebviewdelegate callback Method-(BOOL) WebView: (UIWebView) WebView shouldstartloadwithrequest: ( nsurlrequest) Request Navigationtype: (Uiwebviewnavigationtype) Navigationtype. We can intercept the WEB's tuning directly in this method, as shown in Figure 7 of the IFRAME processing process.
Fig. 7 Hybrid interactive mode of IFRAME
In the way of IFRAME, our App greatly simplifies the interactive process of the Hybrid framework, but also solves the problem of memory leak, coupling with cache function, consuming unnecessary memory space. Third version schema
As the business progresses, some new technology needs come in, for example, some of the basic modules can be separated from the app for multiple applications, need to provide a log SDK for the turn-around app, a Passport SDK to sign in for apps, and a customizable sharing group for other apps. Pieces and so on. App Split Component
At this point we urgently need to be at the engineering code level of the original App to split, component development, as shown in Figure 8.
Figure 8 Architecture of the third edition
We split the App into three tiers, from the bottom up to the underlying service tier, the underlying business layer, the main business layer: the components in the underlying service tier are business-neutral, and are called for by the upper level, each component as a project, such as a network, database, log, etc. Some of these components are used by other apps throughout the company, such as Le Gaozhi, and we provide an SDK that we can put together with the documentation on a code server for use by other teams. And all the Third-party libraries used in the App are concentrated in a dedicated project and are easy to update and maintain. The components in the underlying business layer are business-related, used by the main business layer, each component is a project, such as login, share, push, IM, etc., we put the Hybrid framework in the business layer. Where the login component we make the Passport SDK for the company other APP integration calls. Main business includes APP homepage, personal Center, Business Line business and third party access business, business Line business mainly includes release, Big class, List, details. Integrated Management Components
After splitting the project, it is the engineering integration, we use Cocoapods to integrate the project into a compile run and package, for each project configured. podspec file. When configuring Podfile files, when used for local development, we integrate through path, without the need to temporarily download engineering code, as shown below.
Pod proj,:p ath => ' ~/58_ios_libs/proj '
When Jenkins is packaged, we download the code in real time by Git:
Pod proj,: Git => ' git@gitlab.58corp.com:58_ios_team/proj.git ',: Branch => ' 1.0.0 '.
Gitlab Services for code management
We build a Gitlab service on the LAN to manage all the engineering code and set up the development team and the corresponding permissions. The Gitlab also enables submission code audits, code merge requests, and Engineering branch protection. Fourth Edition architecture
With the increase of the number of app users, the rapid growth of business lines, the new demand for the app, such as to speed up the rendering of large class list details page, it is necessary to Native the original HTML5 pages, and other lines to customize the list details and filter style. In the face of so many needs, obviously the original architecture has not met, it requires us to further improve the client architecture, the main business layer further split. main business layer split
We split the main business layer and split the overall architecture as shown in Figure 9, where each module is a project and a component.
Figure 9 Architecture of the fourth edition
We will be the home page, publishing, Discovery, Message Center, Personal Center and third party business from the main business layer split out into an independent project. The same will be real estate, second-hand, second-hand cars, yellow pages, recruitment and other lines of code from the original project inside the stripping out, each line of business independent of a project, the list and details are stripped out and Native, for the upper line of business lines customization features to provide interface.
When business lines are split we follow the following principles: there can be no dependencies between lines of business because our lines of business run independently throughout the development process and do not contain other line of business code. Non-business line engineering can not be dependent on each line of business, that is, all business lines are not integrated into the APP to be able to compile normally. Each line of business can retain the necessary dependencies on the non-business line project, such as the business Line dependency on the list component.
In the split process we also adopted a number of strategies, such as in the split recruitment line, the recruitment line from the integration of the project to delete, to compile, there will be a variety of compiler errors, indicating that there is a project on the recruitment line of business code to rely on. How to solve these dependencies. We are mainly to solve the interdependence, the recruitment line of business lines on the non-business line project must have a certain dependence, this is the first to retain, we have to solve other components may even be other lines of business dependence on recruitment. We have summed up, mainly in the following ways: Will rely on the file or method sinks, such as some files are not dedicated to the recruitment line of business, can be sunk from the recruitment to other projects, as well as some methods can also be sunk. Runtime, this way is more common, but also do not need to use all places, after all, its maintenance costs are relatively high. Category methods, such as the Personal Center component method Funa to invoke the method Funb in the recruitment component, but the FUNB implementation relies on recruiting internal code, in which case the personal center relies on the recruitment line of business, theoretically recruiting can rely on a personal center instead of relying on it in turn. The solution is to add a class to the personal center, such as ClassA, where the method Funb is added, but the implementation is NULL, if the return value can return a default value, and then add a classa category Classa+xx recruitment, the original recruitment method Funb into the classa+ XX, so if recruitment integration comes in, will execute the Funb method in Classa+xx, otherwise carry out personal center own Funb method. Jump Bus
The bus includes the UI bus and the service bus, the former mainly handles the jump between the pages of the components, especially in the main business layer, the UI bus is used more frequently. The service bus mainly deals with the service invocation between components, which is mainly about the jump bus. In the main business layer, the encapsulated components require page jumps through the UI bus, and we design a pattern for the general distribution center and the Sub distribution center to process, as shown in Figure 10.
Figure Ten UI Jump bus
The main business tier has a child distribution center within each component, and its processing logic is performed within each component, but some common interfaces must be implemented, and the child distribution center needs to be registered. When UI jumps are required within the component, the total distribution center is invoked, the jump protocol is passed into the total distribution center, and the distribution center finds the corresponding target component sub distribution center according to the component identification (such as line of business identification) in the protocol, and passes the jump protocol through the corresponding sub distribution center. The next jump is done by a child distribution center. This approach greatly reduces the coupling between components.
Jump protocols in the UI bus we used to use the JSON form, then the unified adjustment for the URL of the way, will be m, browser, push, external app set up and app jump unified processing.
The new Hop protocol URL format is as follows:
Wbmain://jump/job/list? abmark=markid¶ms=