In the
previous article I touched on the subject of the project structure. In my opinion, this is the first step that starts a beautiful code.
The second step is the proper organization of the files of the class itself.
For some, articles about Obj C may seem archaic, but for now we are not planning a widespread move to Swift. It is rather a smooth replacement in new projects. There is still a huge Objective-C code base that needs to be maintained.
In addition, Swift has not yet gained enough experience in large projects.
As an anti-example, I propose to consider
WYPopoverController .
Imagine that he came to us not from the pod, but this is our own class, written in the era of violent youth.
')
In the header file there are 279 lines, in the implementation file there are 2948 lines.
Hi, ⌘F, I did not miss.
Maintaining such a huge file can bring a lot of difficulties.
I try to keep all .m files up to 100 lines. Maximum 150.
This helps to quickly find the necessary logic without having to hold a map in your head.
Header file WYPopoverController.h
After connecting libraries from the SDK, the protocol and class are declared:
@protocol WYPopoverControllerDelegate; @class WYPopoverTheme;
This is a great solution to avoid adding headers for the WYPopoverTheme class.
Not all classes using WYPopoverController need to change the theme.
But the WYPopoverTheme class itself is declared in the same file.
There is a simple rule about this:
one .h file - one
@interface
one .m file - one
@implementation
Pay attention to the line "//// ..." before the interface.
What is she for? Obviously not to be confused where one interface ended, and where the other begins.
It's so easy to add a property or method to the wrong place.
Every programmer sometimes has the feeling that the file has become too large and difficult to understand. Symptoms of this disease include frequent use of a drop-down list of methods or file search (⌘F).
Another symptom is the desire to insert the #pragma mark.
I've already gotten sick, here's your medicine.
Never use the #pragma mark or lines like "//// ...", this does not change anything. Only instead of the method, you will first look for the label.
The first thing to do is to render the classes in separate files. Then declare via
@class
ones that are needed.
Minimize
#import
to avoid global recompilation every time headers change.
Constants
#define WY_POPOVER_DEFAULT_ANIMATION_DURATION .25f #define WY_POPOVER_MIN_SIZE
Defaults must be removed in a private header file. This is the default.
If I want to know them, I’ll go into the implementation.
In addition, constants must be defined as follows through const:
const CGFloat kAbc = 0.25;
.
If you need to declare a constant then:
FOUNDATION_EXPORT const CGFloat kAbc;
Using
NS_OPTIONS I agree.
We need to know this type to call the controller. And the controller should provide this knowledge. It may be worth putting them in a separate header. Well, okay, this is nagging.
@interface WYPopoverController"
Supports UIAppearanceContainer, this is cool. We can easily set it up in one place. Plus sign. More in detail
here .
Properties
I do not understand why on what basis the list of properties is compiled. It seems that they were added to the header as they were implemented.
I would have made the delegate to the topic, and grouped the rest of the settings into categories or just alphabetically (I’m set to ⌃⌘A).
@property (nonatomic, assign) BOOL dismissOnPassthroughViewTap; @property (nonatomic, assign) BOOL dismissOnTap; @property (nonatomic, assign) BOOL implicitAnimationsDisabled; @property (nonatomic, assign) BOOL wantsDefaultContentAppearance; @property (nonatomic, assign) CGSize popoverContentSize; @property (nonatomic, assign) float animationDuration; @property (nonatomic, assign) UIEdgeInsets popoverLayoutMargins; @property (nonatomic, copy) NSArray *passthroughViews; @property (nonatomic, readonly, getter=isPopoverVisible) BOOL popoverVisible; @property (nonatomic, strong) WYPopoverTheme *theme; @property (nonatomic, strong, readonly) UIViewController *contentViewController; @property (nonatomic, weak) id <WYPopoverControllerDelegate> delegate; @property (nonatomic, copy) void (^dismissCompletionBlock)(WYPopoverController *dimissedController);
By the way, assign is optional;
@property (nonatomic) BOOL dismissOnPassthroughViewTap;
It’s good that the getter for popoverVisible is defined as isPopoverVisible, this property answers the question about the state of the object, therefore it starts with is:
@property (nonatomic, readonly, getter=isPopoverVisible) BOOL popoverVisible;
But for dismissOnTap and others, you also need to make custom getters.
@property (nonatomic, assign) BOOL dismissOnTap;
This property answers the question of what will happen to an object under certain events.
And specifically what needs to be done on tapu.
For these options, I define a getter with the prefix will (correct if I use English incorrectly):
@property (nonatomic, getter=willDismissOnTap) BOOL dismissOnTap;
Additional liability
+ (void)setDefaultTheme:(WYPopoverTheme *)theme; + (WYPopoverTheme *)defaultTheme;
If there is a theme object, then it should monitor its state by default.
Why load our class with additional responsibility?
I transfer to WYPopoverTheme.
But I agree that the class methods should be at the top of the list of methods.
Initialization
- (id)initWithContentViewController:(UIViewController *)viewController;
The proposed method calls [self init], so you can be sure that the object initializes all the necessary data before using it.
But to find out about this, I had to watch the source.
But if you just call init, you won't be able to set contentViewController. Since he readonly.
So you need to mark int as a method that you do not need to use, and initWithContentViewController as the desired initialization method.
Otherwise, you can blunt for a long time by making alloc init.
There is such a way.
Use NS_DESIGNATED_INITIALIZER to indicate to the client the desired initialization method.
You can mark multiple methods this way.
You can even mark the superclass method:
Like so
- (id)init NS_DESIGNATED_INITIALIZER;
Then, when calling the wrong initializer, Xcode will show the working (I have an error).
Method returns id.
It is better to use
instancetype . Then the method will always return an instance of the class for which it was called. Even if we inherit from it.
Auxiliary methods
Notice the comment? This is again a symptom - the header file is too big.
We will solve this problem further.
Section "// Present popover from classic views methods"
This is a real disease. The file has become too large, you need to share the header.
To do this, use the category.
Judging by the .h files from Apple (for example, UIView.h), THEY also do this.
Take the methods of this section before "// Present popover from bar button items methods" and create the category PresentationFromView.
⌘N -> iOS -> Source -> Objective-C File

