Kivi

没有什么远大理想,只是永远都不会满足而已


  • 首页

  • 关于

  • 标签

  • 归档

编写高质量代码-改善Objective-C程序的61个建议

发表于 2016-03-02 更新于 2017-07-02 分类于 iOS 阅读次数:
本文字数: 6.6k 阅读时长 ≈ 6 分钟

###写在前面
自己看完这本书之后做的一点记录,收获还是有的,但是没有封面上吹的那么好,建议还是去看英文版的《Effective Object-C》系列的吧,如果能忍受住英语,还是不要看翻译过的了。

###让自己习惯Object-C

tip 1 Objective-C为一门动态开发语言

  • Objective-C是动态语言,C++是静态语言,runtime相当于是运行Objective-C的操作系统
  • 静态语言的执行效率和安全性要比动态语言高,但是不够简便
  • 运行时可以处理弱类型,函数存在检查等工作,确保了Object-C的灵活性

tip 2 在头文件中尽量减少其他头文件的引用

  • 尽可能使用@class,@class的使用也要求类名和.h文件名需要保持一致
  • 在.h文件中使用@class,在.m文件中使用#import可以减少不必要的编译时间,降低类之间的藕合度,还可以避免出现循环引用
  • 设计类的时候尽量多的使用协议,避免过多的使用#import引入不必要的部分
  • 如果头文件.h中有多个类的定义,尽量采用模块方式,只针对性引进所需要的类

tip 3 尽量使用const,enum来替换预处理#define

tip 4 优先使用对象字面量语法而非等效方法

  • 举个例子:@[@"string1", @"string2"]

tip 5 处理隐藏的返回值类型,优先选择实例类型(Instancetype)而非id

tip 6 尽量使用模块方式与多类建立复合关系

  • #include和#import其根本就是简单的复制粘贴,将目标.h文件中的内容一字不落的复制到当前文件中,后者可以避免多次的重复引用
  • 以预编译头文件的方式,虽然可以缩短编译时间,但是不易维护
  • 模块功能,其引用不仅仅表现在编译的速度加快,同时在链接框架等方面也非常好用
  • 启动模块功能后,编译器会隐式地把所有的#import转换为@import

tip 7 明解Object-C++中的有所为而有所不为

tip 8 C语言与Object-C语言的关系时充分而非必要条件

tip 9 高度警惕空指针和野指针的袭击

  • 没有存储任何内存地址的指针就称为空指针(NULL指针)
  • 野指针不是NULL指针,而是指向垃圾内存(不可用内存)的指针

tip 10 在64位环境下尽可能利用标记指针

tip 11 谨记兼容32位和64位环境下代码编写注意事项

tip 12 记清楚常量字符串和一般字符串的区别

  • 由于编译器优化,相同内容的常量字符串的地址值是完全相同的
  • 如果使用常量字符串来初始化一个字符串,那么这个字符串也是相同的常量
  • 对于常量字符串永远不要release

tip 13 在访问集合时要优先考虑使用快速枚举(for…in…)

  • 使用快速枚举,要尽可能使用枚举新写法
  • 和直接使用NSEnumerator相比,使用快速枚举可以更有效率
  • 使用快速枚举(for…in…)更安全,因为如果在枚举过程中,枚举会监控枚举对象的变化,如果发生变化,则会抛出一个异常
  • 多个枚举可以同时进行,枚举对象在循环中是不允许被修改的,可以使用break和continue来略过当次循环而进行到下一元素

tip 14 有序对象适宜存于数组,无序对象适宜存于集合(Set)

tip 15 存在公共键时,字典是在对象之间传递信息的最佳方式

tip 16 明智而谨慎地使用BOOL类型

  • BOOL在OC中被定义为unsigned char,这意味着它不仅仅有YES(1)和NO(0)两个值
  • 整形转为BOOL类型,使用三元操作符,保证返回YES和NO
  • 整形转换为BOOL的时候,要避免直接和YES比较
  • BOOL值进行逻辑运算(&& || !)不仅有效,而且可以保证返回值安全的转换为BOOL类型

###内存管理

tip 17 理解内存和OC内存管理规则

  • 内存是内存控制器和CPU之间的桥梁
  • OC内存管理模式基于对象的“所有权”上,任何对象都会被一个或者是多个使用者使用,只有当对象没有使用者的时候,系统才会自动销毁该对象
  • 对象所有权策略是基于引用计数实现的,每一个对象有一个retain count变量,当retain count减少到0时,对象被销毁

tip 18 内存管理讲究“好借好还,再借不难”

tip 19 区别开alloc init retain release dealloc之间的差异

  • alloc创建变量,dealloc释放变量,retain是引用计数+1,release是引用计数-1

tip 20 优先使用存取方法管理内存

