iOS编写高质量代码(三)

一、用前缀避免命名空间冲突

OC里没有命名空间的概念(namespace)。于是,我们需要给类加前缀,避免重名,避免发生命名冲突。当然,不仅是类名,一些全局变量和方法也需要加上适当的前缀加以区分。
所以,我们要:

  • 选择与公司、工程相关的前缀作为类名的前缀。
  • 为了避免重复引用第三方库带来的冲突,必要时也要为他们加上前缀区分。
二、提供“全能初始化方法”
  • 在类中提供一个全能初始化方法,并在文档中写明注释。其他的初始化方法全调用此全能初始化方法。
  • 好处:当类的结构发生改变或初始化逻辑发生改变时,只需要改动全能初始化方法即可。

举个例子来说:可以看一下NSDate类中定义了一个全能初始化方法

- (instancetype)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti NS_DESIGNATED_INITIALIZER;

其余的初始化方法 定义在NSDate (NSDateCreation) 分类中

- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs;
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)secs;
- (instancetype)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;

在官方文档中,关于NSDate有如下说明
解释:选定一个方法作为全能初始化方法,剩下的其余的初始化方法都调用这这个方法初始化,这样做的好处是以后如果初始化的逻辑更改了只需更改全能初始化方法,或者即使子类覆写的时候也只覆写全能初始化方法~

三、实现 description 方法

本条写的是通过覆写description(或者debugDescription)方法来在NSLog打印(或者LLDB打印时)输出更多的自定义信息。

下面举个例子:

- (NSString *)description {

    return [NSString stringWithFormat:@"<%@: %p, %@>",
            [self class],
            self,
            @{
              @"qi": _qi,
              @"share" : _share}
            ];
}
四、尽量使用不可变对象
  1. 声明对外属性时,尽量使用不可变对象,同时,对外属性声明里尽量加上readonly修饰(默认是readwrite修饰)。这样外部只能读取数据而不能修改数据,保证了这个类的实例所持有的数据更加安全。尤其是不要把可变的collection作为属性公开,而是应该提供相应的方法修改可变的collection。
  2. 若外部想修改修改对象的值有两种途径
  • 提供接口方法修改
  • 使用KVC(Key-Value Coding)技术

这种技术允许对象的数据或属性可以在运行时通过其键名进行查找,其中,属性的名称即为其值的键名。在静态语言中,这样的做法是不可能的。KVC大大的增加了设计的自由度:通过KVC,无需知道对象的类型即可访问其属性或数据。
例如:

不推荐写法

//Animals.h
@property (nonatomic, strong) NSSet *animals;

应改为:

//Animals.h
@interface Animals : NSObject

@property (nonatomic, strong, readonly) NSSet *animals;

- (void)addAnimal:(NSString *)animal;
- (void)removeAnimal:(NSString *)animal;

@end

//Animals.m
@implementation Animals {
    NSMutableSet *_mutableAnimals;
}

- (NSSet *)animals {
    return [_mutableAnimals copy];
}

- (void)addAnimal:(NSString *)animal {
    [_mutableAnimals addObject:animal];
}

- (void)removeAnimal:(NSString *)animal {
    [_mutableAnimals removeObject:animal];
}

但是,小编认为这样写固然有好处:保证了数据的安全性,但代码量也会提升不少。所以推荐大家可以有选择的使用,对一些重要的类才有使用必要。

另外,如果某属性仅可以在对象内部修改,则可以在.h文件中声明为readonly。然后 在.m的类扩展中将属性扩展为readwrite属性。

当然,师父说了:也可以在.h文件中把属性声明为readonly,在.m文件中通过实例变量修改值,当block内部修改值时,可以用self->实例变量的方法访问修改。(小编测试过,确实有效。欢迎路过的大神继续讨论)

五、使用清晰而协调的命名方式

师父语录:“写OC代码像是在讲故事,而读OC代码更像是在听故事。”
这句话要归功于OC清晰而协调的命名方式。

  • 首先,是驼峰式命名方法:这个和大部分编程语言都一样。
  • 其次,也是最关键的方法命名。从左至右读起来就像日常用语中的句子。

例如:我们想给初始化一个矩形,并给他的宽和高赋值。

// C++:
Rectangle *aRectangle = new Rectangle(5.0, 10.0);