Let's transfer implementation of these methods to the WYPopoverController + PresentationFromView.m file.
Copy the interface from WYPopoverController + PresentationFromView.h into WYPopoverController.h after the WYPopoverController interface and transfer the method declarations to it.
The WYPopoverController + PresentationFromView.h file is deleted.
In general, it is easier to have a special template for such categories where the .h file is not needed.
It will be so:
@interface WYPopoverController (PresentationFromView) - (void)presentPopoverFromRect:(CGRect)rect inView:(UIView *)view permittedArrowDirections:(WYPopoverArrowDirection)arrowDirections animated:(BOOL)animated; ……………………………………………………………………… @end
These methods are public therefore their declarations in WYPopoverController.h.
We will not
show underpants to break encapsulation and we will declare private methods in Private extension. It is created in the same place where the category.
All .m files must import it:
#import "WYPopoverController_Private.h"
It must also import the files necessary for the work of different categories.
Files required for only one category need to be imported into a specific category .m file.
In the same place we declare all private properties and methods.
If we need a header file that will be used by the heirs, it can be called Protected.
We do the same with all methods that can be attributed to one logical group.
If the .m file is too large, select another category.
But do not overdo it: 4-5 categories are the maximum, otherwise you can start getting confused again.
If you need more, perhaps you are trying to endow the class with too many responsibilities (
God object ). In this case, make these responsibilities in a separate class. If you really need to make such a super class, make a facade.
It is better, of course, to design classes before implementation, but it does not always work.
Category perfectly help isolate functionality. It can then be taken out without much pain in a separate class.
If you decide to divide a class into several separate classes, avoid the large number of calls between the two classes.
Otherwise there will be a high connectivity of the code and there will be no point in the separation. The object should be more like a black box in which one method is called, and after processing the data it calls one method in the opposite direction. No swiss knives.
A category can be a good place to endure protocol methods.
We do this:
@interface WYPopoverController (TableView) <UITableViewDataSource, UITableViewDelegate>
Or move IBAction to the UserActions category.
Not so long ago, Xcode learned to define IBAction in implementation files.
But you still need to declare them in the title, then it will be easy to understand where their implementations are.
Sometimes it is necessary to define public and private methods in one category.
Then in WYPopoverController.h we will add the category TableView.
And in WYPopoverController_Private.h the category is TableView_Private, and the implementation file will be one- WYPopoverController + TableView.m.
The class in the project will look like this:

In the comments to the previous article,
greenkaktus suggested describing the work with "#pragma, #warning, // FIXME".
I only use #pragma when I need to disable the revings in certain places.
I use it very carefully, only when I am sure that this is the only sure way.
#pragma mark never use. I beat my programmers with a ruler for this.
I do not use #warning, they treat me as errors. Some details are in the previous article.
"// TODO" I use as tips for the future. For example, if I see a potential bottleneck that may require optimization with increasing loads.
"// FIXME" I use in places that need to be fixed, but a little later.
I still have a lot of comments on the quality of this code, but this topic deserves a separate article.
In the next article I plan to describe the class device that I use to work with api.
I tried to design it as flexible as possible and with the maximum degree of automation.
So that adding a new call to the api method and parsing the answer can be done in several lines.
Any suggestions and comments are welcome.
Thank you for reading to the end, all good.