3个经常被忽略的iOS编码规范

对于编程开发来说,编码规范是不可或缺的一个环节。在iOS开发领域,苹果也有官方的编码规范文档:《Coding Guidelines for Cocoa》。尽管对官方的这些权威指南,每一个iOS开发人员都应当去遵守,但在不少视频教程、文章、示例代码中,依然经常可以看到违反编码规范基本原则的情况。本文将列出3个经常被忽略的规范原则,希望大家在日常的开发中能留意纠正。

一、使用get开头的方法来返回数据

对于从其他语言转向iOS(尤其是从C++/Java转过来的)的开发人员,很容易犯这个错。在其他语言中,很多都习惯用getXXX作为getter方法,许多人也把这个习惯自然而然的带到了iOS,因此他们经常会在iOS中写下类似的代码:
- (XxxModel *)getXxxModel;
百度导航SDK:
+ (BNCoreServices*)GetInstance;
但iOS是个“异类”,明确规定不提倡这么做:

Use “get” only for methods that return objects and values indirectly. You should use this form for methods only when multiple items need to be returned.

只有方法需要间接返回多个值的情况下才使用 get。
那么所谓的“间接返回”指的是什么?比如下面的方法:

// NSString
- (void)getLineStart:(nullable NSUInteger *)startPtr end:(nullable NSUInteger *)lineEndPtr contentsEnd:(nullable NSUInteger *)contentsEndPtr forRange:(NSRange)range;
NSUInteger startPtr;
NSUInteger lineEndPtr;
NSUInteger contentsEndPtr;
NSString *testText = @"这是一段很长的测试文本\n这是第二段测试文本\n";
[testText getLineStart:&startPtr end:&lineEndPtr contentsEnd:&contentsEndPtr forRange:NSMakeRange(2, 5)];
NSLog(@"startPtr:%d lineEndPtr:%d contentsEndPtr:%d", startPtr, lineEndPtr, contentsEndPtr);
// 输出:
// startPtr:0 lineEndPtr:12 contentsEndPtr:11

可以看到,这个方法通过引用的方式,将行开头位置、行结束位置、行内容结束位置,这3个信息返回回来。这种做法可以说师承C语言,因为方法只能返回单个值,要返回多个值,就会用这样的变通方式来实现。

解决方法:

  1. 去掉get,这种方法最简单,也是苹果规范中提倡的方法
    - (XxxModel *)xxxModel;
    + (instancetype)instance;
  2. 使用其他单词来代替get,如:retrieve,但这种方法可能让人更不习惯
    - (XxxModel *)retrieveXxxModel;
    + (instancetype)retrieveInstance;

二、使用init开头的方法却不返回实例对象

有些开发人员习惯在ViewController中写类似下面的方法:
- (void)initData;
- (void)initViews;
另外,在第三方SDK中也经常会看到init开头的方法,比如百度导航SDK:
- (void)initServices:(NSString *)ak;
Objective-C Automatic Reference Counting (ARC)文档中提到了method family的概念,init开头的方法属于初始化系列的方法,规范上来说,应该要返回相应的Objective-C对象。
但是规范也意识到在现实的开发中,业已存在许多不符合规范的做法,出于务实的考虑,规范也对这一情况做出了额外的规定:

There are a fair number of existing methods with init-like selectors which nonetheless don’t follow the init conventions. Typically these are either accidental naming collisions or helper methods called during initialization.
......
Note that a method with an init-family selector which returns a non-Objective-C type (e.g. void) is perfectly well-formed; it simply isn’t in the init family.

也就是说,使用init开头的方法,但没有返回对象(返回值声明为void)将被视为普通方法,不会产生什么影响。

解决方法:

  1. 保留原样。这种命名方式规范上来说,会被视为普通方法,不会产生什么危害
  2. 用其他单词替换init,如setUp:setUpDatasetUpViews。虽然以init开头也没有问题,但不使用init来命名可以使自己的代码更加“iOS化”

三、使用缩写

In general, you shouldn’t abbreviate names when you design your programmatic interface (see General Principles). However, the abbreviations listed below are either well established or have been used in the past, and so you may continue to use them.
......
You may use abbreviations more freely in argument names (for example, “imageRep”, “col” (for “column”), “obj”, and “otherWin”).

在设计编程接口时通常不应使用缩写,一些被广泛使用的缩写名称(比如alloc、init)除外。而在方法参数中,则可以放宽限制,使用缩写。
然而在实际的开发中,经常可以看到缩写泛滥:

@property (nonatomic, strong) UIViewController *xxxCtrl;
@property (nonatomic, strong) NSString *oldPwd;

缩写的问题在于较难规范,如:password,有人用pw,有人用pwd,还有人用psd,这会导致有时看到缩写而不知其义的情况,只能结合上下文来判断。

解决方法

iOS中不提倡用缩写,SDK自带各种类的属性/方法多是完整拼出单词。我们在编码中也应遵守规范,尽量使用完整单词,而不是缩写。

不遵守规范的后果

