Got 1.2K stars on github with terrible architecture. How?
I want to share a fairly ordinary, but significant history. The idea of the project appeared 3 months ago, for 1 month it was implemented and now for two months as a project it periodically hangs in the top of GitHub, got into what specialized news resources are possible, and even got into the digest in the article “Top 5 Libraries of April” You might think that I am praised, but no. This background is needed for deeper dissonance. I want to talk about ... architecture . Yes, I know, I know, “ how much is possible ” and “ what does he allow himself ”. But I will speak not so much about patterns, as about the approach to their use. I was looking for such articles and love them. You will find more examples of singleton and factory than errors when a new version of Swift comes out, and we will talk about a generalized approach using the example of my library .
Before diving - read the instructions.
I would not say that I am an iOs-developer of some extraordinary level, so I ask you to treat everything said critically . I am sure there are people more experienced than me, for them everything is obvious. But for a lost soul, yesterday sorting arrays, it would be good to be objective. I will try.
Batten the hatches! Dive!
The project simplifies work with permissions. In addition, it increases the conversion to receive the same notifications. Who does not like the beautiful dialog box?)
Basic requirements for the project: ')
Easy implementation
Simple use
Convenient customization and expansion (if you suddenly want to add new permissions or visuals)
Otherwise, I simply tried to do well (for example, the ability to change business logic was not a priority, but was taken into account)
And now let's argue. This is generally useful stuff. To make the project simple, you need to have a simple interface and not load the programmer with the implementation under the hood. In this, I was inspired by the approach from Appodeal. In general, you need to have one entry point. Those. configured the object with 2 permissions, and then requested them. It should be as simple as it sounds!
Immediately the devil on the left? shoulder whispers: “Singleton ...”. And the first few days it seemed to me a great solution, I configured it in AppDelegate, and show the controller wherever you want.
But there were more problems:
It is rarely necessary to keep a dialog box in mind.
After obtaining all the permissions, you definitely do not need to keep it in memory
It is unlikely that you will need to request the same permissions on different screens.
In general, the pattern had more minuses than pluses. Here I want to draw attention to the fact that it is repelled from the options for using your project. I am sure that Singleton is justified in use, but in other situations.
At the turning point, my point of view was confirmed by the well-known Android developer in certain circles, Android developer Alexey Skutarenko, calling the pattern “doubtful”. Whether from dislike for this pattern, or from not the best applicability of it to my needs - is unknown. But the decision was made to throw out 20 sheets of waste paper, to get new ones. The marker, in fact, also ended.
Then it was decided to go on the contrary. How would I like to use the project? I clearly represented it:
The decision was asked by itself: there should be a main class, we will call it PermissionAssistant. And we will divide logic into key blocks, for convenience we will combine them with the word Manager. And what is, logically, different tasks - respectively, different classes will be responsible for them.
Now let's define what the functional parts will be. Obviously, one of them will be responsible for requesting permissions and receiving information about them. Let's call it PermissionsManager. Since the visual part is also implied, we’ll add a PresenterManager (we assign the naming of the Viper, so that its discoverers will be well off).
The presenter will be responsible for the presentation of the controller, its configuration ... in general for the UI (if it is, of course, will be). By the way, I draw your attention to the fact that all parts are hidden by protocols for more flexibility in the future.
Flexibility ?!
Yes. Maybe not the best word, but reflects the essence. Imagine that we are repairing a submarine, screw fastening - 16-sheet thread with 29 inches (just made up). It is not necessary to make a new submarine every time, it will be enough to make a screw with known requirements and fasten it. Make and screw.
I am not a master of allegory, for example with the code. Let's sort PermissionsManager, its protocol hiding and implementation. To begin, we define the functional manager. Two methods are enough for us:
Request permission
Is permission approved
To taste, you can expand the functionality, but we have enough. And so, the protocol:
In our case, these are the requirements for the propeller of the boat.
Now we implement the protocol. We get a real object (screw). Him and fasten. But our main class Assistant (submarine) will not know what specific implementation (from what metal, how many days it was poured and how much work cost). The main class only knows that there are two functions. Wanted to change the implementation - please) This will be especially useful in customizing the visual part and the DataSource. Here about him now and talk.
Obviously, the visual part is much more complicated than just the Presenter. For good it should be divided into modules. Actually divided into two parts: Controller and DataSource Presenter holds the controller, he will deal with gestures, the screen and other things that controllers do. Of course, the question arises of how the controller will report actions. For the most inquisitive, the question will arise: “Will an ARC not destroy everything to hell in an hour?”
If with the first question everything is clear - delegates, then the second is the problem that stopped my work for a week. But for you to understand, I supplemented the scheme with links between objects (we hope everyone knows about strong and weak links, there shouldn't be any questions).
The problem is obvious (I hope) - the controller holds only the object Assistant (of course, the Presenter, but it holds the Assistant, so we omit the link). If the problem is not yet clear, explain:
Imagine that the object Assistant out of its scope, and accordingly, was thrown overboard ARC. If the controller was not presented, then the entire object dies. This is the correct behavior. But if the controller was presented ... then it now weighs on the stack - and accordingly it has a link outside the object. But Assistant, as it goes out of sight, it will die. You can demonstrate it by a simple example.
if true { let permissionAssistant = SPRequestPermissionAssistant.modules.dialog.interactive.create(with: [.Camera, .PhotoLibrary, .Notification]) permissionAssistant.present(on: self) }
We get a situation where the controller will be alive, but all the environment classes will die. And even Presenter, who kept the controller.
Sad
- I thought, and began to dive deeper into a bunch of diagrams, read about patterns (maybe I missed some), and generally dropped out of this world. Immersed in swimming.
How many conversations there was about the problem, even the sailors of the staff raised their ears. All in one voice asserted - “ What a terrible architecture? ”,“ To the author - poison ”and“ The controller should keep everything ”. Yes, I carried the controller to the center. But the problem is that if the controller holds the Assistant and was not presented immediately upon initialization, the entire object dies. In general, the connection could not be reversed, which meant that ...
take out controller as main object! Write logic inside the controller - well, no.
The decision came by itself over a cup of tea and it was something like an insight:
- "Why not?"
Just initialize Assistant as a controller controller and that's it! While the parent is alive, alive and Assistant. And since all interactive controllers were implied modal, the solution was well integrated. Such a solution seemed to me the best, although the pedantry inside was sad. Well, let's continue. The spirit has been raised, we pick up speed again!
Now it would be nice to separate the UI and PermissionManager. Everything is trivial here - we are doing the PermissionInterface protocol, which looks like this:
And for each new permishina (Location, Notification, Camera ...) we implement it. And in PermissionManager, we create the necessary Permission class and pull the necessary functions.
Update the schema: Now we see the whole picture. And as you can see, we can replace any brick. What I personally think is great. The lower the block on the stairs, the less will have to be rewritten. In order to implement a new controller, you need to implement its interface and inject it into the current system (how is your business). Want to change text and colors? Implement the DataSource protocol. I especially like the idea of having multiple PresenterManager. Now you want a dialog box, and on another screen - a pop-up banner on top (already in development)
Pop time
I perfectly understand that the number of stars correlates poorly with the quality of the code, I hope obviously the text is not about that at all.
While the project was in work, I received so many tips that I spent more time arguing (for myself) why one or another pattern / idea would not work. And that's good , I did a lot of work.
I do not consider myself a developer who can afford to give advice. But I will tell a little story: when I started to just think about the project, my good comrade Gennady (let's change the name) was working on a single-screen application. He did it on VIPER, and did not particularly delve into why and why it uses it. To my arguments:
- “ Let go of the problem, why do you need a heavy pattern on a super-simple application ?”
he was adamant. Several months passed, I made small projects, put them in the apartment and bought them great, handed over the job to the customer and saw how spring comes. Gennady continues to write the application ...
Patterns are not a pill for all diseases. Do not use it as an indicator of professionalism or because " so do gurus ." Hands will not wither if you make “not non MVL MVC” from MVC. Use patterns when you realize that this is necessary. Use them to make work easier. The argument " this pattern is used by cool projects " is completely irrelevant to the goals and this approach is unlikely to make your life easier.
I do not urge to abandon the patterns, to write everything in the controller and in general from VIPER to fire back with a magnetic gun. Use but thought out! Set specific requirements for the architecture and experiment - find out that it is better to solve the tasks. If VIPER is perfect, but PRESENTER seems odd to you - throw it away , why not? It is empirical work with architecture that will give the best, albeit not a classic result.
I do not know what my pattern is called (compote?) - but I was pleased with the way he solved his tasks. This is the result that should bring the use of the pattern.
“ Even professionals use storyboards ” by an unknown author.
All successful builds and Orthodox CTR!
UPD in a year: Now the project has collected more than 2K stars and decided to update it.