Foreword: This article is contributed by Zang Chengwei, an iOS technical expert from Meituan. After teaching two series of RactiveCocoa courses at StuQ, Mr. Zang recently opened a new course called "iOS Practical Black Magic". The course content involves a lot of low-level knowledge and application skills such as Objective-C Runtime and Swift. If you are interested, you can read the introduction at the end of the article. Thanks to Zang Chengwei for the authorization, the following is the text of the article. background In program development, we always hope to express our logic more concisely and semantically. Chain call is a common way of processing. The third-party libraries we often use, such as Masonry and Expecta, adopt this processing method.
This kind of expression used in a specific field is called DSL (Domain Specific Language). This article will introduce how to implement a chain call DSL. Implementation of chain call Let’s take a specific example. For example, we use chain expressions to create a UIView, set its frame and backgroundColor, and add it to a parent View. For the most basic Objective-C (before the iOS4 block appeared), if you want to implement chain calls, it can only be like this:
With blocks, we can change the bracket syntax to dot syntax.
It can be seen that the semantics of the chained syntax is very clear, and the syntax of the latter is more compact. Let's look at the implementation of the latter from two perspectives. 1. From a grammatical perspective Chaining can be done in two ways: 1) Use attributes in the return value to save information in the method For example, the .left .right .top .bottom and other methods in Masonry will return an instance of the MASConstraintMaker class when called, which has properties such as left/right/top/bottom to save information for each call;
For another example, the .notTo method in Expecta returns an instance of the EXPExpect class, which has a BOOL attribute self.negative to record whether .notTo is called;
For another example, in the .with method in the above example, we can directly return self; 2). Use block type properties to accept parameters For example, the .offset(15) method in Masonry receives a CGFloat as a parameter. You can add a block type property to the MASConstraintMaker class:
For example, in the example of .position(x, y), you can add an attribute to a class:
When the .position(x, y) method is called, this block is executed and the ViewMaker instance is returned to ensure that the chain call can be performed. 2. From a semantic perspective From a semantic perspective, it is necessary to define which are auxiliary words and which need to accept parameters. In order to ensure that the chain call can be completed, it is necessary to consider what is passed in and what is returned. Let’s take the example above as an example:
Let's look at it step by step. This DSL expression needs to describe an imperative sentence, starting with Alloc and ending with intoView. Before the terminator of intoView, we modify UIView in some ways, using position, size, bgColor, etc. Let's look at four sections below to see how to implement such an expression: (1) Object In the semantics of AllocA(UIView), we have determined that the object is a UIVIew. Since the UIView is determined at the end of intoView, we need to create an intermediate class to save all the intermediate conditions. Here we use the ViewMaker class.
In addition, we can notice that AllocA is a function, and UIView cannot be directly passed to this function, so the syntax becomes AllocA([UIView class]) and loses its simplicity. So we need to define a macro to "swallow" the brackets and class method:
(2) Particles Often, in order to make the DSL syntax look more coherent, we need some auxiliary words to help, such as the "with" in the sentence "make.top.equalTo(superview.mas_top).with.offset(padding.top)" in Masonry. This auxiliary word is the same as the grammar we have learned. It usually has no practical effect and simply returns self.
It should be noted that if you return to yourself, there is no way to prevent users from constantly calling yourself.with.with.with. To avoid this situation, you can generate a new class. Each class has methods at its own level to avoid cross-level calls.
This effectively prevents syntax like .with.with.with. But in reality, we need to develop according to real needs. Users who use DSL for better expressiveness will not write code like .with.with.with. Such protective measures seem a bit unnecessary. However, using classes to distinguish particles has several other small advantages. It ensures that when giving syntax prompts, the ViewClassHelper class only has a syntax prompt like .with, while ViewMaker does not have a .with syntax prompt; and at the same time ensures that .with must appear. However, to simplify the article, we will use the former, that is, .with returns self to continue the following text:
(3) Modifying part: attributive Like the position size bgColor in the example, these are the attributive parts used to modify UIView. They exist in the ViewMaker instance in the form of attributes. In order to support chain expressions, they will continue to return self when implemented. Let's try to implement this:
(4) Terminal word The term "terminal word" is really not found in modern grammar, but it is particularly important in DSL. The ViewMaker instance collects a lot of modifications from beginning to end, and a *** expression word is needed to produce the *** result, which is called a "terminal word". For example, in the open source library Expecta, equal is to show the real behavior, and neither to nor notTo will actually trigger the behavior. In our example, the term .intoView(aSuperView) can be implemented like this:
In this way, a terminal word is written. Summary of the final code:
Summarize This chain call can make the program clearer and more readable in certain scenarios. The same is true in Swift. You can make good use of it to make your code more beautiful. In fact, if iOS developers want to continue to improve and grow into true masters, they must put their vision above business needs, streamline and strengthen their core skills, and improve their mastery of languages and tools in order to improve development efficiency and enhance their skill levels. Here we have prepared more fun and advanced iOS black magic attack and defense techniques for you to get twice the result with half the effort. StuQ Academy has specially invited Mr. Zang Chengwei, a senior iOS technical expert who is well-liked by students, to open the course "iOS Practical Black Magic". You can efficiently get the advanced iOS black magic attack and defense techniques that must be mastered in 6 weeks and 12 hours, so that you can gradually step out of ordinary developers, see a different language, and experience a different development! |
<<: Reward Collection | The second issue of Aiti Tribe Stories is officially launched
>>: Android development: from modularization to componentization (Part 1)
At the Huawei P30 series domestic launch conferen...
Issue 1204 @Embarrassing Encyclopedia Have you ev...
In recent days, extreme rainfall has occurred in ...
It is understood that this update appears under &...
According to the New York Times, security researc...
As we enter 2023, AI technology continues to deve...
For movie fans, the most important thing to enjoy...
On January 3, Tuhu Auto Care issued a statement r...
On the morning of January 2, Evergrande Health...
Morgan Stanley recently released "Humanoid R...
Introduction Maugham, author of The Moon and Sixp...
[[155037]] The process of starting a business is ...
[Popular Science Long Picture] Across a Century, ...
[[148706]] After the press conference yesterday, ...
Difficulty in recruiting students and high costs ...