First, the disadvantages of the traditional way
UITableView is a highly attended view component. Developers configure layout logic by implementing the
and
protocol methods. Protocol-oriented design patterns are common in Apple code design. It fits most business scenarios and is flexible enough.
The protocol approach associated with UITableView fully embodies the single responsibility principle. This approach has many advantages, such as the component only needs to care about the data currently needed at a certain time, avoiding unnecessary calculations, and also allowing data to be released in a timely manner to reduce memory spikes.
However, when an interface structure is complex and the display order can change dynamically, developers often need to write a lot of if/else/else IF or switch branch statements to distinguish the view types and layouts of different sections/rows. Due to the unique responsibilities of the UITableView related protocol methods, this branch statement can be repeated in multiple protocol methods.
Obviously, in this scenario, UITableView becomes less elegant.
Second, conventional optimization ideas
Of course, it’s easy to think of using an intermediate class to centralize the management of data that is distributed across a Cell:
@interface CellLayout : NSObject@property (nonatomic, strong) Class cellClass; @property (nonatomic, assign) CGFloat cellHeight; @property (nonatomic, strong) AnyModel *cellModel; . @end
Copy the code
And then from NSArray
layoutArray in each protocol method of UITableView; Get the CellLayout object configuration in the array, so the developer only needs to worry about how to build the layoutArray array and avoid writing too many branch statements in the protocol method.
This line of thinking does two things:
-
Provides an intermediate class that contains all the layout information for a Cell.
-
In the case that the intermediate class is determined, the return value of the and protocol method only needs to be based on an attribute of the corresponding intermediate class, which is concise and clear.
After thinking, the writer did a team spent some time (https://github.com/indulgeIn/YBHandyTableView), it can make you easy to realize the optimization scheme of the core operation is to use an array to replace the agreement method for UITableView configuration data.
Of course, this has its limits, which I’ll discuss later.
Component architecture design
After the previous analysis, the component needs to design an intermediate class and encapsulate the implementation of the
and
protocol methods.
The core idea
As a general idea, you might want to design a generic intermediate class like CellLayout, and then use inherited properties to add additional properties to CellLayout. This does work, but it introduces serious coupling and requires developers to know from the start that they have to write a class that inherits from your CellLayout, which is a pain if they need to inherit from another class in their own business (after all OC does not support multiple inheritance). Moreover, it might be a hassle to eliminate this solution one day, as the more CellLayout is designed and the more business it contains, the harder it will be to peel it off.
Also, a CellLayout isn’t going to solve the problem, because configuring a UITableView might require some data from the UITableViewCell, and some generic way to tell the UITableViewCell when to configure data to refresh the UI, which means that according to this logic, We also need to write a BaseTableViewCell…
Obviously, this approach is not elegant and violates the dependency inversion principle.
My approach is to abstract this “intermediate class” as two protocols: YBHTCellProtocol and YBHTCellConfigProtocol, which contain the data needed to lay out a UITableView. You can certainly extend these two protocols with your own business. YBHTCellProtocol is implemented by a custom UITableViewCell; YBHTCellConfigProtocol is implemented in whatever class the developer chooses to use. In general, it is the fastest way to implement it using a Model that contains the data required by UITableViewCell (see the use case in the Demo).
Deep customization is guaranteed
For one thing, UITableView has a lot of protocol methods, and extending all the configurations for YBHTCellProtocol and YBHTCellConfigProtocol would require a lot of code, which might not be worth the cost.
Therefore, the author uses Multi-proxy (YBHandyTableViewProxy) to ensure the in-depth customization requirements of component users, and also to avoid some special situations in which the business module of this component can quickly expand functions that were not available before:
- (
void)ybht_addDelegate:(
id<
UITableViewDelegate>)delegate;
- (
void)ybht_addDataSource:(
id<
UITableViewDataSource>)dataSource;
Copy the code
Of course, there are pitfalls to doing this, so I suggest readers to understand the principle of this component if you want to use it, the code of this component is not much and not profound, I believe that as long as interested friends can quickly understand.
4. Disadvantages of components
Component configuration is simple:
NSArray<id<YBHTCellConfigProtocol>> tmpArr = ... ; [anyTableView.ybht_rowArray addObjectsFromArray:tmpArr]; [anyTableView reloadData];
Copy the code
As you can see in the code, you need to pass in an instance that implements the YBHTCellConfigProtocol, along with the corresponding UITableViewCell that implements the YBHTCellProtocol (see UML class diagram).
For example, if you write a UITableView in a UIViewController and use that component to configure data, it is clear that the component wraps the
and the
protocol, The UIViewController is no longer coupled to the UITableViewCell that you’re customizing, which means that they can’t interact directly with each other.
So how do they interact indirectly?
-
YBHandyTableViewIMP is a component implementation and protocol class, so passing a UIViewController object into that class will allow you to interact with UITableViewCell, But since both YBHandyTableViewIMP and UITableViewCell do not rely on the YBHTCellProtocol directly, this makes custom interactions difficult.
-
Another way to think about it, from the way that components are used, is that UIViewController is associated with id, and id is associated with UITableViewCell, So you can pass a UIViewController to a UITableViewCell with an id, and then interact with it.
-
Intercepts events based on the delivery path of the response chain. This approach is neat, but still feels insecure, and it has the advantage that processing the interactive events of the UITableViewCell can be done without going through the component.
Finally, I suggest the second approach.
The UITableViewCell can be used to transfer a large number of events to the outer layer of the business, such as the jump interface, network requests, etc. If a lot of interaction is necessary (or in order to meet business architecture specifications), give up being lazy and design a way that works for the business.
Five, the conclusion
This article is a small practice of the author to share ideas, need to understand that a code design can not satisfy all business, especially this kind of business closely linked components. At first I was hopeful that the scenarios for this component were large, but later I found that there were limitations.
Components always make granularity bigger, and when you go for smaller granularity you’ll find: damn, it’s like this component doesn’t make sense 😂.
There is no one-size-fits-all solution.
Making address: YBHandyTableView (https://github.com/indulgeIn/YBHandyTableView)
Recommended reading
Tab Bar ICONS used to work like this
Explore the transmission performance limits of messageHandler
Use GLSL for Tiktok effects in iOS
Talk about the iOS network layer design
IOS App seconds on H5 optimization summary
Just count while you’re watching