// Objective-C:
Rectangle *aRectangle = [[Rectangle alloc] initWithWidth:5.0 andHeight:10.0];

很显然,OC的方法可以很直接的看出所要传递的参数的具体含义,而C++的传参就并没有这么直观。

六、为私有方法名加前缀

这一条:给大家参考一下我们QiShare团队制定的 iOS 代码规范
QiShare更喜欢通过#pragma mark -来区分 公私有等方法

#pragma mark - Private Functions

// code...


#pragma mark - Action functions

// code...


#pragma mark - Request functions

// code...


#pragma mark - xxxDataSource

// code...


#pragma mark - xxxDelegate

// code...
七、理解 Objective-C 错误模型

很多语言都有异常处理机制,Objective-C也不例外。@throw

但是,

注意:OC里抛异常会导致内存泄漏

解释:OC里的ARC机制(Automatic Reference Counting)在默认情况下是“无异常安全”。简单来说,一旦抛出异常,对象就无法正常自动释放了。
所以,

  1. 异常只用于处理严重的错误(fatal error,致命错误)
  2. 对于一些不那么严重的错误(nonfatal error,非致命错误),有两种解决方案:
  • 让对象返回nil或者0(例如:初始化的参数不合法,方法返回nil或0)
  • 使用NSError
八、理解 NSCopying协议

在iOS开发中,使用对象时经常需要拷贝它,这时我们会通过copy/mutableCopy方法完成。
如果我们想让自己的类支持拷贝,那就必须要实现NSCopying协议,该协议只有一个方法:

- (id)copyWithZone:(nullable NSZone *)zone;

当然,如果要求返回对象是可变的那就要实现NSMutableCopying协议,对应方法:

- (id)mutableCopyWithZone:(nullable NSZone *)zone;

同时,在拷贝对象时,要注意是执行浅拷贝还是深拷贝
那么引出了一个概念:什么是深拷贝?什么是浅拷贝?

  • 深拷贝:内容拷贝(既拷贝新的指针又拷贝出新的Object)
  • 浅拷贝:指针拷贝(仅拷贝新的指针指向原来的Object)

这里有张很经典的图解:
image.png

左边浅拷贝,右边深拷贝

深拷贝在拷贝对象时,会将指针所指的底层数据也拷贝一份。而浅拷贝只是创建了一个新的指针指向要拷贝的内容。一般情况下,尽量使用浅拷贝。
此外,还有一个注意点:

[NSMutableArray copy] 拷贝出 => NSArray (不可变)

[NSArray mutableCopy] 拷贝出 => NSMutableArray(可变)

这种操作可以在可变版本和不可变版本间切换。
说太多,不如给一个Demo~
下面请看小编准备的NSCopying协议相关的小Demo:
QiShareMember.h:

@interface QiShareMember : NSObject <NSCopying>

@property (nonatomic, copy, readonly) NSString *name; //!< 姓名
@property (nonatomic, copy, readonly) NSString *sex; //!< 性别
@property (nonatomic, assign, readonly) NSUInteger age; //!< 年龄

//! 初始化方法
- (instancetype)initWithName:(NSString *)name andSex:(NSString *)sex andAge:(NSUInteger)age;

- (void)addFriend:(QiShareMember *)friend;
- (void)removeFriend:(QiShareMember *)friend;

@end

QiShareMember.m:

@implementation QiShareMember {

    NSMutableSet *_friends;

}

- (instancetype)initWithName:(NSString *)name andSex:(NSString *)sex andAge:(NSUInteger)age {

    if (self = [super init]) {

        _name = [name copy];
        _sex = [sex copy];
        _age = age;
        _friends = [NSMutableSet new];
    }

    return self;
}

- (void)addFriend:(QiShareMember *)friend {

    [_friends addObject:friend];
}

- (void)removeFriend:(QiShareMember *)friend {

    [_friends removeObject:friend];
}

- (id)copyWithZone:(NSZone *)zone {

    QiShareMember *copy = [[[self class] allocWithZone:zone] initWithName:_name andSex:_sex andAge:_age];
    copy->_friends = [_friends mutableCopy]; //!< 注意friends只是一个实例变量不是一个属性,所以不能用点语法

    return copy;
}

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

推荐阅读更多精彩内容