0
点赞
收藏
分享

微信扫一扫

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

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

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

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

只有方法需要间接返回多个值的情况下才使用 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对象。
但是规范也意识到在现实的开发中,业已存在许多不符合规范的做法,出于务实的考虑,规范也对这一情况做出了额外的规定:

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

解决方法:

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

三、使用缩写

在设计编程接口时通常不应使用缩写,一些被广泛使用的缩写名称(比如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参数。


注意:设置完成最好先 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已经给出相应的警告:

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

用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;

总结

还是按规范行事吧

举报

相关推荐

0 条评论