作为苹果官方的framework,我觉得它在变量命名和使用上有很多值得我们借鉴的地方
@protocol MKAnnotation <NSObject>
// Center latitude and longitude of the annotation view.
// The implementation of this property must be KVO compliant.
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
@optional
// Title and subtitle for use by selection UI.
@property (nonatomic, readonly, copy) NSString *title;
@property (nonatomic, readonly, copy) NSString *subtitle;
// Called as a result of dragging an annotation view.
- (void)setCoordinate:(CLLocationCoordinate2D)newCoordinate NS_AVAILABLE(10_9, 4_0);
@end
// Post this notification to re-query callout information.
MK_EXTERN NSString * const MKAnnotationCalloutInfoDidChangeNotification;
typedef NS_ENUM(NSUInteger, MKAnnotationViewDragState) {
MKAnnotationViewDragStateNone = 0, // View is at rest, sitting on the map.
MKAnnotationViewDragStateStarting, // View is beginning to drag (e.g. pin lift)
MKAnnotationViewDragStateDragging, // View is dragging ("lift" animations are complete)
MKAnnotationViewDragStateCanceling, // View was not dragged and should return to its starting position (e.g. pin drop)
MKAnnotationViewDragStateEnding // View was dragged, new coordinate is set and view should return to resting position (e.g. pin drop)
} NS_ENUM_AVAILABLE(10_9, 4_0);
对于这两个变量的命名我们可以看到都是把'MKAnnotationView'完整的放在变量名字前面,通常他们作为可以被外部引用的类型,这样命名有助于清楚的表示出他的从属和含义。
在这部分代码中,我们看到了两个我们经常使用的数据定义方式:
用于包裹通知的字符串
常量字符串对象: MKAnnotationCalloutInfoDidChangeNotification是一个指向NSString对象的常量指针,通常这种类型是用于包装通知的。其实涉及到的const的使用就不赘述了。
其中有个很有趣的东西:MK_EXTERN。我们来看看定义:
#ifdef __cplusplus
#define MK_EXTERN extern "C" __attribute__((visibility ("default")))
#else
#define MK_EXTERN extern __attribute__((visibility ("default")))
#endif
这个说起来也挺长的,注意几点即可: 一.cplusplus是cpp中的自定义宏,那么定义了这个宏的话表示这是一段cpp的代码。 二.被extern限定的函数或变量是extern类型的 三.在cpp代码中使用extern “C"表示这一点以C的方式处理函数名。四.attribute__((visibility (“default”))会使链接过程中符号在所有情况下都被输出,使得这些符号都对外部可见,以用来让外部文件也能使用和操作他。
枚举型
说到枚举型变量,我们来看看系统关于NS_ENUM的定义
#define NS_ENUM(_type, _name) CF_ENUM(_type, _name)
#define NS_OPTIONS(_type, _name) CF_OPTIONS(_type, _name)
// Enums and Options
#if (__cplusplus && __cplusplus >= 201103L && (__has_extension(cxx_strong_enums) || __has_feature(objc_fixed_enum))) || (!__cplusplus && __has_feature(objc_fixed_enum))
#define CF_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#if (__cplusplus)
#define CF_OPTIONS(_type, _name) _type _name; enum : _type
#else
#define CF_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type
#endif
#else
#define CF_ENUM(_type, _name) _type _name; enum
#define CF_OPTIONS(_type, _name) _type _name; enum
#endif
我们可以注意到NS_ENUM就是对emun和typedef关键字的封装,这里需要提的一个问题是关于NS_ENUM和NS_OPTIONS的一些小区别。从枚举定义来看,NS_ENUM和NS_OPTIONS本质是一样的,仅仅从字面上来区分其用途。NS_ENUM是通用情况,NS_OPTIONS一般用来定义具有位移操作或特点的情况(bitmask)。
比如在MKDirectionsTypes.h文件中:
typedef NS_OPTIONS(NSUInteger, MKDirectionsTransportType) {
MKDirectionsTransportTypeAutomobile = 1 << 0,
MKDirectionsTransportTypeWalking = 1 << 1,
MKDirectionsTransportTypeAny = 0x0FFFFFFF
} NS_ENUM_AVAILABLE(10_9, 7_0);
对于枚举型变量,我们还注意到一点他们是名词的组合,对于start, drag, walk这类有动词含义的词都已ing形式命名,诸如:starting,dragging,walking。
// Defaults to YES. If NO, ignores touch events and subclasses may draw differently.
@property (nonatomic, getter=isEnabled) BOOL enabled;
// Defaults to NO. This gets set/cleared automatically when touch enters/exits during tracking and cleared on up.
@property (nonatomic, getter=isHighlighted) BOOL highlighted;
// Defaults to NO. Becomes YES when tapped/clicked on in the map view.
@property (nonatomic, getter=isSelected) BOOL selected;
- (void)setSelected:(BOOL)selected animated:(BOOL)animated;
对于BOOL型变量,这里有个很有趣的处理方式:
对于init方法:
- (instancetype)initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier;
我们使用instancetype作为返回值类型,使那些非关联返回类型的方法返回所在类的类型。也就是说在这里我们可以明确的知道这个函数返回的类型就是MKAnnotationView。
typedef void (^MKDirectionsHandler)(MKDirectionsResponse *response, NSError *error);
typedef void (^MKETAHandler)(MKETAResponse *response, NSError *error);
这里有两个block类型的定义, 看看细节:
这个文件除了最下面的 ‘NSValue (NSValueMapKitGeometryExtensions)’ 之外都是都像是C语言的头文件,它主要是封装了很多对于地图常用的数据结构和一些计算函数。
挑几个有代表性的
typedef struct {
CLLocationDegrees latitudeDelta;
CLLocationDegrees longitudeDelta;
} MKCoordinateSpan;
NS_INLINE MKMapRect MKMapRectMake(double x, double y, double width, double height) {
return (MKMapRect){ MKMapPointMake(x, y), MKMapSizeMake(width, height) };
}
NS_INLINE double MKMapRectGetMinY(MKMapRect rect) {
return rect.origin.y;
}
MK_EXTERN MKMapRect MKMapRectInset(MKMapRect rect, double dx, double dy) NS_AVAILABLE(10_9, 4_0);
我们可以看到一个struct,两个内联函数定义,一个extern函数的声明。
这个文件对于开发者来说应该是最常用引用的一个头文件了,其实它相对而言是非常简单易懂的。本文不谈功能和业务。从这个文件来看一些命名的细节。
@property (nonatomic) BOOL showsUserLocation;
- (void)removeAnnotations:(NSArray *)annotations;
事实上,上述只是零零散散说了一些细节。作为一篇想把命名方式说的比较清楚的文章,明显这样做是不负责任的。 但是通过上述的一些描述有助于我们更好的理解下面的分析。
这里吹个牛逼引入了"Apple Style"这个词, 作为一个不牛逼iOS开发者,我觉得苹果他们自己家的命名还是非常棒的。
在前面第一部分的文章中我们着重强调了一个概念:
何时需要呢?在此我们偷个懒看一下苹果官方文档的解释:
Use prefixes when naming classes, protocols, functions, constants, and typedef structures. Do not use prefixes when naming methods; methods exist in a name space created by the class that defines them. Also, don’t use prefixes for naming the fields of a structure.
当我们定义Class,Protocal,functions(这里很有意思,functions我们认为是C语言全局函数, 而methods是objective-c的函数),Constant(全局的常量)以及typedef一个struct的时候。举个例子:
1. @interface MKMapView : UIView <NSCoding>
2. @protocol MKAnnotation <NSObject>
3. MK_EXTERN NSString * const MKAnnotationCalloutInfoDidChangeNotification;
4. typedef struct {
CLLocationDegrees latitudeDelta;
CLLocationDegrees longitudeDelta;
} MKCoordinateSpan;
补充一点,枚举型也需要添加前缀
typedef NS_OPTIONS(NSUInteger, MKDirectionsTransportType) {
MKDirectionsTransportTypeAutomobile = 1 << 0,
MKDirectionsTransportTypeWalking = 1 << 1,
MKDirectionsTransportTypeAny = 0x0FFFFFFF
} NS_ENUM_AVAILABLE(10_9, 7_0);
何时不需要?
翻译上述后半段,
对于oc的方法都不要加前缀因为他们的namespace已经被他们从属的类定义过了。
不要对struct内部的变量名加前缀。
1.- (instancetype)initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier;
2.typedef struct {
CLLocationDegrees latitudeDelta;
CLLocationDegrees longitudeDelta;
} MKCoordinateSpan;
嗯, 在真正开始分析变量和函数命名之前
本人英语水平有限,下面都是我通过现查总结的:
列举一下我们常见的词性:
再看一下句子成分:
来看主谓宾组合的句子
比如 I need you就是最最简单的主谓宾短句。
现在我们要着重介绍一个概念叫做宾语从句,在复合句中,由一个句子充当宾语,这个句子就叫做宾语从句。
比如: He is wondering when can he finish this difficult job. 'when can he finish this difficult job'就是一个句子起到了宾语的作用
还有一个是主系表
例如The problem is puzzling.
在重点看一个句子:The problem is when we can get a pay rise. ‘when we can get a pay rise’就是整句话作为表语
这两个句子很容易让我们联想到OC的方法, 如果调用者是主语的话,如果方法的目的是去做一件事,就像是主谓宾句式中的'谓语+宾语'部分。如果方法是去得到一个状态或者属性,方法本身又是起到"系动词+表语"的作用。
我们首先来看oc类中的变量,或者说属性(property)。 因为他代表的就是所属类的特征, 通常来说在命名上不需要明确的指出他从属关系,正常来说他们就是一些简单的名词动词形容词,或者说是相关的词组。
首先苹果默认的私有变量前加了一个下划线,我们通常不希望打破这个约定,所以一般这么做
@implementation MyClass {
BOOL _showsTitle;
}
首先也是最重要的一点是:明确
我们来看一些例子
在MKMapItem.h中:
@property (nonatomic, readonly) BOOL isCurrentLocation;
@property (nonatomic, copy) NSString *phoneNumber;
明确的意思是:用词准确清晰。 看上述给了我们很少的误解,可能你觉得我举得例子很牵强,那我们改换一种写法
@property (nonatomic, readonly) BOOL isCurrLocation;
@property (nonatomic, copy) NSString *phone;
看起来应该没有上面的舒服吧,很多时候造成命名不明确的就是我们喜欢缩写和省略词组的一部分。
那么问题来了,我们真的不能缩写了吗?! 苹果给了一个说法是这样的,有的时候用一下大家都明白的缩写也是可以滴。
为此,我们在文档中找到了一段神奇的内容:
我们多希望它能长一点呀,这样我们写代码的时候就可以少敲点键盘了。
作为一名机智的程序员, 我觉得可以扩充一下它。为此我们再去翻看苹果的代码。
在MKPolygon.h中我们看到一段:
+ (instancetype)polygonWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count;
注意到"coords"没有, 他就是coordinates的缩写。那么….问题又来了,到底何时用比较好,这个"度"在哪呢?
让我们尝试着总结一下:
随着我们分析到缩写和省略词组的一部分会造成困扰。他的反向的极端,也就是说命名的太罗嗦和用到很多无意义的词汇也是不好的。
我们举个例子吧
@property (nonatomic, copy) NSString *phoneNumberString;
@property (nonatomic, strong) NSArray *coordinateArray;
其实这一部分,我暂时也没有想到什么特别好的例子。上述的两行代码看起来命名不会对使用者造成什么困扰。 但是很多时候我们也会觉得他过于啰嗦,究其原因就是变量名的后半部分指代了他的类型。
从中我们认为这是一个约定。
上面说了过短和过长的不利影响,这些很容易被我们发现和改正, 下面我们谈一谈如何能准确的表达词义。
这一部分很难总结出什么规律, 它的好坏的决定因素在于,开发者自己对于业务理解的准确和专业名词(或者说英文水平的积累)。但是, 我们仍然要迎难而上。
下面我们会分三个维度来分析这个问题 词义, 词性, 词组
首先我们来看看,MKMapKit中对于词汇的使用, 看代码:
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
@property (strong) UIColor *strokeColor;
@property CGLineJoin lineJoin; // defaults to kCGLineJoinRound
@property (nonatomic, readonly, getter=isLoading) BOOL loading;
@property (nonatomic, getter=isSelected) BOOL selected;
@property (nonatomic, getter=isZoomEnabled) BOOL zoomEnabled;
@property (nonatomic) BOOL canShowCallout;
@property (nonatomic, getter=isDraggable) BOOL draggable NS_AVAILABLE(10_9, 4_0);
@property (strong, nonatomic) UIView *leftCalloutAccessoryView;
词义:
这部分取决于我们的经验和词汇积累,如果我们熟悉地理相关的开发我们就知道coordinate表示的是坐标, 而熟悉绘图框架的人就知道strokeColor表示的是画线的颜色。
词性:
在这里 变量一般表示三个意思:1.组成部分 2.状态。3.权限。
这里看到的东西其实很多脱离了词性的范畴, 看起来像是句子了, 在这里先不展开。
词组:
我们引入一个概念叫做偏正词组,它是由修饰语和中心语组成,结构成分之间有修饰与被修饰关系的词组,再引入一个他的分支:定中词组。 定中词组的修饰语是定语,充当中心语的一般是体词性成分,定语从领属、范围、质料、形式、性质、数量、用途、时间、处所等方面描写或限制中心语。我们来看leftCalloutAccessoryView, 这里的AccessoryView就从属于leftCallout。
很多时候 我们喜欢用一些指代不明的词汇。下面例举一些:
当这些单词单独出现在我们的程序当中时,常常让人手足无措。
说到这里,对于我们去命名变量的借鉴意义到底在哪呢。
有了之前对于内容的铺垫, 我们对程序中最小的单位变量在命名上有了一些参考。下面来看如何来命名一个合适的函数将他们组合起来。
有一点需要提的 objective-c是一门真真贴近自然语言的变成语言。所以当我们思考函数命名的时候考虑一下简单的翻译成句子是不是通畅是一个很重要的衡量标准。
来看一个简单的例子
- (MKAnnotationView *)viewForAnnotation:(id <MKAnnotation>)annotation;
这里我们看到MKMapView的一个方法,虽然他很短,我们也拆开来分析它。viewForAnnotation这一段由三个单词组成 view(名词), for(介词), annotation(名词)。 他们构成了一个介词词组,简单的翻译介词的意思就构成了 'annotation所对应的view',再加上这个方法的调用着,我们称它为mapView吧。他们共同构成了这样一个句子 ‘mapView上的一个annotation对应的view’。
我们再看它为何如此排列。在调用他的时候
[mapView viewForAnnotation:annotation]
返回值类型view写在开头,这能让我们很清楚的看到这个方法的返回值。参数写在单词annotation之后,标记他为一个annotation相关类型的参数。一切如此自然。在这里我们把这一类方法归为以名词开头
- (void)addOverlay:(id <MKOverlay>)overlay level:(MKOverlayLevel)level NS_AVAILABLE(10_9, 7_0);
- (NSArray *)overlaysInLevel:(MKOverlayLevel)level NS_AVAILABLE(10_9, 7_0);
- (void)insertOverlay:(id <MKOverlay>)overlay atIndex:(NSUInteger)index level:(MKOverlayLevel)level NS_AVAILABLE(10_9, 7_0);
第一个方法以动词开头,返回值为void。 分析一下他的组成 add(动词) + overlay(名词) + level(名词)。根据英文语法分析,这句话是不对的啊,在看下面第二个,用到了in level, 第三个函数对于index前也加了介词at。 目前看来这对我们分析出结构产生了不好的影响, 为此我专门查了一下level本身有一个动词含义是 '对准'。任性的apple工程师啊。。。
还有一种情况,我们引入NSFileManager.h文件:
- (BOOL)createSymbolicLinkAtURL:(NSURL *)url withDestinationURL:(NSURL *)destURL error:(NSError **)error NS_AVAILABLE(10_7, 5_0);
这个也是以动词开头,但是以BOOL作为返回值。
下面我们开始总结这样一种命名方式:
说到这里,有一个上面遗留的问题,以动词第三人称单数形式开头,看代码
@property (nonatomic) BOOL showsPointsOfInterest NS_AVAILABLE(10_9, 7_0);
@property (nonatomic) BOOL showsUserLocation;
对于这两个属性,我们更愿意理解他们为方法名而不是变量名。从功能上看他们的getter方法返回的是bool型,也就是返回一个状态。于是我们尝试着从语言的角度分析一下。 'mapView get whether it shows userLocation.',类似表语从句结构。 从这里看我们认为以动词第三人称单数开头方式开头的一般作为返回BOOL的命名方式。
前面提过对于返回BOOL型有几种写法:
对于前三种表示形容词含义的, 如果加上调用者那个名词凑成短句, 在前面加上is会非常清楚,比如: [mapView isZoomEnabled];
对于动词第三人称开头或者情态动词(can, should)开头则不需要这么做。
有一点需要注意的是,不要使用do,does一类无含义的情态动词
讨论完bool变量作为返回值的情况之后,我们来看看如果方法变量为一个bool值会如何命名:
- (void)selectAnnotation:(id <MKAnnotation>)annotation animated:(BOOL)animated;
回头看比如对形容词draggable或者从句canShowCallout这类setter方法我们无需讨论了。
就看这个animated,回到我们的编程命名习惯上,通常animate是当动词使用的,而animating一般表示正在进行中。在这里需要注意的是 animated在这里是作为形容词 释义为:“需要以动画方式进行”。
在苹果文档中,有一个原则 当使用分词的时候把它当做动词写到他修饰的词前面,而不要使用它的形容词形态放到后面
例子是这样的
- (BOOL)acceptsGlyphInfo; -------->Right
- (BOOL)glyphInfoAccepted; ------->Wrong
在oc的各种回调函数中 我们最常见的两个词是will和did,一个表示将要,一个表示已完成。这部分在回调函数中会经常出现。
前面我们说到: 介词(prep):介词是一种用来表示词与词、词与句之间的关系的虚词, 诸如 at,in,before,after,on,by,with等。
我们尝试着总结一下常用的介词和介词词组的使用
- (MKMapPoint)mapPointForPoint:(CGPoint)point NS_DEPRECATED_IOS(4_0, 7_0);
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)context NS_DEPRECATED_IOS(4_0, 7_0);
+ (instancetype)circleWithCenterCoordinate:(CLLocationCoordinate2D)coord
radius:(CLLocationDistance)radius;
- (instancetype)initWithCircle:(MKCircle *)circle;
- (CLLocationDistance)distanceFromString:(NSString *)distance;
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
fromEyeCoordinate:(CLLocationCoordinate2D)eyeCoordinate
eyeAltitude:(CLLocationDistance)eyeAltitude;
- (void)applyStrokePropertiesToContext:(CGContextRef)context
atZoomScale:(MKZoomScale)zoomScale;
- (void)fillPath:(CGPathRef)path inContext:(CGContextRef)context;
- (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(UIView *)view;
首先我们发现我们发现在多个参数并列没有用到and这类连词,and这类连词并不能对我理解代码产生任何帮助
我们总结了一些介词和相关词组
在实际的编程中, 常常忽略这些词的合适场景, 继而造成别人理解上的障碍。
来看 for与from他们都有类型转换的意思,在这里它们表示的都是"从。。。而来”。观察一些代码:
- (CGPoint)pointForMapPoint:(MKMapPoint)mapPoint;
- (MKMapPoint)mapPointForPoint:(CGPoint)point;
- (CGRect)rectForMapRect:(MKMapRect)mapRect;
- (MKMapRect)mapRectForRect:(CGRect)rect;
- (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate;
- (NSString *)stringFromDistance:(CLLocationDistance)distance;
- (CLLocationDistance)distanceFromString:(NSString *)distance;
// NSDateFormatter.h
- (NSString *)stringFromDate:(NSDate *)date;
- (NSDate *)dateFromString:(NSString *)string;
我们发现for一般用在坐标类型的转换函数中(他们格式类似,数值不同),而from多用在其他的类型转换上
再看看in,at
- (NSArray *)overlaysInLevel:(MKOverlayLevel)level NS_AVAILABLE(10_9, 7_0);
- (void)applyStrokePropertiesToContext:(CGContextRef)context
atZoomScale:(MKZoomScale)zoomScale;
- (void)insertOverlay:(id <MKOverlay>)overlay atIndex:(NSUInteger)index NS_AVAILABLE(10_9, 4_0);
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)context NS_DEPRECATED_IOS(4_0, 7_0);
这几个介词通常取决于他之后的单词, 这里记住几个词组吧。in level, in context, at zoom scale, at index。
再来看
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay;
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
for有"对于"的意思,所以两个对象有比较复杂关系的时候,通常用for将他们连接
再有
- (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(UIView *)view;
- (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view;
- (CGRect)convertRegion:(MKCoordinateRegion)region toRectToView:(UIView *)view;
- (MKCoordinateRegion)convertRect:(CGRect)rect toRegionFromView:(UIView *)view;
这部分之前打算放到"细节和例外情况"中去的,它们都以动词开头,但是都是有返回值的。 convert to这个词组表示一个转换动作, 他最后的from/to修饰前面第一个和第二个名词, 其实这个方法一度对我也造成了困扰。
最后看看with,这个词我们经常会看到,通常它代表的含义是"跟 … 一起”, 我们经常见到的init with, 包括MKCricle的 cricleWith…等。
此处引用苹果文档内容
回调有自己独特的结构
他的第一个参数是消息的发送者
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;```
冒号的位置应该放在委托对象之后,除非只有一个参数
我们不该
- (BOOL)application:(NSApplication *)sender OpenUntitledFile;
而应该
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
有一个例外情况是回调函数是一个通知引发的
- (void)windowDidChangeScreen:(NSNotification *)notification;
'did'和'will'经常用在回调函数当做,标记'已经发生'或者'将要发生'
- (void)browserDidScroll:(NSBrowser *)sender;
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;
'should'应用场景通常是询问代理行为是否应该发生,通常返回BOOL
- (BOOL)windowShouldClose:(id)sender;
什么时候在变量名前加new/a
- (void)setCoordinate:(CLLocationCoordinate2D)newCoordinate
- (void)setDragState:(MKAnnotationViewDragState)newDragState animated:(BOOL)animated NS_AVAILABLE(10_9, 4_2);
//NSString
- (void)setString:(NSString *)aString;
- (NSRange)rangeOfCharacterFromSet:(NSCharacterSet *)aSet;
以init开头时,我们通常是有返回值的,这是我们约定的一个习惯
对于方法命名来说:
本文旨在探讨 objective-c中变量和方法的命名。 对于相关C语言系统方法,常量的定义,在苹果官方文档(链接在下面第一个)中就有很好的介绍。在此就先不讨论了。
注:参考资料:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html
https://google-styleguide.googlecode.com/svn/trunk/objcguide.xml
http://cocoadevcentral.com/articles/000082.php