诚然,不遵守规范通常不会导致什么灾难性的后果,规范更多是自觉自律、协作开发的要求。不过,对于iOS这个“异类”来说,有时不遵守规范却真的会导致灾难性的后果,只是这种情况现在并不常见。下面我将用一个例子来演示这种情况,如果你有兴趣,可以继续看下去。

首先,我们创建一个类TestCopyNameMethod,这个类有一个copyRightString方法:

// TestCopyNameMethod.h
@interface TestCopyNameMethod : NSObject
- (NSString *)copyRightString;
@end

// TestCopyNameMethod.m
#import "TestCopyNameMethod.h"

@implementation TestCopyNameMethod

- (NSString *)copyRightString {
    return [NSMutableString stringWithString:@"this is a copyright"];
}

@end

下面我们调用这个方法,运行一下:

TestCopyNameMethod *testCopyNameMethod = [[TestCopyNameMethod alloc] init];
NSString *text = [testCopyNameMethod copyRightString];
NSLog(@"%@", text);

此时,程序会正确输出相应的值,运行正常,没有崩溃,也没有内存泄露。

接下来,我们将TestCopyNameMethod.m编译设置为非ARC(MRC)方式,在编译设置中加上-fno-objc-arc参数。


在项目设置中对.m添加非ARC编译参数

注意:设置完成最好先 Clean 一下项目,否则可能有些文件编译有缓存,导致运行看不到效果
再次运行,可以看到程序崩溃了:

运行后崩溃了

只是将TestCopyNameMethod.m设置为MRC方式进行编译,为什么就会导致整个程序崩溃?
我们知道,即使是在MRC的时代,只要不是alloc、new、copy产生的对象,我们是不需要管理的。上面的代码中,copyRightString返回的是一个NSMutableString对象,而NSMutableString对象是调用stringWithString方法生成的,这个方法生成的对象是属于autorelease的,我们并不需要管理内存,按理说,程序不应该因此崩溃。那是什么原因导致的崩溃呢?
这跟我们混合使用ARC和MRC有关。
在ARC的机制中,对于调用copy开头的方法,ARC会认为这是要转移“所有权”的一个方法,因此它会自动在调用方法中插入相应的内存管理代码,这使得我们的调用代码就像:

TestCopyNameMethod *testCopyNameMethod = [[TestCopyNameMethod alloc] init];
NSString *text = [testCopyNameMethod copyRightString];
NSLog(@"%@", text);
[text release];

在最后多了一个release。
而TestCopyNameMethod.m已经被设置为MRC,copyRightString方法返回的对象则是一个autorelease对象,如果再做一次release就是过度释放,就导致程序崩溃。
注意:copyRightString中使用的是NSMutableString,不能使用NSString(即不能写成return @"this is a copyright";)。因为NSString本身是有“缓存”的,retain和release都是无效的(如果你用%lu的格式打出NSString的retainCount值,会发现这个值非常大)。
其实,在运行之前,如果先对项目进行Analyze,会发现Analyze已经给出相应的警告:

Analyze给出的警告

而如果我们反过来,TestCopyNameMethod.m使用默认的ARC,而把调用copyRightString的.m文件(如:ViewController.m)设置为MRC,则会导致内存泄露。
再次强调,改完编译参数先 Clean 一下项目,否则可能因为“缓存”看不到效果
运行Analyze会给出内存泄露的警告:

Analyze给出内存泄露警告

用Profile运行也会检测到内存泄露:


Profile运行时检测到的内存泄露

这是因为TestCopyNameMethod.m设置为ARC时,ARC看到copyRightString这样以copy开头的方法,就会在返回的NSMutableString对象上加上retain(因为返回的NSMutableString对象是属于autorelease的,而copy方法意味着要转移“所有权”,所以会加上retain)。所以,在ARC的作用下,copyRightString就变成类似:

return [[NSMutableString stringWithString:@"this is a copyright"] retain];

而对于我们的调用代码,因为设置成了MRC方式,编译器并不会自动帮我们插入内存管理的代码(即[text release];代码)。

NSString *text = [testCopyNameMethod copyRightString];

text本身由于ARC为copyRightString方法加上retain的缘故,已经拥有了返回对象的“所有权”,就必须负责相应的内存释放。而在MRC下,代码并没有做release,自然就导致内存泄露。

如果TestCopyNameMethod.m和调用代码都是ARC,则ARC在copyRightString方法加上了retain,又在调用代码中加上了release,正好在内存管理上达到了平衡,因此程序运行不会有问题。显然,现在的项目基本都是ARC的,所以,很少会遇到这方面的问题,但如果程序中使用的某些第三方库还是MRC的,就有可能导致问题。从中我们也看到了不遵守规范,有时确实会带来灾难性的后果。

对于这类问题的解决方法有两个:

  1. 修改方法名称,不要以copy开头
  2. 在.h文件的方法声明中,添加NS_RETURNS_NOT_RETAINED宏:- (NSString *)copyRightString NS_RETURNS_NOT_RETAINED;

总结

还是按规范行事吧

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容