After more than eight years of iOS development, there have been many excellent third-party libraries, but what is elegant? Overall, AFNetworking is elegant, while GPUImage is usable, not elegant. Writing an elegant third-party library is like creating a fine work of art. The process is intoxicating and the results are pleasing. Otherwise, it is simply code accumulation and function realization, the process is like moving bricks, and there is no sense of achievement after completion. Here are my tips on how to elegantly encapsulate third-party libraries.
named
Good naming rules are the beginning of a successful third library, but in reality many people name themselves with names, resulting in increased communication costs. In fact naming issues, apple has its official uniform standard, namely first letters lowercase camel, such as: elegantly-named setName:, reloadDataWithName: andEmail: etc., and general agreement in the getter getName, not directly use the name.
attribute
Attribute naming is best can directly express its meaning of English nouns, of course, when appropriate to add adjectives, such as:
@interface XTUser : NSObject
@property (nonatomic, strong) NSString *fullName;
@property (nonatomic, strong) NSString *firstName, *lastName;
@property (nonatomic, strong) NSString *phoneNumber;
@property (nonatomic, strong) NSString *email;
@end
Copy the code
If an array or a collection, etc., use the plural form of the noun:
@interface XTDownloadManager : NSObject
@property (nonatomic, strong) NSArray<NSURL *> *downloadURLs;
@end
Copy the code
To express the number of attributes, you can add numberOfXXX or XXXCount as follows:
@interface XTBook : NSObject
@property (nonatomic, assign) NSInteger numberOfPages;
@property (nonatomic, assign) NSInteger pageCount;
@end
Copy the code
Properties that represent the state of an object, such as:
@property (nonatomic, assign) BOOL isClosed;
, has been closed@property (nonatomic, assign) BOOL isClosing;
, is closing@property (nonatomic, assign) BOOL isAvaliable;
, currently available@property (nonatomic, assign) BOOL hasChanged;
, has been changed
The following is not clear:
@property (nonatomic, assing) BOOL isClose;
Of course, the previous code could be written more elegantly:
@property (nonatomic, assign, getter=isClosed) BOOL closed;
@property (nonatomic, assing, getter=isClosing) BOOL closing;
@property (nonatomic, assing, getter=isAvaliable) BOOL avaliable;
@property (nonatomic, assign, getter=hasChanged) BOOL changed;
The enumeration
The iOS base class library has enumerated types that are easier to use than any other official library, because there is no pain in using them, no need to look up the documentation, type according to its type and then there is an explicit autocomplete, such as the one commonly used in UIKit:
typedef NS_ENUM(NSInteger, UIControlContentHorizontalAlignment) {
UIControlContentHorizontalAlignmentCenter = 0,
UIControlContentHorizontalAlignmentLeft = 1,
UIControlContentHorizontalAlignmentRight = 2,
UIControlContentHorizontalAlignmentFill = 3,
};
typedef NS_OPTIONS(NSUInteger, UIControlState) {
UIControlStateNormal = 0,
UIControlStateHighlighted = 1 << 0, // used when UIControl isHighlighted is set
UIControlStateDisabled = 1 << 1,
UIControlStateSelected = 1 << 2, // flag usable by app (see below)
UIControlStateFocused NS_ENUM_AVAILABLE_IOS(9_0) = 1 << 3, // Applicable only when the screen supports focus
UIControlStateApplication = 0x00FF0000, // additional flags available for application use
UIControlStateReserved = 0xFF000000 // flags reserved for internal framework use
};
Copy the code
And so on. And its characteristics are obvious
Enum Type name {Type name + enumeration name 0 = enumeration value 0, type name + enumeration name 1 = enumeration value 1, Type name + enumeration name 2 = enumeration value 2,}Copy the code
So for a while, I wanted to rewrite the disgusting macros in OpenGL like this (github.com/rickytan/Co…). , such as:
CC_ENUM(int, GLUnsignedType) {
GLUnsignedTypeByte = GL_UNSIGNED_BYTE,
GLUnsignedTypeShort = GL_UNSIGNED_SHORT,
GLUnsignedTypeInt = GL_UNSIGNED_INT,
};
CC_ENUM(int, GLDrawMode) {
GLDrawModePoints = GL_POINTS,
GLDrawModeLines = GL_LINES,
GLDrawModeLineLoop = GL_LINE_LOOP,
GLDrawModeLineStrip = GL_LINE_STRIP,
GLDrawModeTriangles = GL_TRIANGLES,
GLDrawModeTriangleStrip = GL_TRIANGLE_STRIP,
GLDrawModeTriangleFan = GL_TRIANGLE_FAN,
GLDrawModeQuads = GL_QUADS,
GLDrawModeQuadStrip = GL_QUAD_STRIP,
GLDrawModePolygon = GL_POLYGON,
};
Copy the code
However, due to limited skills and insufficient understanding of OpenGL, I gave up.
Some vendors currently on the market do not release SDKS according to this convention, so you have to constantly check the documentation to know how to do it. Like the much-maligned goose factory:
Enum QQApiInterfaceReqType {EGETMESSAGEFROMQQREQTYPE = 0, // ESENDMESSAGETOQQREQTYPE = 1, //< Third-Party app -> mobile, Third party applications to hand Q share news ESHOWMESSAGEFROMQQREQTYPE = 2 / / / < hand Q - > the third party applications, request the third party applications show the data in the message}; /** @interface QQBaseReq: NSObject /** Request message type, see \ref QQApiInterfaceReqType */ @property (nonatomic, assign) int type; @endCopy the code
There are three problems with the SDK above:
QQBaseRequest
You don’t have to abbreviate itQQBaseReq
In theObjective-CThe world, the name is long enough, but to be able to class, method and other functions clearly stated;type
The type should be explicitly specified, just oneint
Overwhelm the user;QQApiInterfaceReqType
The enumeration name should start with QQApiInterfaceReqType and be named in camel shape for auto-completion. All caps are generally macro definitions.
Interface and implementation
When writing the third party library, try to face the interface programming, and give a default implementation, convenient user extension.
For example, if you implement a View that displays photos, your class definition is as follows:
@interface MyPhoto : NSObject
@property (nonatomic, strong) UIImage *thumbnailImage;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSURL *originURL;
@end
@interface MyPhotoGalleryView : UIView
@property (nonatomic, strong) NSArray<MyPhoto *> *photos;
@end
Copy the code
In a real project, however, a user would typically have a custom photo object (such as @class XTPhoto), and in order to use your implementation, he would have to convert his object to your photos property, causing unnecessary memory waste.
@interface XTPhoto : NSObject @property (nonatomic, strong) UIImage *image; @property (nonatomic, strong) NSString *photoPath; @end ... NSMutableArray *photos = [NSMutableArray arrayWithCapacity:self.photoGallery.count]; for (XTPhoto *photo in self.photoGallery) { MyPhoto *my = ... ; [photos addObject:my]; } photoGalleryView.photos = [NSArray arrayWithArray:photos];Copy the code
This problem can be avoided to some extent if the implementation is different and defines an interface:
@protocol MyPhoto <NSObject>
@required
@property (nonatomic, readonly) UIImage *thumbnailImage;
@property (nonatomic, readonly) NSString *name;
@optional
@property (nonatomic, readonly) NSURL *originURL;
@end
@interface MyPhoto : NSObject <MyPhoto>
@property (nonatomic, strong) UIImage *thumbnailImage;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSURL *originURL;
@end
@interface MyPhotoGalleryView : UIView
@property (nonatomic, strong) NSArray<id<MyPhoto> > *photos;
@end
@interface XTPhoto : NSObject <MyPhoto>
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, strong) NSString *photoPath;
@end
@implementation XTPhoto
- (UIImage *)thumbnailImage
{
return self.image;
}
- (NSString *)name
{
return self.photoPath.lastPathComponent;
}
- (NSURL *)originURL
{
return [NSURL URLWithString:self.photoPath];
}
@end
Copy the code
This allows you to assign self.photoGallery directly to photogalleryview.photos.
In addition to interface oriented programming, the following principles should also be followed in the implementation of classes:
-
Keep it simple. Expose only the minimum number of interfaces that enable functionality. If you have a view with a Label that shows the amount of gold remaining, red if greater than 0 and green if less than 0, then only one NSInteger interface should be exposed.
@interface XTStatsView : UIView @property (nonatomic, assign) NSInteger numberOfGold; @end Copy the code
Then set the value of Label in the concrete implementation:
@interface XTStatsView () @property (nonatomic, strong) UILabel *goldLabel; @end @implementation XTStatsView - (void)setNumberOfGold:(NSInteger)numberOfGold { _numberOfGold = numberOfGold; Self.goldlabel. text = [NSString stringWithFormat:@"%d gold ", _numberOfGold]; self.goldlabel. text = [NSString stringWithFormat:@"%d gold ", _numberOfGold]; if (_numberOfGold >= 0) { self.goldLabel.textColor = [UIColor redColor]; } else { self.goldLabel.textColor = [UIColor greenColor]; } } @endCopy the code
A lazy approach is to expose goldLabel directly, which gives the user more flexibility, but also leads to some unpredictable results.
In addition, there are some cases that expose redundant interfaces. For example, if you define a Cell to display the contents of an Entity, define it as follows:
@interface MyCell: UITableViewCell @property (nonatomic, strong) MyEntity *entity; - (void)renderData; @end Copy the code
When the user sets the Entity, it has to call – (void)renderData to display it. You can remove – (void)renderData and internally call – (void)renderData or some similar method in the setter of the Entity. What the consumer sets is what it sees (gets), and no other methods need to be called.
-
Call order is irrelevant. If you implement a class that has more state independent properties, it should be call order independent. For example, if a VC exposes a titleColor property that can set the color of the Label in the view, a common error is implemented as follows:
@interface MyViewController: UIViewController @property (nonatomic, strong) UIColor *titleColor; @end @implementation MyViewController - (void)viewDidLoad { [super viewDidLoad]; self.titleLabel.textColor = self.titleColor; } @end Copy the code
The problem with the above implementation is that setting the titleColor will not work if the VC view is already loaded. The correct implementation would look like this:
@implementation MyViewController - (void)viewDidLoad { [super viewDidLoad]; self.titleLabel.textColor = self.titleColor; } - (void)setTitleColor:(UIColor *)titleColor { if (_titleColor ! = titleColor) { _titleColor = titleColor; if (self.isViewLoaded) { self.titleLabel.textColor = self.titleColor; } } } @endCopy the code
This ensures that the user’s Settings are valid at any time.
The macro
Proper use of NS’s built-in compiler preprocessor macro definition can immediately improve the overall code, such as:
UIKIT_EXTERN NSString *const MyUserDidLoginNotification; @interface MyUser: NSObject @property (nonatomic, strong) NSString *name DEPRECATED_MSG_ATTRIBUTE("Use userName instead!" ); @property (nonatomic, strong) NSString *userName; - (instancetype)initWithName:(NSString *)name email:(NSString *)email NS_DESIGNATED_INITIALIZER; - (void)reloadData NS_REQUIRES_SUPER; @end @interface MyUserManager : NSObject + (instancetype)sharedManager; - (instancetype)init NS_UNAVAILABLE; @endCopy the code
More macro see < Foundation/NSObjCRuntime. H >.