tip 21 对象销毁或者被移除一定考虑所有权的释放

  • 释放对象前,一定要先释放对象的所有权
  • 从集合移除对象,集合要释放对被移除对象的所有权
  • 防止出现父对象被释放前而子对象没有被释放
  • 在OC中,是否负责对象的释放,要看如何获取的对象,即要看对象的所有权策略

tip 22 谨慎使用dealloc

###设计与声明

tip 23 编写代码要遵守CocoaAPI约定

tip 24 实例变量

  • 只加入一些绝对必要的实例变量,负责容易造成大的开销
  • 永远不要将变量设置为@public,这违反了封装的原则
  • 确保类基本属性对应的实例变量有存取方法

tip 25 透彻了解属性俄里里外外

  • 属性的动态性定义,需要用关键字@dynamic,属性动态性是相对于@synthesis而言的,不是有编译器自动生成setter和getter,而是在运行时添加的setter和getter
  • 属性采用动态性,与采用静态性相比,可简化代码的编写,便于代码的管理
  • 利用类拓展,可实现对属性的“篡改”

tip 26 存取方法是良好的类接口必要的组成部分

tip 27 明晓类公共领域的方法都是虚方法

  • 虚方法是多态的基础,实现机制时运行系统将根据对象的类型自动选择适当的具体实现运行,没有定位虚方法的方法不具备次性质
  • OC中所有的方法都是虚方法
  • 实现纯虚方法,可以用正式协议来实现,虚方法的不足是不知道哪些方法被重定义了,而正式协议可以保证协议中的方法都被实现

tip 28 初始化还是解码取决于是否支持归档和解档

  • 类的对象支持归档和解档,该类必须遵循NSCoding协议,必须实现对对象进行编码(encodeWithCoder)和解档(initWithCoder)的方法
  • 类的初始化方法和initWithCoder在角色上的并行性存在例外

tip 29 利用键-值机制访问类的私有成员变量的方法

  • KVC(key-value coding 键值编码)
  • KVO(key-value Observing 键值观察)
  • KVB(key-value binding 键值绑定)

tip 30 浅复制适宜指针 深复制适宜数据

浅复制:将原始对象的指针复制到副本中,原对象引用计数+1
深复制:复制指针所引用的数据,并将其赋给副本的实例变量

tip 31 明智而谨慎的使用NSCopying

tip 32 使用协议来实现匿名对象的提供

###实现

tip 33 使用类别(catagory)把类的实现拆分成不同的文件

  • 类别的方法具有最高的优先级

tip 34 明智地使用内省(instrospection)

主要就是说明下面几个函数的重要

  • class
  • superclass
  • isKindOfClass
  • respondsToSelector(这个很有用,常用判断delegate的某个方法是否已经实现)
  • conformsToProtocol
  • hash&isEqual
    • 实现hash方法必须返回一个整数
    • isEqual就是判断两个对象的hash是否相等
    • NSObject isEqual只是简单地检查指针是否相等

tip 35 尽量使用不可变对象,而不是可辨对象

  • 尽量不要把可变对象存储到集合对象中,否走容易导致存储的可变对象被破坏或变成无效
  • 可变对象在开销上比不可辨对象大,因为可变对象必须动态管理一个可变的辅助存储
  • 如果不确定对象是否可变,则把它当成不可变处理(保证程序的安全)

tip 36 利用复合能巧妙的将两个类或者对象融合

  • 在面向对象编程中,类与类,对象于对象之间的关系,一个是继承,一个是复合
  • 复合是通过在类中声明一个指向另外一个类对象的指针作为实例变量,从而将这两个类复合
  • OC中所有对象之间的交互都是通过指针实现的

tip 37 使用类拓展来隐藏实现的细节

  • 类拓展(extension)语法如下

    1
    2
    @interface className ()  
    @end
  • 类拓展从某种程度来说可以称之为匿名类别

  • 利用类拓展来隐藏私有信息
  • 可以在单独的头文件中声明类拓展,然后再需要用到的源文件中进行引入

tip 38 内联块中尽量少使用self,可能回形成循环引用

  • 内联块中能直接引用self,但是要注意避免导致循环引用

    • 独立的block

      1
      2
      3
      4
      5
      6
      void (^independentBlockObject)(void) = ^(void){  
      NSInteger localInteger = 10;
      NSLog(@"local integer = %ld",(long)localInteger);
      localInteger = 20;
      NSLog(@"local integer = %ld",(long)localInteger);
      };
    • 内联的block

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      //内联Block  
      - (void)simpleMethod
      {
      NSUInteger outsideVariable = 10;
      NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:@"obj1", @"obj2", nil];
      [array sortUsingComparator:^NSComparisonResult(id obj1, id obj2){
      NSInteger insideVariable = 20;
      NSLog(@"Outside variable = %lu", (unsigned long)outsideVariable); //在这个Block里不能使outsideVariable的值变化,如果要改变outsideVariable,可以在定义outsideVariable时加上__block存储类的前缀
      NSLog(@"Inside variable = %lu", (unsigned long)insideVariable);
      NSLog(@"self = %@", self);
      return NSOrderedSame;
      }];
      }
  • 避免由于在内联block中直接食用self导致的的循环引用,较好的方式,是用strong-weak-dance来解决

  • 在非内敛块中不能直接使用self,只能通过将self当成参数传递到块中才能使用,并且此时的self只能通过setter和getter方法访问属性,不能使用句点式语法

