Preface After the publication of "iOS Application Architecture Talk", many people urged me to publish the second one quickly. This article was quite difficult to publish because there were so many things going on in the company and I had some personal matters, so I didn't have enough time to write a blog. Now it’s okay, the second article is out. When we start designing the architecture of the View layer, the App has often not yet started development, or the App has already released several versions, and then a very thorough reconstruction is needed. Generally, we will develop the View layer architecture at these two times. Due to the special nature of this timing, we must clearly realize that once the View layer architecture is implemented or finalized, there is very little room for modification after the App is released. Because it is most closely related to the business, even if it is slightly changed, the butterfly effect it causes may not be something that the business side can hold. In this case, when implementing this architecture, we must modify the code diligently and not be lazy. We must also have a full self-skeptical attitude and be careful when making decisions. The architecture of the View layer is very important. In my opinion, this part of the architecture is the most important part of the four aspects covered in this series of articles. Why do I say that? The View layer architecture is one of the factors that affect the business side’s iteration cycle Product managers will generate requirements very quickly, especially when the company is still in its early stages of entrepreneurship. In larger companies, product managers also like to dig big holes to show their presence in front of leaders, such as Alibaba. This results in a very heavy workload for business engineers. Under normal circumstances, it is unlikely that product managers will cut requirements. Therefore, as an architect, if there are some things that can be done or not in the architecture, it is best to do them if you can, and don't be lazy. This can help reduce the burden on the business side, and you can pay more attention to the business when writing code. When I communicate with some friends, they all complain more or less that their team's iteration speed is not fast enough, or that the iteration speed is unreasonably slow. I think the iteration speed cannot be increased as you wish. There are many factors that affect the iteration speed. The amount of tasks and the complexity of tasks in the first phase of the PRD will affect the extent to which the iteration cycle can be achieved. Putting aside these external factors, from the internal reasons that may cause the iteration cycle to fail to reach a reasonable speed, one of the reasons is likely that the View layer architecture is not well done, so that business engineers need to deal with too many extra things when completing a not-so-complex requirement. Of course, too many meetings and poor engineers are also internal reasons for the slow iteration speed, but this is not within the scope of this article. Also, overtime is not the right way to optimize the iteration cycle , um. Generally speaking, there are five main reasons for a poor View layer architecture:
These five factors will affect the efficiency of business engineers in meeting requirements, thus slowing down the iteration cycle. Other defects in the View architecture will also have more or less impact, but in my opinion, these five are the more important factors. If you think there are other factors that are more important than these four, please put them forward in the comments section and I will add them. I would like to emphasize the fifth point: the design of the architecture must be inherited. The architecture with inheritance will be very coordinated from the overall perspective. But the actual situation may be that one person leaves and another takes over. Even if the task handover is complete, it is inevitable that different people have different architectural ideas, which will affect the fluency of the entire architecture. To solve this problem, on the one hand, we should try to avoid single point problems and let the architect bring another person when doing the architecture. On the other hand, the architecture should be designed as simple as possible to smooth the learning curve of the successor. When I left Anjuke, I made a guarantee: all the code that came out of my hands will be guaranteed for life. So don't think that you will not care about anything after leaving. This is not only a question of professionalism, but also a question of whether you are confident enough in your code. Inheritance is very important for the View layer architecture because it is closest to the business and has the least room for change. So when CTOs, technical directors, and team leaders feel that the iteration cycle is not fast enough, you don’t have to rush to recruit new people. The Mythical Man-Month has long said that adding new people cannot completely solve the problem. At this time, if you can look back and see if the View layer architecture is unreasonable, fixing this is also one of the ways to optimize the iteration cycle. Well, as for the impact of the other three architectural solutions in this series on the iteration cycle, I think none of them has as high an impact as the View layer architectural solution on the iteration cycle, so this is one of the reasons why I think the View layer architecture is the most important. The View layer architecture is the underlying architecture that is closest to the business Although the View layer architecture is also considered the bottom layer, it is not that bottom layer. It has the widest connection with the business and has the deepest impact on the business layer code. When all the bottom layers are affected, the View architecture will cause the largest impact on the business layer. Therefore, once the View architecture is finalized, there is the least room for modification among all architectures. When we first consider the View-related architecture, we not only need to implement the functions, but also consider more things about the specifications. The purpose of formulating specifications is on the one hand to prevent the code of business engineers from corroding the View architecture, and on the other hand to ensure that there is a legacy. Following the specifications, it is always less likely to go wrong. In addition, the architect will have many things to consider at the beginning, and it is impossible to implement them all in the first version. For an App that has not yet been released, the first version of the architecture is often the minimum complete set of functions. In the development process of the second and third versions, the iteration task of the architecture is likely not just your job, and I believe that you may not be able to handle all of it alone. So you have to make an agreement with your collaborators. In addition, after the first version is released, business engineers will also have many modification opinions during use. Which opinions are reasonable and which are unreasonable must also be screened through the pre-agreed specifications to finally decide how to adopt them. The standards are not static. When to reject opinions and when to change the standards depends on your skills and experience. The above is the preface. What is this article about?
View code structure requirements Architects don’t just write SDKs and hand them over to business parties. Every company must have a set of code standards, and architects’ responsibilities also include defining code standards. In theory, defining code standards should be common knowledge, but I’m talking about it here because I need to add a standard for View. Strictly speaking, formulating code standards is not part of the View layer architecture, but it will have a greater impact on the future of the View layer architecture and is also something that architects need to consider when designing the View layer architecture. The importance of formulating View layer standards lies in:
In this section, I am not going to define a set of standards from scratch. Apple has a set of Coding Guidelines. When we set the code structure or standards, we must first comply with these standards. Then, I believe that everyone has their own set of standards in their respective companies. The specific standards actually depend on the experience of each architect. I just suggest that you add the following point on the basis of your own standards. The code of viewController should be similar to this: #p# The main points are as follows: All properties use getters and setters Don't initialize your view in viewDidLoad and then add it, as this will make the code ugly. Do only addSubview in viewDidload, then do layout in viewWillAppear ( errata 1 ), and finally listen to Notification in viewDidAppear. As for the initialization of properties, leave it to getters. For example:
This way, even if there are a lot of properties, the code can still be kept neat, and the initialization of the view is left to the getter. In short, try to avoid the following situations:
This approach is not clean enough, just throw it all into the getter. Regarding this approach, there is an article in Tang Qiao's technical blog that is different from the approach I advocate, which I will discuss in detail later. Put all getters and setters last Because a ViewController may have many views, just like the code sample above, if getters and setters are written in the front, the main logic will be dragged to the back, and others will have to go through a long list of getters and setters first, which is not good. Then ask business engineers to allocate the positions of code blocks in order when writing code, first life cycle , then Delegate method implementation , then event response , and then getters and setters . This way, later readers can save a lot of effort when reading the code. Each delegate should have the corresponding protocol name. Do not write delegate methods everywhere, but write them in one area. For example, the method set of UITableViewDelegate is written with #pragma mark - UITableViewDelegate . This has the advantage that when others read a Delegate implementation method that they are not familiar with, they only need to hold down the command and click the protocol name, and Xcode will immediately jump to the part of the code corresponding to the protocol definition of this Delegate, saving them from searching everywhere. Open a code area specifically for event response All button and gestureRecognizer response events should be placed in this area, not everywhere. Regarding private methods, under normal circumstances, ViewController should not be written It is not a delegate method, event response method, or life cycle method, but a private method. Yes, normally there are no private methods in ViewController. These private methods are usually used for small functions such as date conversion and image cropping. Such small functions can be written as a category or a module, even if the module has only one function. ViewController is basically the carrier of most business, and its code is already quite complex, so if you can avoid putting things that are not closely related to the business in ViewController, don't put them. Another point is that the function of this private method is only used by you at this time, but it may be used in other places in the future. Separating it from the beginning will facilitate future code reuse. Why do you ask for this? I have seen countless ViewControllers with messy code layouts, a delegate here and a getter there, and the ViewController code is usually very long, which gives people a headache just by reading it. By defining this specification, the ViewController can be clearly organized, and the business programmer can easily distinguish which ones are more suitable and which ones are not suitable in the ViewController. In addition, it can also improve the maintainability and readability of the code. About View layout This is the question that business engineers cannot escape when writing Views. Whether using Frame or Autolayout, if it is not carefully designed, the layout will be terrible. Using CGRectMake directly is not very readable, and you cannot know the positional relationship between views just by looking at the numbers. Using Autolayout is a little more readable, but the length of the generated Constraint is too long, and the code does not look good. For Autolayout, you can consider using Masonry, which will make the code more readable. If you are still using Frame, you can consider using this project. This project provides convenient methods related to Frame ( UIView+LayoutMethods ), which basically cover all layout requirements and are very readable. After using it, you can basically say goodbye to CGRectMake. Because Tmall has only recently switched to support iOS6, Tmall used Frame layout before. In the Tmall App, the layout of the home page and some pages of Faner uses these methods. Using these convenient methods can achieve twice the result with half the effort. This project also provides a convenient method for producing Constraints under the Autolayout solution ( UIView+AEBHandyAutoLayout ), which is much more readable than the native one. When I wrote this series of methods, I didn't know about Masonry. After knowing about Masonry, I took a look and found that Masonry is indeed powerful. However, although this series of methods is not as powerful as Masonry, it is enough. At that time, the View layout of the Anjuke iPad version of the App was all done by Autolayout, which used the methods in this project. The readability is very good. Allowing business engineers to use good tools to layout the View can improve their work efficiency and reduce the chance of bugs. Architects should not only care about those high-end content, but also provide business engineers with more convenient and easy-to-use tools to maximize the value of architects. When to use storyboard, when to use nib, and when to write views in code This issue is also mentioned in this article in Tang Qiao’s blog, and my opinion is basically the same as his. I would like to add a few more things here: iOS development with a certain scale (more than 10 people) has the following characteristics:
If you understand these three characteristics at a glance, you don't need to read the following explanation. If you want to discuss my inclination further, you can read my explanation below first and then talk about it. There are many authors for the same code file, and it is not uncommon for different authors to modify the same code at the same time. Therefore, the probability of conflicts when using Git for code version management is also relatively high. During iOS development, you will encounter two most annoying conflicts: project.pbxproj and StoryBoard or XIB . The readability of the contents of these files is very poor. Although Apple has made some optimizations to the StoryBoard file description in XCode5 (I am not sure if it is this version), it only improves the readability from very poor to very poor . However, StoryBoard often contains multiple pages, and it is basically impossible for one person to complete all of these pages. If another person, while operating StoryBoard, moves a page that does not belong to him for some purpose, such as adjusting the position for aesthetics. And another person also adjusts the position of another page in Storyboard because he wants to add a page. In this case, I can only say: Good luck . Look carefully, the specific page content has not been moved yet. But if you use code to draw the View, Conflict will still occur, but this kind of Conflict is much easier to resolve, you know. Requirements change very frequently, and product managers often have to make minor adjustments to existing code to meet requirements, or partially reuse existing code. I think it's not the product manager's fault that he has an idea at the moment. He may be forced to do so. For example, everyone will get involved in the design of the product. Everyone in the company, from the CEO to the grassroots employees, may comment on the product design. As long as they are not satisfied with a certain aspect of the product (most likely personal preference) and happen to be familiar with the product manager and can talk to him, they will make various suggestions. The product manager can neither avoid nor provoke it. Sometimes there is nothing he can do, um. But when it comes to engineers, this situation is very painful. Because sometimes the change is not only about the UI, but also the logic corresponding to the UI. The engineer will modify both files. The view you originally linked is no longer linked, and your corresponding outlets must also be deleted. If one of these two parts is not done, the App will crash after running it after compilation. It doesn't seem like a big deal, but it really affects your mood. In addition, if some code is reused, for example, if you want to put a View on a page on another page, the related operation is not as simple as copying and pasting, you have to link it again, which also affects your mood. There are many development tasks for complex interface elements and complex animation interaction scenes. If you want to make an animation in a StoryBoard-based project, it is very annoying. It is also very annoying to make several complex interface elements. Sometimes we attach a Custom View, but it is actually a blank View in StoryBoard. Another point is that when your layout has problems and needs to be adjusted, it is still difficult to find the problem, especially in the case of complex interface elements. So when it comes to the requirements of the View layer, I also recommend not using StoryBoard. It is just as easy to implement simple things with code, and it is easier to implement complex things with code than StoryBoard. So I advocate using code to draw views instead of storyboards. Is it necessary for the business side to derive ViewController uniformly? Sometimes, we derive our own ViewController from UIViewController to execute some common logic in order to record user operation behavior data or to unify the configuration page. For example, Tmall client requires all ViewControllers to inherit from TMViewController. This unified parent class has some settings for all life cycles of a ViewController. As for what settings are here, it is not important for this article. What I want to discuss here is, when designing the View architecture, if you want to achieve the purpose of unified settings or executing unified logic, is it necessary to use the method of derivation? I don't think it's necessary. Why not?
These two reasons are why I think it is unnecessary to use derivative means. If you understand both of them, then you don’t need to read the following. If you are still a little confused, please see below for me to explain the reasons in detail. Why does the use cost for the business side increase when derivation is used? In fact, not only the cost of use by the business side will increase, but also the maintenance cost of the architecture. So where does the specific cost come from?
The integration cost mentioned here is as follows: If the business party opens an independent demo and quickly completes a certain independent process, now he wants to integrate this existing process. Then the problem comes, he needs to change all independent UIViewControllers to TMViewController. So why not use TMViewController immediately from the beginning? Because if you want to introduce TMViewController, you have to introduce all the business lines and all the basic libraries of the entire Tmall App, because this parent class involves a lot of content that is only available in the Tmall environment. As the saying goes, if you want to simply inherit something, you will have to spend a lot of time setting up the environment before this small demo can run. For all parent classes in the business layer, they are easily entangled with other codes in the project, which makes the business side face a dilemma when developing: either to deal with all dependencies and then develop a demo based on the App environment (such as Tmall), or to write their own demo and modify the code according to the environment requirements. Both dilemmas here will bring costs and affect the iteration progress of the business side. I'm not sure if this situation will happen in your company, but I can give you a real example from my time at Alibaba: I've recently been developing a filter demo and related page flows, which will eventually be merged into the Tmall App. If you use the Tmall environment for development, it takes about 10 minutes to pod install all dependencies, and then after opening the workspace, you have to wait about another minute for Xcode to index before you can officially start working. I'd like to thank Zeping here, because he made a lot of optimizations on this basis, making this 1 minute much shorter than the original time. But if the Tmall environment is updated, you have to repeat the above process again, otherwise it is very likely that it will fail to compile. Please, I just want to make a demo, I don't want to make it so complicated.
Sometimes new business engineers may not remember that every ViewController must be derived from TMViewController instead of directly from UIViewController. New engineers cannot just follow Apple's native approach to do things, they need to learn extra things, for example: all ViewControllers must inherit from TMViewController.
Using inheritance as little as possible can improve the maintainability of the project. I have talked about the specific content in "Breaking Out of Object-Oriented Thinking (I) Inheritance". I don’t want to repeat what I said in that article here. In fact, for the business side, the main problem is the first integration cost, which is a long-term pain that will occur every time you want to do something. The second point is not so bad, it is a short-term pain. The third point has nothing to do with business engineers. So if we don't use derivation, what should we use? My suggestion is to use AOP. Before architects implement a specific solution, they must think through several issues before deciding which solution to adopt. What are these issues?
After answering these three questions one by one in order, the specific plan can be obtained. Let’s look at the first question first: What is the effect of the plan and what is the ultimate goal? The effect of the program should be:
In fact, it is to achieve the function of allowing the business to be perceived by the framework without actively catering to the framework through business code. In detail, there are two problems: the framework must be able to intercept the life cycle of ViewController, and the other problem is the definition of the timing of interception. For method interception, it is easy to think of Method Swizzling . Then we can write an example to add method interception for UIViewController when the App starts. This is one way. Another way is to use the load function of NSObject to automatically listen when the application starts. The advantage of using the latter is that this module can work as long as it is included in the project, without adding any code in the project. Then another thing to consider is that the original TMViewController (the so-called parent class) will also provide additional methods for the convenience of subclasses. Method Swizzling only supports operations on existing methods. If you want to expand methods, well, of course, use Category. I personally do not agree with the overuse of Category, but since Category is the most typical means of converting inheritance into composition, it is still suitable for use in this scenario. In addition, regarding the implementation of method interception by the method swizzling method, the industry already has a ready-made open source library: Aspects, which we can use directly. I have a very small demo that I can show you. This demo is just a finishing touch. I have also written some words in this demo. Architects, you can expand it based on the needs of your company's App. This demo does not include Category, after all, you still have to write Category yourself. Then this solution can complete all the tasks that can be completed by derivation, but at the same time allow the business party to use the native UIViewController directly without adding any code. Another thing to remind you is that the purpose of this solution is to eliminate unnecessary inheritance. Although it is not limited to UIViewController, it also has a scope of application. Where inheritance is applicable, you should still use inheritance honestly. For example, if you have a data model that is a set of models derived from a basic model, then you should still use inheritance honestly. As for when to use inheritance, I believe that all architects will be able to handle it well, or you can refer to the article I mentioned earlier to control the scale of judgment. #p# A lot of ideas about MVC, MVVM, etc. In fact, these are relatively common ideas. The three roles I mentioned in the opening are still the core: data manager, data processor, and data presenter . These various ideas are nothing more than formulating a specification that stipulates how these three roles should exchange data. But at the same time, these are also the most controversial topics, so I will sort out several mainstream ideas here so that you can have a better reference when you are working on the View layer architecture. MVC MVC (Model-View-Controller) is the oldest idea. It is so old that the Gang of Four classified it into a pattern in their book. Model is the data manager, View is the data displayer, and Controller is the data processor . Model and View are both deployed by Controller according to business needs, so Controller also has the function of data flow deployment. When I was writing this article, I saw this article published by InfoQ, which mentioned a pain point in mobile development: understanding the division of MVC architecture . I was unable to attend this symposium and could not express my personal opinion, so I can only write it here. In the field of iOS development, how should we divide MVC? There are actually two problems here:
Why do we get hung up on the MVC division issue in the field of iOS development? Regarding this, everyone may have different concerns, and I don’t know what everyone’s opinions were at the forum. But please allow me to guess: Is it because UIViewController comes with a View and controls the entire life cycle of the View (viewDidLoad, viewWillAppear...), and we all know in common sense that the Controller should not have such a close connection with the View, so everyone is confused about the division? Below I will give my opinion on this guess. In the server-side development field, the interaction between Controller and View is generally like this, such as Yii:
Here, the distinction between Controller and View is very clear. After the Controller has done its own work, it will hand over all the work related to View to the page rendering engine. The Controller will not do anything related to View, including generating View, which is done by the rendering engine. This is a difference, but in fact, the real difference between the concept of View on the server side and the concept of View on the Native application is that if we strictly divide the concepts, there is actually no View on the server side. Thanks to the HTTP protocol, the View we usually discuss is just a string used to describe the View (more substantially, it should be called data). The real View is the browser. . Therefore, the server only generates the description of the View. As for the appearance of the View, UI event monitoring and processing, the browser is responsible for generating and maintaining it. However, from the Native side, the tasks that originally belonged to the browser cannot escape being done by itself. So who is the most suitable person to do this? Apple's answer is: UIViewController . Given that Apple has made a lot of hard work in this layer, iOS engineers don't have to implement these contents themselves. Moreover, it puts all the functions on UIView, and makes UIView not only display UI, but also serve as a container object. Do you understand what I mean? Another identity of UIView is actually a container! The view that comes with UIViewController, its main task is to act as a container. If all its related names are changed to ViewContainer , the code will become like this:
Just changed the name, does it feel much clearer now? If we want to explain it in more detail, the server-side MVC we usually think of is divided like this: But in fact, the MVC division of the whole process is as follows: As can be seen from the figure, under this concept, our server-side development actually only involves the development of M and C. The browser, as a container of View, is responsible for the display of View and the monitoring of events. Then corresponding to the MVC division of the iOS client, it is like this: The only difference is that the View container is in the browser on the server side. In the entire website process, it is very reasonable to put this container in the browser. On the iOS client side, the View container is in the view of UIViewController. I also think that this choice made by Apple is very correct and wise. Because the relationship between the browser and the server is very loose, and they belong to two different camps, the server will generate a description of the View and hand it over to the browser for display. However, once any event occurs on the view, it is rarely passed to the server (the so-called Controller) (it can also be passed: AJAX), and everything is done on the browser side. Therefore, in this case, the View container is suitable to be placed on the browser (V) side. However, in the field of iOS development, although there are also ways to let View listen to events, this practice is very rare. Events are usually passed back to the Controller, and then the Controller makes separate arrangements. Therefore, it is very appropriate to place the View container in the Controller at this time. The Controller can easily change the container content due to different events. For example, when loading fails, the container content is replaced with the View of the failed page, and when there is no network, the container page is replaced with the View without network, etc. In the field of iOS development, what is the correct approach to MVC division? This question has actually been partially answered above, so the answer to this question can be regarded as a summary of the above questions. What M should do:
What C should do:
What V should do:
I answered these two questions by comparing with the server-side MVC division. The reason for doing this is that I know that many iOS engineers have switched from the server side. I was also a server-side developer before joining Anjuke. In the process of learning iOS, I also had doubts about the MVC division problem in the iOS field. The point of my doubt is the point I guessed at the beginning. If someone asks me how to divide MVC in iOS, I will answer like the above. MVCS Apple itself adopts this kind of architecture. As the name suggests, it is also a set of architecture derived from MVC. Conceptually, it splits the Model part and splits it into a Store. This Store is responsible for data access. But from the perspective of actual operation, it splits the Controller. This is a thin model solution. The thin model is only used to express data, and the storage and data processing are handed over to the outside. The premise of using MVCS is that it assumes that you are a thin model, and the storage and processing of data are all done in the Controller. So corresponding to MVCS, it is a split Controller from the beginning. Because the Controller is responsible for data storage, it will become very large, so the part of the Controller that is specifically responsible for accessing data is extracted and handed over to another object, which is the Store. After this adjustment, the entire structure becomes a true MVCS. #p# About fat model and thin model When I was interviewing and chatting with others, I found that not many people knew the concept of fat model and skinny model. About two or three years ago, the foreign industry had a very heated discussion on this, and the theme was Fat model, skinny controller . Now there are not many discussions on this aspect, but to this day, the industry has not yet reached a conclusion on which is better, so this is a controversy that has not been resolved in the industry. I rarely see any materials discussing this in China, so here I plan to add what is a fat model and what is a skinny model. And where their controversy comes from.
The fat model contains some weak business logic. The purpose of the fat model is that after the Controller gets the data from the fat model, it can directly apply the data to the View without doing any additional operations or with only very few operations. For example:
Converting timestamp into a string required for specific business is business code, which is considered weak business. After FatModel does these weak businesses, the Controller can become very skinny, and the Controller only needs to focus on strong business codes. As we all know, the possibility of change of strong business is much greater than that of weak business, and weak business is relatively stable, so it is no problem to stuff weak business into the Model. On the other hand, the frequency of recurrence of weak business is greater than that of strong business, and the requirement for reusability is higher. If this part of the business is written in the Controller, similar codes will be scattered everywhere. Once the weak business is modified (the low frequency of modification of weak business does not mean that there is no modification), this will be a disaster. If it is stuffed into the Model, if one place is modified, many places will be modified accordingly, and this disaster can be avoided. However, its disadvantage is that fat Model is relatively difficult to migrate. Although it only contains weak business, it is still a business after all. It is easy to pull out the carrot and bring out the mud when migrating. Another point is that the MVC architecture idea is more inclined to the Model is a layer, not an object. The things that a layer should do should not be given to an object to do. Finally, software will grow, and FatModel is likely to become fatter and fatter as the software grows, and eventually difficult to maintain.
The thin model is only responsible for expressing business data, and all businesses, regardless of strength, are thrown to the Controller. The purpose of the thin model is to write fine-grained models as much as possible, and then use various helper classes or methods to abstract weak businesses, while strong businesses are still handed over to the Controller. For example:
Since SlimModel has nothing to do with the business, its data can be handed over to any Helper or other object that can process its data to complete the business. It is highly independent during code migration, and rarely brings out mud when pulling out carrots. In addition, since SlimModel is just a data expression, it is basically free to maintain it. No matter how big the software is, SlimModel will not be that big. The disadvantage is that the Helper approach is not necessarily a good idea. Here is an article criticizing this. In addition, since Model operations appear in various places, SlimModel violates the DRY (Don't Repeat Yourself) idea to a certain extent, and the Controller still inevitably has code expansion to a certain extent. My attitude? Well, I will talk about it in the section on the core principles of our school . Back to the point, MVCS is an architectural idea based on thin models. It abstracts part of the data storage code among the many things that the original model has to do into a Store, which reduces the pressure on the Controller to a certain extent. MVVM MVVM was discussed a lot in the industry last year, both at home and abroad. Especially after the maturity of the ReactiveCocoa library, the signal mechanism of ViewModel and View finally had a relatively elegant implementation in iOS. MVVM is essentially an idea derived from MVC. The problem that MVVM focuses on is to reduce the tasks of the Controller as much as possible. Whether it is MVVM or MVCS, their consensus is that the Controller will become very large and difficult to maintain and test as the software grows . It's just that the premise of the two architectural ideas is different. MVCS believes that the Controller does part of the Model's work and needs to be split out and turned into a Store. MVVM believes that the Controller does too much data processing, so MVVM frees the data processing task from the Controller, so that the Controller only needs to focus on data allocation, and the ViewModel is responsible for data processing and lets the View respond to changes in the ViewModel through the notification mechanism. MVVM is built based on the architectural concept of fat Model, and then the fat Model is split into two parts: Model and ViewModel . I would like to make an additional explanation about this point of view: the fat Model first reduces the burden on the Controller, and then because the Model becomes fat, the ViewModel is split out on this basis. This is not inconsistent with the general industry perception that MVVM is essentially to reduce the burden on the Controller, because the fat Model also reduces the burden on the Controller. In addition, I said earlier that MVVM frees the task of data processing from the Controller , which is not inconsistent with the fact that MVVM splits the fat Model . To liberate the Controller, you first need to have a fat Model, and then split the fat Model into Model and ViewModel. So how should MVVM be implemented? This is most likely a question that most people struggle with. I intend to try to answer this question here based on my personal experience. Everyone is welcome to communicate in the comments section. In the iOS field, most MVVM architectures use ReactiveCocoa, but are iOS applications that use ReactiveCocoa based on the MVVM architecture? Of course not. I think many people have this misunderstanding. Some people I interviewed mentioned ReactiveCocoa and MVVM, but their understanding of this was so superficial that I couldn't help laughing. Well, I would give an example of not using ReactiveCocoa in the network layer architecture, but it feels a bit early for me to give it now. The key to MVVM is to have a View Model! Not ReactiveCocoa (Correction 2) What does ViewModel do? It turns RawData into a model that can be used directly by View. For example:
Here, we assume that RawData is longitude and latitude. Don't worry about the numbers I wrote casually. Then you have a map module. Converting all longitude and latitude arrays into MKAnnotation or its derived classes is a weak business for the Controller (remember, fat Model is used for weak business), so we use ViewModel to directly convert it into NSArray of MKAnnotation, and then give it to the Controller, which can use it directly. Well, this is what ViewModel does. Do you think it is very simple and does not show its advantages? Anjuke Pad application also has a map module, where I designed an object called reformer (actually ViewModel) specifically for this purpose. So where is the advantage of doing this? Anjuke has three major businesses: rentals, second-hand houses, and new houses. The mobile development team corresponding to these three businesses has three API development teams, and they work independently. This has resulted in a result: although the data content fed back to the mobile client by the three API teams is consistent, the data format is inconsistent, that is, the keys corresponding to the same value are inconsistent. But it is impossible to write three ViewControllers to display the map, so there must be an API data compatibility logic. I put this logic in the reformer, so the business process became like this: In this way, the originally complex MKAnnotation assembly logic is separated from the Controller, and the Controller can directly display the data returned by the Reformer. APIManager belongs to the Model, and the reformer belongs to the ViewModel. I will explain the specific things about the reformer in detail in the network layer architecture. The ViewModel role played by the Reformer at this time can greatly reduce the burden on the Controller. At the same time, the maintenance cost is also greatly reduced. The output of the reformer is always MKAnnotation, which can be used directly by the Controller. Then another point is that there is a business requirement to obtain nearby housing listings. The map API request can hold this requirement, so there is no need to change other places. Just replace the reformer when fetchDataWithReformer, and leave other things to the reformer. So what role should ReactiveCocoa play? MVVM is possible without ReactiveCocoa, and using ReactiveCocoa can better reflect the essence of MVVM. The example I gave above is only about the direction of data from API to View. The operation of View will also generate "data", but the "data" here is more reflected in expressing the user's operation. For example, if you input something, the data is text, and if you select a cell, the data is indexPath. So when the data goes from view to API or Controller, that's where ReactiveCocoa comes into play. We know that ViewModel is essentially the Model layer (because it is a part of the fat Model), so View is not suitable to hold ViewModel directly. So what should View do once it generates data? Throw a signal to ViewModel, who should throw it? ReactiveCocoa. The first purpose of using ReactiveCocoa in MVVM is that, as mentioned above, View is not suitable for directly holding ViewModel. The second purpose is that ViewModel may not only serve a specific View, and using a looser binding relationship can reduce the coupling between ViewModel and View. So what role does the Controller play in MVVM? Most domestic and foreign materials describe MVVM in this way: View <-> ViewModel <-> Model , which creates the illusion that MVVM does not need a Controller. Now it seems that the industry has begun to say that MVVM does not need a Controller . In fact, MVVM must involve the Controller, although MVVM weakens the presence of the Controller to a certain extent and reduces the burden on the Controller (which is also the main purpose of MVVM). However, this does not mean that the Controller is not needed in MVVM. The relationship between MMVC and MVVM should be as follows: (Source: http://www.sprynthesis.com/2014/12/06/reactivecocoa-mvvm-introduction/) View <-> C <-> ViewModel <-> Model , so it is not correct to say that after using MVVM, the Controller is no longer needed . Strictly speaking, MVVM is actually MVCVM. As can be seen from the figure, one of the main things that the Controller does between the View and the ViewModel is to bind the View and the ViewModel. Logically, the Controller knows which View should be displayed, and the Controller also knows which ViewModel should be used, but the View and the ViewModel do not know each other, so the Controller is responsible for controlling their binding relationship, which is why it is called the Controller . After talking so much, it all comes down to one sentence: On the basis of MVC, we split C into a ViewModel to be responsible for data processing, which is MVVM . Then, in order to make the View and ViewModel have a loose binding relationship, we use ReactiveCocoa, because Apple itself does not provide a binding method that is more suitable for this situation. In the field of iOS, KVO, Notification, block, delegate and target-action can all be used for data communication to achieve binding, but they are not as elegant as the RACSignal provided by ReactiveCocoa. If ReactiveCocoa is not used, the binding relationship may not be so loose and good, but it does not affect the fact that it is still MVVM. In the actual iOS application architecture, MVVM should appear in the iOS application architecture diagram of most startups or new apps of established companies. As far as I know, an iOS application under Yibao Payment has adopted the MVVM architecture as a whole. They have extracted an Action layer to install various ViewModels, which is also a relatively reasonable structure. Therefore, in MVVM, the Controller is responsible for the binding between View and ViewModel on the one hand, and is also responsible for conventional UI logic processing on the other hand. VIPER VIPER (View, Interactor, Presenter, Entity, Routing). I have not actually used VIPER, but I saw it in the 13th issue of objc.io. Whenever a new architecture appears or a new architecture that I am not familiar with before, I can be very sure that this guy must have disassembled some part of MVC (smirk, I have already explained the theoretical basis for this judgment in the first article). The fact is that VIPER has indeed disassembled a lot of things, except View. The two articles I mentioned are very detailed about VIPER, and you can understand them at a glance. But I am not sure about the pitfalls or controversies when using VIPER. If I have to write this section, I can only rely on YY, so I think I'll forget it. If any of you readers have adopted the VIPER architecture in your actual App or are very interested in VIPER, please bring it up in the comment section and we can discuss it. Our School's Heart Method A heavy sword has no edge, and great skill is not skillful. ---- The Return of the Condor Heroes This is a passage written by Yang Guo next to the Xuantie Heavy Sword when he was picking up the sword. I agree with this. The purpose of mentioning this passage is to tell you that when designing the View layer architecture, you don’t need to stick to the rules of MVC, MVVM, VIPER, etc. These are all moves. Once I tell you, you will know them, and then you can play them any way you want. But the mind method is not like this. The mind method is a great skill. It is simple to say, but whether you can keep the mind method in mind when designing the actual architecture and act according to the rules depends on the individual. The mindset of splitting The origin of Kung Fu is Shaolin, and the origin of MVC is architecture. ---- Casa Taloyum MVC is actually a very high-level abstraction, which means that countless architectural methods can be derived under the MVC system, but the core is that it must comply with the MVC specification. This sentence is not said by me, but I saw it in some English material, but time has passed and I can no longer find the source. I agree with this sentence. The architecture I use is strictly speaking MVC, but it also does a lot of splitting. According to the baptism of the previous sections, I believe you have also understood this truth: different splitting methods have given birth to various derivative architectural solutions (MVCS splits fat Controller, MVVM splits fat Model, VIPER splits everything), but no matter how diverse the splitting methods are, they are just moves. The specification of splitting is the mindset. In this section, I will talk about the mindset of splitting when I was doing View architecture.
In the field of iOS development, UIViewController carries a lot of things, such as View initialization, business logic, event response, data processing, etc. Of course, there are more that I can't list now, but we know that there is one thing that the Controller must do: coordinate V and M. In other words, no matter how it is disassembled, the coordination work cannot be disassembled. Then we can split the rest, such as UITableView's DataSource. Tang Qiao's blog has an article that mentions that he and another engineer argued for a long time about whether to split DataSource. Splitting DataSource should be considered a common practice. In simple applications, it may indeed look like just an array, but in complex situations, it may involve complex logic such as file content reading and data synchronization. The first section of this article advocates this practice, and I actually advocate it too. The previous article also mentioned a lot of things that can be disassembled, so I won’t move them here. You can go in and take a look. In addition to the content mentioned in this article, anything that is relatively large and dirty in the ViewController, as long as it is not the core logic of the Controller, can be considered to be disassembled, and then defined and designed as an independent module during the architecture.
The things that are disassembled according to the first principle are likely to be strongly business-related, which is sometimes unavoidable. But we must disassemble them in a nice way. The disassembled parts should be classified into a certain type of object, and then it is best to abstract a common logic so that it can be reused. Even if the common logic cannot be extracted, try to abstract a protocol to implement IOP. Here is an article about IOP, and you will understand its superiority after reading it. The third principle: Try to increase the abstraction level after splitting the modules In other words, the granularity of the split should be as large as possible, and the encapsulation should be more transparent. Tang Qiao said that all hiding will increase the complexity of the code unless it brings benefits . This makes sense to a certain extent. Hiding without benefits is indeed not good (laughs). Increasing the degree of abstraction is actually increasing the strength of encapsulation. Abstracting a responsible business into one that can be completed with very little input is highly abstract. Well, inheriting many layers, although this approach also increases the degree of abstraction, I don’t recommend it. I’m not sure whether the hiding that Tang Qiao mentioned here is the same concept as the encapsulation I mentioned, but what I want to advocate here is to increase the degree of abstraction as much as possible. The benefit of increasing the level of abstraction is that, for the business side, they only need to collect very little information (the minimum necessary and sufficient conditions) and do very little scheduling (the Controller is responsible for scheduling large modules, and the large modules then schedule small modules) to complete the task. This is the correct way to reduce the burden on the Controller. If the abstraction level of the split module is not high enough and the module requires many parameters from the outside world, then there will be a lot of code for collecting parameters in the Controller. If part of the parameter collection logic can be completed by the module, it can also help reduce the burden on the Controller. Otherwise, it will feel that the split is not clean enough because there will still be some unnecessary parameter collection logic in the Controller. If the granularity of the split is too small, the Controller will have to write a lot of scheduling code when completing the task, which is not good. The primary factor that leads to small split granularity is that the business itself may be relatively complex . Small split granularity is not bad. If it can be larger, it can be larger . If it is smaller, it is also fine. To deal with this situation, you need to use the strategy mode. For the case of small split granularity, let me give you a practical example. This example comes from a friend of mine who is working on the message sending module of a chat application. When the message is text, it is sent directly. When the message is a picture, you need to apply to the server to upload the resource first, obtain the resource ID, and then upload the picture. After uploading the picture, get the picture URL, and then send the message with the URL. At this time, we can split the modules into: data sending (called module A), resource upload application (called module B), content upload (called module C). Then to send a text message, the Controller can dispatch A. If you want to send a picture message, the Controller dispatches B->C->A. Assuming that there will be tasks to upload other types of messages in the future, they will also rely on modules D/E/F. This will be very annoying because the logic is complicated, and the Controller needs to dispatch more things to distinguish, and the Controller will be bloated. So how to deal with it? You can use the Strategy pattern. Let's analyze it again. What are the conditions that the Controller has in the initial situation to complete the task? It has all the data of the message and also knows the type of the message. So what does it need in the end? The result of the message sending: success or failure. The above is the final result we want to achieve. The Controller only needs to throw the message to the MessageSender, and then let the MessageSender do something and tell the Controller when it is done. So how to schedule the logic in the MessageSender? The MessageSender can have a StrategyList, which stores Blocks or Invocations (Target-Action) that express various logics. So let's first define an Enum, which specifies the scheduling logic required for each task.
Then the StrategyList in MessageSender is as follows:
This is how it is used in the Controller:
The MessageSender looks like this:
Then in an Invocation, it looks like this:
That's it. Even if the split granularity cannot be refined due to objective reasons, the complex judgment logic and scheduling logic can be extracted from the Controller, which really reduces the burden on the Controller. In short, if you can achieve a large granularity, try to achieve a large granularity. If you really can't do it, then it's okay to use Strategy to hold it. This example is a small granularity case. The large granularity case is too simple, so I won't give an example. #p# Design philosophy The architecture of the View layer is not only about how to reasonably split the MVC to reduce the burden on the UIViewController, but also about taking into account the cost of use for the business side. The best case scenario is that the business side knows nothing, and then they can run the code and get all the functions provided by the framework. For example, the spectator stands on Tiananmen Square are what I think is the best design because no one will notice it.
Inheritance is evil, try not to inherit. As far as I know, except for Anjuke's Pad App, which does not have an inheritance design for UIViewController at the framework level, other companies have more or less inheritance for UIViewController, including Anjuke iPhone app (I was powerless at that time, which shows how important it is to design the View architecture from the beginning). Some even inherit from UITableView, which is a very outrageous, cruel, and crazy thing. Although it is inevitable that we have to inherit from Apple's native objects in some cases, such as UITableViewCell. But I still suggest that you try not to add functions to native objects through inheritance solutions. The Aspect solution and Category solution mentioned above can both be used. Use Aspect + load to implement overload functions, and use Category to implement add functions. Of course, it is also okay to use Category to add properties. These solutions have covered all the functions of inheritance, and are very easy to maintain and more transparent to the business side. Why not do it? Not using inheritance may not be so intuitive in terms of thinking, but the benefits of not using inheritance are enough to offset the disadvantages of using inheritance. By the way, I want to correct the name of Category: the industry's attitude towards Category is rather ambiguous, and it has been advocated in many occasions (lectures, data documents) that Category should not be used as much as possible. What they say has some truth, but I think Category is the best solution provided by Apple to use collections instead of inheritance, but the design of Category also has high requirements for architects, so please use it reasonably. In addition, Apple also uses Category in many occasions to split an originally large object into different Categories according to different scenarios, thereby improving maintainability. I have already mentioned the benefits of not using inheritance. When it comes to iOS application architecture, there are two more benefits: 1. When the business side is doing business development or demo, they can leave the App environment or spend less time setting up the environment. 2. The functions are more transparent to the business side, which also meets the business side's first intuition when developing.
This is mainly to improve maintainability. In an object with a very large file, it is especially important to limit the layout of different types of code in the file. For example, when writing ViewController, the specification I set for the team before was that the first section was all getters and setters, and then the next section was the life cycle, and methods such as viewDidLoad were all here. Then the next section was the various Delegates to be implemented, and the next section was the event response, Button or GestureRecognizer were all here. Then the private method came after that. Generally speaking, if the split is done well, there will be no methods in the private method section of ViewController. Later, as time went on, I found that putting getters and setters at the beginning affected the reading too much, so I changed it to put them all at the end of ViewController.
The reason why Controllers become huge is, on the one hand, because the Controller carries business logic. The summaries of MVC (before the formal proposal of MVC, there were more or less people design it like this, so the designer of MVC is not very accurate) also carries business logic. Therefore, Controller is used to do this, which is natural. On the other hand, because in MVC, the definitions of Model and View are very clear, and few people will put something that belongs to M or V elsewhere. Then, in addition to Model and View, there are many ambiguous things left. These things are conceptually considered Controllers, and since M and V are so clearly defined, it is intuitive that these things are inappropriate to put them in M or V, so they plug into the Controller. It is precisely because of the above two reasons that the Controller has expanded. Let’s think about it carefully. It is relatively easy to split the Model and View. After the Controller expands, splitting will be extremely difficult. So if you can try to put what you can do elsewhere at the beginning, so that your part of the code that may be split in the future can be kept away from business logic at the first time. So we need to change our thinking a little: don’t stuff the ambiguous modules. It is better to stuff V or M or anything else than stuff the Controller, making it easier to split in the future. So regarding the choices of fat Model and thin Model that I pressed before, my attitude is more inclined to fat Model. Objectively speaking, after business expansion, the code scale is definitely indispensable. No matter how good your technology is or rich experience, the code can only be optimized at most. It will still be expanded. Moreover, after optimization, the code is often ugly, and using various tricks and tricks also comes at a cost. Therefore, the results of optimization for code volume often sacrifice readability or portability (genericity). Every magic always needs a pay, you have to make a trade-off. So since the expanded code or the code that may be expanded in the future, no matter which part of the MVC is placed, it will be split in the end. Since it will be split sooner or later, it is better to put it in the Model, so that it will be easier to split the fat Model in the future than to split the fat Cotroller. When I was still in Anjuke, the ViewController that the Anjuke Pad app carries the most complex business was less than 600 lines, and most other controllers were between 300-400 lines, which reduced the difficulty of getting started and maintenance complexity for those who took over the latter. The split items can be directly migrated to iPhone apps. Now looking at the ViewController on Tmall, there are thousands of lines at every turn, and I can't see it for a long time and I feel dizzy. After asking, everyone said that they are very accustomed to such code lengths and spread their hands.
The rank and status of an architect in the company are often higher than that of a business engineer, and the technical strength and experience of an architect are often higher than that of a business engineer. Therefore, you deserve to gain a higher position in the company, but a high position in the company does not mean that he has a high role in software engineering. Architects must serve business engineers, and they are the ones who command you rather than you command them. In addition, formulating specifications is one that restricts the code of a business engineer, but more importantly, this is actually to use your ability to help a business engineer avoid crises that he cannot foresee, so a high status has certain benefits. After all, summer insects cannot be spoken of, and sometimes it may not be explained. Therefore, what comes with high status is that they are more persuasive. But in software engineering, you must be humble and must consider more for business engineers. An architect who doesn't understand this principle is often complicated and difficult to use because he is only willing to do the core things, and he hopes to leave them to the business engineers for doing what he doesn't want to do. Sometimes he just makes a demo and then gives them to the business engineers. The business engineer becomes a job for him. However, an architect who understands this principle will design things very useful. The business party only needs to throw away few parameters and get the results. Such an architecture is called a good architecture. To give an example of saving images to local, one way is to provide an interface like this: - (NSString *)saveImageWithData:(NSData *)imageData , and the other is - (NSString *)saveImage:(UIImage *)image . The latter is better, the reason you think about it yourself. The humbler your attitude, the better you can design a good architecture. This is the last and most important thing in my design mindset. Even if your technical strength is not at the level of a big shot in the industry, as long as you maintain this mentality and do the design, you are already a qualified architect, and it will be very fast to become a big shot in the industry. A brief summary In fact, there are still three things to do in the architectural design of the View layer: code specifications, architecture patterns, and tool sets. Code specifications are of great significance to the View layer. After all, the View layer is very business-oriented. If the code layout is chaotic, it will be difficult for latecomers to take over and maintain. The specific choice of the architecture model depends entirely on the complexity of the business. If the business is quite complex, then you can use VIPER. If it is relatively simple, just change it slightly. Each architecture model that has become a fixed pattern may not be suitable for the corresponding business of each company, so the architects need to do some splits or changes according to the situation. There is generally no problem with splitting. When changing, just don’t mess up the three roles of MVC. Do whatever M should do, C should do whatever V should do, and don’t do anything randomly. This article has already mentioned what most architecture models should look like, but I think the most important thing is the mental method later. The model is just a trick. Only by familiarizing yourself with the mental method can you be very tricks . The view layer tool set mainly focuses on how to layout the View, as well as some specific Views, such as the search box with search prompts. This article only mentions the tool set for view layout. Other tools sets are relatively more dependent on the business of their respective companies. It is not difficult to implement or use ready-made CocoaPods. For small-scale or medium-sized iOS development teams, it is enough to do the above three points well. In large-scale teams, there is an additional problem to consider, which is the design of cross-business page call schemes. Design of cross-business page call scheme Cross-business page call means that when there are multiple services such as A, B, etc. in an App, B may need to display a page of A, and A may also call a page of other services. In a small-scale app, we will directly import a ViewController of other services and then push or present, which will not cause a particularly big problem. However, if the app is very large and involves a lot of services, then importing it directly will cause problems. It can be seen that cross-service page calls in apps composed of multiple services will lead to horizontal dependence. So what will happen if you don’t try to solve horizontal dependence like this?
Of course, if the app scale is particularly small, the impact of these three points will be particularly small. However, in a large-scale team like Alibaba, a large-scale app like Tmall/Taobao will be very scammed once it encounters even one of the things in it. So how should we deal with this issue? Let dependencies sink. How to make dependencies sink? Introduce Mediator mode. The so-called introduction of Mediator mode to make the dependency sink. In essence, every time a page is called, another page is summoned through an intermediary, so that as long as each business depends on this intermediary, the role of the intermediary can be placed at the bottom of the business layer, which is the dependency sink. When service A needs to call a page of service B, the request is handed over to Mediater, and then Mediater obtains an instance of service B page through some means and returns it to A. In the process of implementing this mechanism, there are the following problems that need to be solved:
This looks very similar to the URL mechanism we used in web development. It sends a Get or Post request, and the CGI call script distributes the request to an Action under a Controller, and then returns the HTML string to the browser for parsing. Apple itself has also implemented a cross-App calling mechanism, which also operates based on the URL mechanism, but the problem it wants to solve is data exchange and page calls across Apps. The problem we want to solve is to reduce the coupling degree of various businesses. However, we cannot directly use Apple's native mechanism because this mechanism cannot return object instances. We hope to get object instances, which can not only make cross-business page calls, but also cross-business function calls. In addition, we hope that our Mediater can also be compatible with Apple's native cross-app calls, which can help business parties save some of the development volume. As far as I know, a certain iOS app under AutoCad (I don't remember which app it was for a long time. If you are an iOS developer of AutoCad, you can add it in the comments section.) This page call method is adopted. Tmall is also using this mechanism, but due to historical reasons, there is a situation where new and old versions are mixed, so it has not yet been able to play its due role well. Well, I want to ask my classmates who want a demo, I can tell you very generously, no. But I plan to take the time to write one, and now I have done nothing except to think of the name Summon, haha. About Getter and Setter? I'm more used to writing the "private" properties of an object in the extension, and then the initialization of these properties is all in getter. Outside init and dealloc, there will be no writing methods like _property . That's it:
Tang Qiao said that his favorite method is to use _property , and then the initialization of _property is done through [self setupProperty] . Judging from the above code just now, it is just to call an additional setup method in viewDidLoad, and the method I recommend is to go directly to getter without adjusting an additional setup method. Well, how to say it, both methods can actually meet the requirements. But from another perspective, the reason why Apple chose to make [self getProperty] and self.property common to each other has clearly expressed Apple's tendency: I hope that each property is obtained through the getter method. As early as 2003, Allen Holub published an article "Why getter and setter methods are evil". Since then, the industry has had various controversies about this. Although it started with Java, it has also participated in various languages later. Then, although there is little discussion on this issue now, it is still in a state of inconclusiveness. The situation of setter is relatively complicated and is not the focus of my section. I still mainly talk about getter. From the perspective of objc design, Apple designers are more inclined to getter is not evil. I think there are many reasons for gettingter is evil , either big or small. As the debate progresses, everyone gradually focuses on one reason: Getter and Setter provide a way to allow external modification of the internal data of the object. This is evil. Under normal circumstances, the variables that an object owns itself should only care about. Then we returned to the iOS field, and objc also faced this problem, even more serious: objc does not have as strict private concept as Java. But in actual work, we are not very good at operating variables that are not in the header file, which is prohibited from standardizing. The reason why I think getter is not evil can also be focused on one: high degree of encapsulation . Getter is actually a factory method. With getter, business logic can focus more on calling without worrying about whether the current variable is available. We can think about it. Suppose a ViewController has 20 subviews to be added to the view. The initialization code of these 20 subviews is definitely not escaped. Where is it better to place it? It is better to place it anywhere than put it in addsubview. I personally think the best place is to put it in getter. After combining the singleton pattern, the code will be very neat, and the production and use places are well distinguished. So for iOS, I still think using getter is better, because the places where evil are basically avoided on iOS, and you can enjoy the places where evil is not evil, which is still good. #p# Summarize To build a View layer architecture, we mainly start from the following three aspects:
Of course, you will encounter many other problems. At this time, you can refer to the mental method proposed in this article. In the design of the cross-business page call scheme mentioned later, you can also see some of my shadows. For iOS clients, it does not have so many unofficial general frameworks like other languages such as Python and PHP. The objective reason is that Apple has done a lot of things for us and has done a lot of effort. Based on Apple’s already doing so many things, when architects want to make plans for the View layer, it is best to try to abide by Apple’s existing specifications and design ideas, and then based on their past experience in developing iOS, reduce the burden on business parties when developing business and improve the maintainability of business code. This is the biggest goal of the View layer architecture solution. 2015-04-28 09:28 Supplement: About AOP AOP (Aspect Oriented Programming), oriented towards slice programming, is also one of the XX-oriented programming terms, but it has nothing to do with object-oriented programming we are familiar with. What is slice? To complete a program, there must be some steps, 1, 2, 3, 4. We can think of each step decomposed here as a slice. What is slice-oriented programming? You can plug some code into the gaps of each slice. When the program normally performs 1, 2, 3, and 4 steps, you can run to the code you stuffed in. Then you write these codes to be oriented towards slice programming. Why is slice-oriented programming occur? If you want to do your own thing in each step, you can achieve the goal without using AOP. Just stuff the code between the steps directly. However, the facts are often very complicated. Just stuff the code directly into it. The main problem is that the code stuffed in is likely to be code that has nothing to do with the original business, and multiple services are doped in the same code file, which will bring about coupling between businesses. In order to reduce this degree of coupling, we introduced AOP. How to implement AOP? AOP generally needs to have an interceptor, and then before and after each slice is run (or wherever you want), the jointpoint is thrown outside by calling the interceptor method. When you get the jointpoint outside, execute the corresponding code. In the field of iOS development, objective-C's runtime provides a series of methods that allow us to intercept calls to a certain method to implement the function of an interceptor. This method is called Method Swizzling. Aspects uses this method to implement interception of methods in a certain class and an instance. In addition, protocol can also be used to implement the interceptor function, and the specific implementation plan is like this:
This comparison with Method Swizzling has an additional advantage that you can provide more information to the interceptor implementers through the interceptor, so that external implementations can better understand the current slice situation. In addition, you can divide the slices more finely. The slice particle size of the Method Swizzling is functionally granular, and the slice particle size of the interceptor implemented by yourself can be smaller and more finer than the function. The disadvantage is that you have to write the code that calls the interceptor method at every insertion point (laughs), and it will be easier to implement AOP through Aspects (essentially Mehtod Swizzling). 2015-4-29 14:25 Supplement: About where to write Constraints? After the article was published, many people had a lot of opinions on Errata 1, so I felt it was necessary to make a supplement here. The process was very complicated, and this article was already very long, so I just said the result. Apple pointed out in the documentation that updateViewConstraints is the place used to make add constraints. But there is an answerer here that updateViewConstraints is not suitable for adding Constraints. Combining the various tests and documents of my brothers who are concerned about this issue in the comments section, I now think it would be better to open a layoutPageSubviews method in viewDidLoad , and then create Constraints in this and add it. It's like the following:
Finally, I would like to thank everyone in the comment area for caring about this issue, giving their own opinions, and even testing it yourself and then telling me the results: @fly2never, @Wythe, @wtlucky, @lcddhr, @Li Xinxing, @Meigan Fang, @Anonymous, @Xiao Moch. This approach is something I think may be more appropriate at present. Of course, other students are welcome to continue to express their opinions and let us discuss it. Errata After reading this article, my former colleague @ddaajing asked me the following two errata and many article questions. I would like to express my gratitude to him here: Errata 1: In fact, changing the UI element in viewWillAppear is not very reliable. Autolayout occurs after viewWillAppear. Strictly speaking, it is usually not modified to view position, but is used to update Form data. Change the position can be placed in viewWilllayoutSubview or didLayoutSubview, and it is safe to set autoLayout after viewDidLayoutSubview determines the UI position relationship. In addition, viewWillAppear will be called every time the page is about to be displayed. Although viewWillLayoutSubviews is called in lifeCycle after viewWillAppear, it will only be called when the page elements need to be adjusted, avoiding the repeated addition of Constraints. Errata 2: MVVM must have a ViewModel and the signal notification effect brought by ReactiveCocoa, which is implemented in ReactiveCocoa, such as RAC. In addition, the use of ReactiveCocoa can implement the MVVM mode more elegantly because of the existence of RAC and other related macros. Just like its name, Reactive-responsiveness, which is also an important aspect of distinguishing the C and MVP of the MVVM VM from the C and MVC. |
<<: OS X 10.11 will have a control center, iOS 9 will support older A5 devices
>>: Mobile IM development: technology selection and common problems
Today I will mainly share my experience and opera...
On April 19, at the Shanghai Auto Show, SAIC Moto...
When I work on growth for a project, it is like a...
2015 has just begun, and the WeChat JS SDK was re...
On June 15, the flame of the Hangzhou Asian Games...
Recently I saw an activity called “100-Day Progre...
The “predecessors” of recommendation systems In 2...
Preface Flutter is a cross-platform solution laun...
If your issue is that your ads aren't serving...
Since 2021, with the outbreak of the global COVID...
1. Learn to analyze competitive products Competit...
(October 12, 2024, Shanghai) Today, the "Del...
"Science Popularization China - Back to Scho...
If you want to do search engine bidding, you must...
On July 15, Apple's online store showed that ...