39 通过类别把方法添加到现有的类

40 通过强弱引用来管理对象的所有权

  • __weak对面是__strong,__strong是默认的
  • 父对象应该强引用子对象,子变量应该弱引用父对象
  • 只要该变量是在范围内,变量就会保持对对象的强引用,或者至它被重新分配给另外一个对象或nil
  • 对一些不支持弱引用的类,可以通过_unsafe_unretained引用来暗渡陈仓
  • 使用弱引用来避免循环retain

###继承与面向对象设计

tip 41 明确isa在继承上的作用

  • isa
    • Object-C对类对象与实例对象中的 isa 所指向的类结构作了不同的命名:类对象中的 isa 指向类结构被称作 metaclass,metaclass 存储类的static类成员变量与static类成员方法(+开头的方法);实例对象中的 isa 指向类结构称作 class(普通的),class 结构存储类的普通成员变量与普通成员方法(-开头的方法)。
  • 实例对象:类实例画得到的对象
  • 类对象,在objc中,类本身也是对象,称为类对象

tip 42 使用类别和协议实现类似多重继承的机制

  • 类别可以实现类的相关方法的模块化
  • 类别可以重载原始类的方法,但是不建议这么做
  • 类别的实现文件中不一定要全部实现生命的方法
  • 协议就是一系列不属于任何类的方法表

tip 43 类别和类拓展是类继承的延续性拓展

  • 类别是为类增加外部的方法
  • 类拓展用做类的内部拓展

tip 44 继承基类的实现行为勿忘调用super

  • 根据意图(补充或者是覆盖基类的方法)来决定super的使用

###设计模式与cocoa编程

tip 45 设计模式是特定环境下的特定问题的解决方案

  • 设计模式是特定环境下特定问题的解决方案
  • 设计模式是某种特定设计的模板或指导原则
  • 在某种意义上,具体设计是设计模式的一个实例化

tip 46 MVC模式是一种复合或聚合模式

tip 47 对象建模在数据库中也广泛使用

tip 48 类簇可简化框架的公开架构而又不减少功能的丰富性

  • 类簇基于抽象工厂设计模式
  • 类簇可以用于隐藏实现的详细细节,为调用着提供一个简单的接口
  • 类簇也可以有多个基类

tip 49 委托用于界面控制,数据源用于数据控制

  • 代理是一种对象,当向外委托任务的对象遇到程序中的事件时,它的委托可以代表它对事件进行处理,或者和它进行协调
  • 代理使一个对象有可能在没有集成的情况下改变另一个对象的行为
  • 数据源很像代理,不同的是代理处理的是界面的的逻辑,而数据源处理的是数据的逻辑

###定制init和dealloc

tip 50 了解对象的alloc和init

  • alloc方法使用应用程序默认的虚存区,区是一个按页对齐的内存区域,用于存放应用程序分配的对象和数据
  • alloc分配过程不仅进行对象内存的分配,还初始化对象两个非常小而非常重要的属性
  • 子类可以不采用带参数的初始化方法,而是实现一个简单的init方法,并在初始化后马上使用set存取方法,将对象设置为用用的初始状态
  • 工厂方法则可以避免为可能没有用的对象盲目分配内存

tip 51 直接访问实例变量的init方法

tip 52 初始化方法必须以init开头

tip 53 从init方法得到的对象可能不是想要的

  • init方法得到的对象可能不是正在被初始化的对象(单例)
  • init方法并不是一定能执行其他对象请求的初始化(出现错误)
  • 在创建对象时,通常应该子处理之前检查返回值是否为nil
  • 一旦对象被初始化了,就不应该再进行初始化,否则容易产生异常

tip 54 实现init…方法的唯一性或指定性并非不可能

  • 总是先调用基类的方法
  • 检查基类返回的对象
  • 在初始化实例变量时,如果他们是其他对象的引用,则在必要时进行保留和复制
  • 将实例变量设置为正当的初始值之后,就返回self,除了以下情况
    • 需要返回一个代替对象,此时,需要先释放新分配的对象
    • 某些问题导致不能成功初始化,

tip 55 init…方法有轻重级别之分

###写在后面
如果你觉得我写的不错,欢迎关注我的微信公众号


微信公众号
# iOS # read
和时间做朋友
iOS基础知识总结
kivi

kivi

nodejs | server
58 日志
17 分类
32 标签
RSS
© 2019 kivi | 173k | 2:37
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Pisces v7.3.0
|