XCode 常用快捷键

1. 工程导航器:Command+1
在光标定位到导航栏区域后按左右键可用于展开和合并目录
Command + 上下按钮用于在不显示具体文件的情况下移动选中文件
如果不按command 只按上下键 则边移动边选中文件

显示/隐藏实用工具面板:Command+Option+0
Command 1 - 8 对应导航栏面板上的8个按钮
显示/隐藏导航器面板:Command+0
c.command + shift + Y 隐藏调试栏

2. 快速查找某个文件 Command + Shift + O
在文件导航栏上定位文件 Command + Shift + J
快速跳转到类的特定行command + L
文件跳转栏:Control+6(键入方法/变量名+Enter跳转)
Show Related Items:Control + 1(注:可以查看光标所在方法的callers和callees)
类文件".h"与“.m”之间切换:control+command+↑/control+command+↓

3. 代码操作快捷键

注释代码: 快捷键:command + /
代码右缩进 : command + [
代码左缩进 : command + ]
快速创建文件 command + N
删除整行:先把光标移到行末,再操作Command + delete
向上/下 移动整行:Option + Command + [ / ]
双击某个分隔符(如()、[]、{} 等),Xcode会选中匹配代码块

在项目导航器中选中文件执行Option+左键点击操作。
搜索导航器(Find Navigator,也就是搜索):Command+Shift+F

3. 调试快捷键
运行app: Command + R
清除工程: Command + Shift + K
清除控制台打印信息:command+K
F6单步调试、F7跳入,F8继续
Command + \: 设置或取消断点
Command + Option + \: 允许或禁用当前断点

开发中常用的LLDB调试命令

启动                      run
调试可执行文件 lldb /Projects/Sketch/build/Debug/Sketch.app
调试运行时带参数的可执行文件 lldb -- DebugDemo.run [参数列表]
调试某个正在运行中的进程
1.启动lldb lldb
2.附到某个进程 process attach --pid 9939 或者 process attach --name Safari
查看代码 list 或者 l
看其他文件的代码 list 文件名 然后在用l来查看
看某个函数的代码 list main

breakpoint [断点管理]
breakpoint set --file foo.c --line 12
b main.m:127 推荐这种写法
breakpoint set --selector alignLeftEdges:
b functionName:
b +[NSSet setWithObject:]


breakpoint list
breakpoint enable <breakpointID>
breakpoint disable <breakpointID>
breakpoint delete <breakpointID>

调试
c[继续运行] n[Step over] s[Step into] finish[Step out]

变量输出
p 变量 [打印出某个变量的详细信息]
默认的格式 p 16
十六进制: p/x 16
二进制: p/t 16
po 变量 [打印出某个变量的简要信息]

p $0 = 23 [修改变量值]

[代码帧调试]
bt 当前栈信息 配合up down 指令使用
frame select 0 [查看某个栈代码]
frame variable [查看方法的调用者及方法名称]

[地址映射到代码]
image lookup -a 栈地址 寻找栈地址对应的代码位置

安装 LLDB插件 [chisel 及 LLDB]
![chisel](https://github.com/facebook/chisel)
![LLDB](https://github.com/DerekSelander/LLDB)

查看某个类或者实例的方法
methods IDLZipTool

taplog 点击控件,会打印控件的地址,大小及透明度等信息 后面跟的控件id可以用于后续操作
flicker 控件会闪烁
hide 0x7f7edd64b280 隐藏控件
show 0x7f7edd64b280 显示控件
border 0x7f7edd64b280 -c red -w 10 给控件加边框
pclass encryptStr 打印继承关系
presponder 0x7faa9455d2f0 打印响应链

给某个控件设置背景
(lldb) p 0x7faa9455d2f0
(long) $44 = 140370609820400
(lldb) p (void)[$44 setBackgroundColor:[UIColor redColor]]
(lldb) caflush
pviews 打印继承树

较好的文章推荐

[1]. LLDB调试利器及高级用法
[2]. 使用LLDB调试程序
[3]. Chisel-LLDB命令插件,让调试更Easy
[4]. GDB to LLDB command map
[5]. The LLDB Debugger
[6]. Debug on the iOS Simulator with LLDB
[7]. 跳舞吧!与LLDB共舞华尔兹

原谅我的懒 !!!

  1. 宏定义在 #define 那一行结束。如果你要书写多行,需要使用 \ 换行
  2. 只有在定义宏之后,宏才能生效
  3. 类函数宏 #define lang_init() c_init()
  4. 宏参数 #define min(A, B) ((A) < (B) ? (A) : (B))
  5. 如果宏的内容中有字符串,那么不会被宏参数替换:#define foo(x) x, “x” ===> foo(bar) → bar, “x”
  6. 一定要为宏参数添加括号
  7. 若宏的结果为值,则为整个宏添加括号
  8. 若宏替换的是代码块,则要为这段代码添加 do{…}while(0)
  9. 可以在宏参数前添加 #,将参数转换为字符串,字符串化会将参数中的所有字符(包括引号)都字符串化,如果中间有很多空格,字符串化后将只有一个空格。如果想通过 # 来实现参数值的字符串化,就需要需要使用两层的宏:
    #define IDL_NS_STR(x) IDL_INNER_NS_STR(x)
    #define IDL_INNER_NS_STR(x) @#x
    #define FOO 4

    IDL_INNER_NS_STR(FOO)
    "FOO"
    IDL_NS_STR(FOO)
    "4"

在使用 IDL_INNER_NS_STR 的时候,x 会立即被字符串化,而没有被宏展开。但是如果我们使用另外一个宏 IDL_NS_STR 嵌套着,那就会先展开,将值带入后,然后再字符串化。

  1. 使用”##”操作符可以实现宏中标记的连接。预处理器会将所有注释转为空格,## 会将左右的空格都忽略。
  2. 如果不确定有多少个宏参数,可以使用 … 代替,这在很多语言中都有类似的做法。相应的,使用 VA_ARGS 在具体的宏中,代替 …
    #define IDL_PRINTF(…) fprintf (stderr, ##__VA_ARGS__)

在加上 ## 之后,预处理器就会在传入空的时候,删掉前面的 , 了

  1. C语言中预定义宏:
__FILE__ :当前源代码的文件名(字符串)
__LINE__:当前源代码中的行号(整型)
__DATE__:进行预处理的日期(”Mmm dd yyyy”形式的字符串)
__TIME__:源文件编译时间(格式“hh:mm:ss”)
__FUNCTION__:同__func__(但IDE不支持),当前源代码的函数名
__PRETTY_FUNCTION__:同__FUNCITON__,但在g++下会输类名、函数名及其他函数信息

该文档的目的在于规范整个项目的代码,使得代码更具可读性,在草拟该文档的时候常照了较多较好的代码规范文档,这些文档将会罗列在该文档参考文献部分。大家如果在使用该文档过程中发现有任何的遗漏或者有更好的规范或者异议都欢迎联系本人(文档末尾有本人的联系邮箱)。

为什么需要有代码规范

一般一个项目代码通常是由一个团队共同开发维护的产物,随着业务的不断扩展,功能的快速迭代,代码会变得越来越庞大,如果这个时候不同的开发者的code风格千差万别,那么阅读代码将会是一件十分痛苦的事情。代码规范除了一些必须遵守的规则外,大多是没有对错而言,它只是让整个项目的代码看起开风格更加统一,我们知道人对熟悉的东西接受起来会快,对于熟悉的代码风格同样也是这个道理,这就是为每个项目拟定代码规范的意义所在.

该规范整体分成三类:

  • [命名规范]
  • [编码规范]

所有规范分成两个等级

  • [必须]
  • [建议]

目录

  1. 命名规范

    • 通用命名规范
    • 文件命名规范
    • 类命名规范
    • 方法命名规范
    • Getter/Setter命名规范
    • 属性,参数命名规范
    • Delegate 命名规范
    • Protocol命名规范
    • Catogries命名规范
    • 常量命名规范
    • Exception命名规范
    • Notification命名规范
  2. 编码规范

    • Initialize 规范
    • dealloc 规范
    • Block规范
    • Notification规范
    • Collection规范
    • 控制语句规范
    • 对象判等规范
    • 懒加载规范
    • 内存管理规范
  3. 项目设计规范

    • 源码注释规范
    • 文件导入规范
    • 代码布局规范
    • interface接口文件布局规范
    • 类设计规范

命名规范

通用命名规范
  • [必须] 命名必须具备见名知意的效果,禁止中文拼音,过度缩写,以及一切无意义的命名。
  • [必须] 除了通知和掩码常量外命名禁止自我指涉(在变量的末尾增加类型后缀)
  • [必须] 参数名、成员变量、局部变量、属性名都要采用小写字母开头的驼峰命名方式。如果方法名以一个众所周知的大写缩略词开始,可以不适用驼峰命名方式。比如FTP、WWW URL等。
  • [建议] 不同文件中或者不同类中具有相同功能或相似功能的属性的命名应该是相同的或者相似的。比如:count同时定义在NSDictionary、NSArray、NSSet这三个集合类中。且这三个集合类中的count属性都代表同一个意思,即集合中对象的个数。
  • [建议] 一般情况下,不要缩写或省略单词,建议拼写出来,即使它有点长。
文件命名规范

我们在刚拿到代码的时候首先会先看项目的目录结构,其次就是文件的组织,而了解文件的组织就是从文件名开始,所以文件命名也是一个非常重要的工作。

  • [必须] 分类文件必须使用分类所依附的主类名 + 分类名称的形式 比如:UIImage+NMAddition.h
  • [必须] 一般建议一个文件中只定义一个类,但是如果定义多个的时候,使用最主要的那个类的名称作为文件名。
  • [必须] 文件后缀选择:
    Extension    Type
    .h C/C++/Objective-C header file
    .m Objective-C implementation file
    .mm Objective-C++ implementation file
    .cc Pure C++ implementation file
    .c C implementation file
类命名规范
  • [必须] 类的名称应该由两部分组成,前缀+名称,前缀用大写字符,名称用大写开头的驼峰规则命名。
  • [建议] 前缀一般使用项目产品名的缩写,之所以不使用公司组织的缩写是因为一个公司有多个产品,使用公司名来作为前缀有可能导致重复。对于前缀一般使用多于两位的大写字符,因为苹果默认保留了两位字符的缩写前缀,但是这不是必须的。
方法命名规范
  • [必须] 方法名必须使用小写开头的驼峰命名方式,如果方法名以一个中所周知的大写缩略词开头,该规则可以忽略。
  • [必须] 一般类方法名不需要使用前缀,因为它们存在于特定类的命名空间中,私有方法可以使用统一的前缀来分组和辨识
  • [必须] 禁止在方法前面加下划线“ ”。Apple官网团队经常在方法前面加下划线”“。为了避免方法覆盖,导致不可预知的意外,禁止在方法前面加下划线。
  • [必须] 如果一个方法代表某个名词执行的动作,则该方法应该以一个动词开头。但不要使用“do”或者”does”作为方法名称的一部分,因为这些助动词不能为方法名称增加太多的意义,反而让方法看起来更加臃肿。同时,也请不要在动词前面使用副词或者形容词。
  • [建议] 方法实现时,如果参数过长,则令每个参数占用一行,以冒号对齐,在分行时,如果第一段名称过短,后续名称可以以Tab的长度(4个空格)为单位进行缩进,

    -(id)initWithModel:(IPCModle)model
    ConnectType:(IPCConnectType)connectType
    Resolution:(IPCResolution)resolution
    AuthName:(NSString *)authName
    Password:(NSString *)password
    MAC:(NSString *)mac
    AzIp:(NSString *)az_ip
    AzDns:(NSString *)az_dns
    Token:(NSString *)token
    Email:(NSString *)email
    Delegate:(id<IPCConnectHandlerDelegate>)delegate;



    - (void)short:(GTMFoo *)theFoo
    longKeyword:(NSRect)theRect
    evenLongerKeyword:(float)theInterval
    error:(NSError **)theError {
    ...

    }
  • [建议] 如果一个方法调用语句太长,需要对参数进行冒号对齐,如果只有一个冒号,但是还是太长了,可以换行,并且第二行与第一行的第二个字符对齐。

  • [必须] 只有在访问某个属性的时候使用”点访问,其他的使用空格调用。
  • [建议] 对输入参数的正确性和有效性进行检查,参数错误立即返回
  • [建议] 对于有返回值的方法,每一个分支都必须有返回值。
  • [必须] 禁止直接调用NSObject的类方法+new,也不要在子类中重载它。使用alloc和init方法

    self.productsRequest = [[SKProductsRequest alloc]   
    initWithProductIdentifiers:productIdentifiers];

  • [必须] 在方法定义的时候需要在-/+符号后面添加一个空格

    - (void)invokeWithTarget:(id)target;
    - (void)selectTabViewItem:(NSTabViewItem *)tabViewItem

  • [必须] 如果某个方法返回一个对象,那么名字应该使用返回对象的名字来命名

  • [必须] 如果方法返回接收者的某个属性,那么请直接以属性名作为方法名。如果方法间接的返回一个或多个值,我们可以使用“getxxx”的方式来命名方法。相反,无需额外的在方法名前面添加”get”。

    - (NSSize)cellSize;     OK
    - (NSSize)calcCellSize; 不OK
    - (NSSize)getCellSize; 不OK
  • [必须] 所有参数前面都应该添加关键字

  • [必须] 尽量使用”with”, “from”, and “to”,进行连接,请不要使用“and”连接接收者属性,但是如果方法描述了两个独立的动作,可以考虑使用“and”连接起来。
  • [必须] 方法定义keyword 和参数之间不能有空格
    可以写成这样
    - (void)setExample:(NSString *)text;
    不能写成这样
    - (void)setExample: (NSString *)text;
    - (void)setExample:(NSString *) text;

Getter/Setter命名规范
  • [建议] 如果属性是名词,推荐格式如下:

    - (type)noun;
    - (void)setNoun:(type)aNoun;
    例如:

    - (NSString *)title;
    - (void)setTitle:(NSString *)aTitle;

  • [建议] 如果某个属性或者变量的名称是一个形容词,可以省略is前缀,并在属性定义的时候使用getter来指定getter方法的名称。

    @property (assign, getter=isEditable) BOOL editable;
  • [建议] 如果属性是一个动词,动词使用一般现在时。推荐格式如下:

    - (BOOL)verbObject;
    - (void)setVerbObject:(BOOL)flag;
    例如:

    - (BOOL)showsAlpha;
    - (void)setShowsAlpha:(BOOL)flag;

  • [必须] 不要把动词的过去分词形式当做形容词来使用。

  • [建议] 可以使用情态动词(can、should、will等)明确方法意义,但不要使用do、does这类无意义的情态动词。
  • [建议] 只有方法间接的返回一个数值,或者需要多个数值需要被返回的时候,才有必要在方法名称中使用“get”。像这种接收多个参数的方法应该能够传入nil,因为调用者未必对每个参数都感兴趣
    - (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase;
属性,参数命名规范
  • [建议] 每个属性命名都加上类型后缀,也就是说变量的名称必须同时包含功能与类型,如,按钮就加上Button后缀,模型就加上Model后缀
  • [建议] 属性的关键字推荐按照 原子性,读写,内存管理的顺序排列。

    @property (nonatomic, readwrite, copy) NSString *name;
    @property (nonatomic, readonly, copy) NSString *gender;
    @property (nonatomic, readwrite, strong) UIView *headerView;
  • [必须] Block,NSString属性应该使用copy关键字

  • [必须] delegate 应该使用weak关键字
  • [必须] 禁止使用synthesize关键词
  • [建议] 如果是静态常量,仅限本类内使用的,加上前缀s,如果是整个工程共用,以sg为前缀。如:

    s_kMaxHeight; 
    sg_kMaxHeight;
  • [建议] 对于本地变量,在最靠京它们使用的地方声明,并且在它们声明的同时对其进行初始化

  • [建议] 由于32位和64位版本的大小不同,请避免使用long类型,NSInteger,NSUInteger和CGFloat,除非匹配系统接口。类型long,NSInteger,NSUInteger和CGFloat在32位和64位版本之间的大小不同。在处理由系统接口公开的值时,使用这些类型是合适的,但对于大多数其他计算应该避免使用它们。
  • [必须] 在使用整形变量存储ID之类的值时,需要使用uint32 uint64这一类,长度不会随着平台版本不同而不同的变量类型。
  • [必须] 在遇到遵循某个协议的对象定义的时候,不要在id和协议之间加空格
  • [必须] 在定义NSArray和NSDictionary时使用泛型,可以保证程序的安全性:
  • [必须] 当使用属性,对象实例变量,应该使用self.形式访问,这样可以确保调用的是具备Setter/Getter方法修饰过的属性值。但是在init和dealloc内建议使用类似_variableName形式的访问方式避免Setter/Getter 带来的副作用,更不要在Setter/Getter 方法中使用self.这样会导致死循环。
  • [必须] 在定义属性或者变量的时候指针应该归变量,也就是说建议使用:

    NSString *text

    而不是

    NSString* text
    NSString * text
  • [建议] 私有属性强烈建议写在类的空扩展中

    @interface IDLCodeStylePrivateDemo ()

    @property (nonatomic, strong, readwrite) NSString *privateProperty;

    @end
  • [必须] 对于对外只读属性仅在.h接口文件中定义并指定其读写属性为readonly,

    @interface IDLCodeStylePrivateDemo

    @property (nonatomic, strong, readonly) NSString *readonlyProperty;

    @end
  • [必须] 如果在实现内部需要修改该属性需要在空扩展内部添加读写属性为readwrite的同名属性:

    @interface IDLCodeStylePrivateDemo ()

    @property (nonatomic, strong, readwrite) NSString *readonlyProperty;

    @end
  • [必须] 当变量释放后,需要将变量置为nil,避免因为野指针引起的程序崩溃。

  • [必须] 变量在使用前应初始化,防止未初始化的变量被引用。
Delegate 方法命名规范
  • [建议] 用delegate做后缀,如 当你的委托的方法过多, 可以拆分数据部分和其他逻辑部分, 数据部分用dataSource做后缀. 如
  • [建议] 名称以标示发送消息的对象的类名开头,省略类名的前缀并⼩小写第⼀个字⺟

    - (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
    - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;

  • [建议] 除非delegate方法只有一个参数,即触发delegate方法调用的delegating对象,否则冒号是紧跟在类名后面的。

    - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
  • [建议] ⽤于通知委托对象操作即将发生或已经发⽣的方法名中要使⽤did或will

  • [建议] 用于询问委托对象可否执行某操作的⽅法名中可使⽤did或will,但最好使⽤should
    - (BOOL)windowShouldClose:(id)sender;
Protocol命名规范
  • [建议] 有时候protocol只是声明了一堆相关方法,并不关联class。这种不关联class的protocol使用ing形式以和class区分开来。比如NSLocking而非NSLock。
  • [建议] 如果proctocol不仅声明了一堆相关方法,还关联了某个class。这种关联class的protocol的命名取决于关联的class,然后再后面再加上protocol或delegate用于显示的声明这是一份协议。
  • [必须] 用optional修饰可以不实现的方法,用required修饰必须实现的方法
Catogries命名规范
  • [必须] category中不要声明属性和成员变量。
  • [必须] 避免category中的方法覆盖系统方法。可以使用前缀来区分系统方法和category方法。但前缀不要仅仅使用下划线”_“。
  • [必须] 如果一个类比较复杂,建议使用category的方式组织代码。具体可以参考UIView。
常量命名规范
  • [必须] 在代码中的常量必须抽成宏或者静态常量,避免使用硬编码内容
枚举常量
  • [必须] 使用枚举类型来表示一组相关的整型常量。
    typedef NS_ENUM(NSUInteger, VPLeftMenuTopItemType) {
    VPLeftMenuTopItemTypeMain = 0,
    VPLeftMenuTopItemTypeShows,
    VPLeftMenuTopItemTypeSchedule,
    VPLeftMenuTopItemTypeWatchLive,
    VPLeftMenuTopItemTypeMax,
    };



    typedef NS_ENUM(NSInteger, RBKGlobalConstants) {
    RBKPinSizeMin = 1,
    RBKPinSizeMax = 5,
    RBKPinCountMin = 100,
    RBKPinCountMax = 500,
    };
const常量
  • [必须] 使用const关键字创建浮点型常量。如果一个整型常量和其他常量不相关,可以使用const来创建,否则,使用枚举类型表示一组相关的整型常量。

    static const int kFileCount = 12;
    static NSString *const kUserKey = @"kUserKey";
  • [必须] 通常情况下,不要使用#define预处理命令创建常量。

  • [必须] 对于局限于某编译单元(实现文件)的常量,以字符k开头,例如kAnimationDuration,且需要以static const修饰

    推荐这样写:
    static const NSTimeInterval kFadeOutAnimationDuration = 0.4;
    不推荐这样写:
    static const NSTimeInterval fadeOutTime = 0.4;
  • [必须] 对于定义于类头文件的常量,外部可见,则以定义该常量所在类的类名开头,例如EOCViewClassAnimationDuration, 仿照苹果风格,在头文件中进行extern声明,在实现文件中定义其值

    例如 在头文件中声明如下定义
    extern const float EOCViewClassAnimationDuration;
    在实现文件中做如下声明
    const float EOCViewClassAnimationDuration = 18.0;
宏常量
  • [必须] #define 预处理定义的常量全部大写,单词间用 _ 分隔
  • [必须] 宏定义中如果包含表达式或变量,表达式或变量必须用小括号括起来。
Exception命名规范
  • [必须] 异常是用全局的NSString字符串进行标识。命名方式如下:
    [Prefix] + [异常模块] + [异常简要描述] + Exception
Notification命名规范
  • [必须] notification的命名使用全局的NSString字符串进行标识。命名方式如下:

    [Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification

    例如:
    NSApplicationDidBecomeActiveNotification
    NSWindowDidMiniaturizeNotification
    NSTextViewDidChangeSelectionNotification
    NSColorPanelColorDidChangeNotification
  • [必须] object通常是指发出notification的对象,如果在发送notification的同时要传递一些额外的信息,请使用userInfo,而不是object。

  • [必须] 如果某个通知是为了告知外界某个事件”即将”发生或者”已经”发生,则请在通知名称中使用“will”或者“did”这样的助动词。例
    如:
    UIKeyboardWillChangeFrameNotification;
    UIKeyboardDidChangeFrameNotification;

编码规范

Initialize 规范

initialize类方法先于其他的方法调用。且initialize方法给我们提供了一个让代码once、lazy执行的地方。initialize通常被用于设置class的版本号,initialize方法的调用遵循继承规则(所谓继承规则,简单来讲是指:子类方法中可以调用到父类的同名方法,即使没有调用[super xxx])。如果我们没有实现initialize方法,运行时初次调用这个类的时候,系统会沿着继承链(类继承体系),先后给继承链上游中的每个超类发送一条initialize消息,直到某个超类实现了initlialize方法,才会停止向上调用。因此,在运行时,某个类的initialize方法可能会被调用多次

  • [必须] 如果我们想要让initialize方法仅仅被调用一次,那么需要借助于GCD的dispatch_once()

    + (void)initialize {
    static dispatch_once_t onceToken = 0;
    dispatch_once(&onceToken, ^{
    // the initializing code
    }
    }
  • [建议] 如果我们想在继承体系的某个指定的类的initialize方法中执行一些初始化代码,可以使用类型检查和而非dispatch_once()

    if (self == [NSFoo class]) {
    // the initializing code
    }
  • [必须] initialize是由系统自动调用的方法,我们不应该显示或手动调用initialize方法

Init 规范

Objective-C有designated Initializers和secondary Initializers的概念:
指定初始化方法(designated initializer)是提供所有的(最多的)参数的初始化方法,间接初始化方法(secondary initializer)有一个或部分参数的初始化方法。一个类可以有一个或者多个designated Initializers。但是要保证所有的其他secondary initializers都要调用designated Initializers。即:只有designated Initializers才会存储对象的信息。这样的好处是:当这个类底层的某些数据存储机制发生变化时(可能是一些property的变更),只需要修改这个designated Initializers内部的代码即可。无需改动其他secondary Initializers初始化方法的代码。

  • [必须] 所有secondary 初始化方法都应该调用designated 初始化方法。
  • [必须] 所有子类的designated初始化方法都要调用父类的designated初始化方法。使这种调用关系沿着类的继承体系形成一条链。
  • [必须] 如果子类的designated初始化方法与超类的designated初始化方法不同,则子类应该覆写超类的designated初始化方法。(因为开发者很有可能直接调用超类的某个designated方法来初始化一个子类对象,这样也是合情合理的,但使用超类的方法初始化子类,可能会导致子类在初始化时缺失一些必要信息)。
  • [必须] 如果超类的某个初始化方法不适用于子类,则子类应该覆写这个超类的方法,并在其中抛出异常。
  • [必须] 禁止子类的designated初始化方法调用父类的secondary初始化方法。否则容易陷入方法调用死循环。
    如果想在当前类自定义一个新的全能初始化方法,则需要如下几个步骤:
  • 定义新的指定初始化方法,并确保调用了直接父类的初始化方法。
  • 重载直接父类的初始化方法,在内部调用新定义的指定初始化方法。
  • 为新的指定初始化方法写文档。
    重载父类的初始化方法并在内部调用新定义的指定初始化方法的原因是你不能确定调用者调用的就一定是你定义的这个新的指定初始化方法,而不是原来从父类继承来的指定初始化方法。假设你没有重载父类的指定初始化方法,而调用者却恰恰调用了父类的初始化方法。那么调用者可能永远都调用不到你自己定义的新指定初始化方法了。而如果你成功定义了一个新的指定初始化方法并能保证调用者一定能调用它,你最好要在文档中明确写出哪一个才是你定义的新初始化方法。或者你也可以使用编译器指令attribute((objc_designated_initializer))来标记它。

    // 超类
    @interface ParentObject : NSObject

    @end

    @implementation ParentObject

    //designated initializer
    - (instancetype)initWithURL:(NSString*)url title:(NSString*)title {
    if (self = [super init]) {
    _url = [url copy];
    _title = [title copy];
    }
    return self;
    }

    //secondary initializer
    - (instancetype)initWithURL:(NSString*)url {
    return [self initWithURL:url title:nil];
    }

    @end

    // 子类
    @interface ChildObject : ParentObject

    @end

    @implementation ChildObject
    //designated initializer
    - (instancetype)initWithURL:(NSString*)url title:(NSString*)title {
    //在designated intializer中调用 secondary initializer,错误的
    if (self = [super initWithURL:url]) {

    }
    return self;
    }
    @end

    @implementation ViewController
    - (void)viewDidLoad {
    [super viewDidLoad];
    // 这里会死循环
    ChildObject* child = [[ChildObject alloc] initWithURL:@"url" title:@"title"];
    }
    @end
  • [必须] 禁止在init方法中使用self.xxx的方式访问属性。如果存在继承的情况下,很有可能导致崩溃。原因见文章为什么不能在init和dealloc函数中使用accessor方法

  • [必须] 校验父类designated初始化方法返回的对象是否为nil。如果初始化当前对象的时候发生了错误,应该给予对应的处理:释放对象,并返回nil。
dealloc 规范
  • [必须] 不要忘记在dealloc方法中移除通知和KVO。
  • [建议] dealloc 方法应该放在实现文件的最上面,在任何类中,init 都应该直接放在 dealloc 方法的下面,如果有多个初始化方法,应该将指定初始化方法放在最前面,其他初始化方法放在其后。
  • [必须] 在dealloc方法中,禁止将self作为参数传递出去,如果self被retain住,到下个runloop周期再释放,则会造成多次释放crash。如下:

    - (void)dealloc{
    [self unsafeMethod:self];
    //因为当前已经在self这个指针所指向的对象的销毁阶段,销毁self所指向的对象已经在所难免。如果在unsafeMethod:中把self放到了autorelease poll中,那么self会被retain住,计划下个runloop周期在进行销毁。但是dealloc运行结束后,self所指向的对象的内存空间就直接被回收了,但是self这个指针还没有销毁(即没有被置为nil),导致self变成了一个名副其实的野指针。
    // 到了下一个runloop周期,因为self所指向的对象已经被销毁,会因为非法访问而造成crash问题。
    }

  • [必须] 和init方法一样,禁止在dealloc方法中使用self.xxx的方式访问属性。如果存在继承的情况下,很有可能导致崩溃。

Block规范
  • [必须] 调用block时需要对block判空。
  • [必须] 注意block潜在的引用循环。
  • [建议] 较短的block可以写在一行内。
  • [建议] 如果分行显示的话,block的右括号}应该和调用block那行代码的第一个非空字符对齐。block内的代码采用4个空格的缩进。
    如果block过于庞大,应该单独声明成一个变量来使用。^和(之间,^和{之间都没有空格,参数列表的右括号)和{之间有一个空格。
    //较短的block写在一行内
    [operation setCompletionBlock:^{ [self onOperationDone]; }];

    //分行书写的block,内部使用4空格缩进
    [operation setCompletionBlock:^{
    [self.delegate newDataAvailable];
    }];

    //使用C语言API调用的block遵循同样的书写规则
    dispatch_async(_fileIOQueue, ^{
    NSString* path = [self sessionFilePath];
    if (path) {
    // ...
    }
    });

    //较长的block关键字可以缩进后在新行书写,注意block的右括号'}'和调用block那行代码的第一个非空字符对齐
    [[SessionService sharedService]
    loadWindowWithCompletionBlock:^(SessionWindow *window) {
    if (window) {
    [self windowDidLoad:window];
    } else {
    [self errorLoadingWindow];
    }
    }];

    //较长的block参数列表同样可以缩进后在新行书写
    [[SessionService sharedService]
    loadWindowWithCompletionBlock:
    ^(SessionWindow *window) {
    if (window) {
    [self windowDidLoad:window];
    } else {
    [self errorLoadingWindow];
    }
    }];

    //庞大的block应该单独定义成变量使用
    void (^largeBlock)(void) = ^{
    // ...
    };
    [_operationQueue addOperationWithBlock:largeBlock];

    //在一个调用中使用多个block,注意到他们不是像方法那样通过':'对齐的,而是同时进行了4个空格的缩进
    [myObject doSomethingWith:arg1
    firstBlock:^(Foo *a) {
    // ...
    }
    secondBlock:^(Bar *b) {
    // ...
    }];
Notification规范
  • [必须] 当我们使用通知时,必须要思考,有没有更好的办法来代替这个通知。禁止遇到问题就想到通知,把通知作为备选项而非首选项。
  • [必须] post通知时,object通常是指发出notification的对象,如果在发送notification的同时要传递一些额外的信息,请使用userInfo,而不是object。
  • [必须] 在多线程应用中,Notification在哪个线程中post,就在哪个线程中被转发,而不一定是在注册观察者的那个线程中。如果post消息不在主线程,而接受消息的回调里做了UI操作,需要让其在主线程执行。
    说明:每个进程都会创建一个NotificationCenter,这个center通过NSNotificationCenter defaultCenter获取,当然也可以自己创建一个center。NoticiationCenter是以同步(非异步,当前线程,会等待,会阻塞)的方式发送请求。即,当post通知时,center会一直等待所有的observer都收到并且处理了通知才会返回到poster。如果需要异步发送通知,请使用notificationQueue,在一个多线程的应用中,通知会发送到所有的线程中。
Collection规范
  • [必须] 不要用一个可能为nil的对象初始化集合对象,否则可能会导致crash。
  • [必须] 对插入到集合对象里面的对象也要进行判空。
  • [必须] 注意在多线程环境下访问可变集合对象的问题,必要时应该加锁保护。不可变集合(比如NSArray)类默认是线程安全的,而可变集合类(比如NSMutableArray)不是线程安全的。
  • [必须] 禁止在多线程环境下直接访问可变集合对象中的元素。应该先对其进行copy,然后访问不可变集合对象内的元素。
  • [必须] 注意使用enumerateObjectsUsingBlock遍历集合对象中的对象时,关键字return的作用域。block中的return代表的是使当前的block返回,而非使当前的整个函数体返回。
  • [必须] 禁止返回mutable对象,禁止mutable对象作为入参传递。
  • [必须] 在访问集合的时候需要做内存操作越界判断
  • [建议] 应该使用可读性更好的字面量来构造NSArray,NSDictionary等数据结构,避免使用冗长的alloc,init方法。
  • [建议] 如果构造代码写在一行,需要在括号两端留有一个空格,使得被构造的元素于与构造语法区分开来:

    //正确,在字面量的"[]"或者"{}"两端留有空格
    NSArray *array = @[ [foo description], @"Another String", [bar description] ];
    NSDictionary *dict = @{ NSForegroundColorAttributeName : [NSColor redColor] };

    //不正确,不留有空格降低了可读性
    NSArray* array = @[[foo description], [bar description]];
    NSDictionary* dict = @{NSForegroundColorAttributeName: [NSColor redColor]};
  • [建议] 如果使用NSMutableDictionary作为缓存,建议使用NSCache代替。

    NSCache优于NSDictionary的几点:
    当系统资源将要耗尽时,NSCache具备自动删减缓冲的功能。并且还会先删减“最久未使用”的对象。
    NSCache不拷贝键,而是保留键。因为并不是所有的键都遵从拷贝协议(字典的键是必须要支持拷贝协议的,有局限性)。
    NSCache是线程安全的:不编写加锁代码的前提下,多个线程可以同时访问NSCache
  • [建议] 集合类使用泛型来指定对象的类型。

  • [必须] 取下标的时候要判断是否越界。
  • [建议] 取第一个元素或最后一个元素的时候使用firtstObject和lastObject
  • [建议] 如果构造代码不写在一行内,构造元素需要使用 两个空格 来进行缩进,右括号]或者}写在新的一行,并且与调用字面量那行代码的第一个非空字符对齐,构造字典时,字典的Key和Value与中间的冒号:都要留有一个空格,多行书写时,也可以冒号对齐:
    NSArray *array = @[
    @"This",
    @"is",
    @"an",
    @"array"
    ];

    NSDictionary *dictionary = @{
    NSFontAttributeName : [NSFont fontWithName:@"Helvetica-Bold" size:12],
    NSForegroundColorAttributeName : fontColor
    };
控制语句规范
  • [建议] 相关的赋值语句等号对齐

    promotionsEntity.promotionImageStr   = activityItemDict[@"promotion_image"];
    promotionsEntity.promotionIdNum = activityItemDict[@"promotion_id"];
    promotionsEntity.promotionNameStr = activityItemDict[@"promotion_name"];
    promotionsEntity.promotionColorStr = activityItemDict[@"promotion_color"];
  • [建议] if条件判断语句后面必须要加大括号{}。不然随着业务的发展和代码迭代,极有可能引起逻辑问题。

  • [必须] 条件过多,过长的时候应该换行。条件表达式如果很长,则需要将他们提取出来赋给一个BOOL值,或者抽取出一个方法
  • [必须] 不要使用过多的分支,要善于使用return来提前返回错误的情况,把最正确的情况放到最后返回。
  • [建议] 对于条件语句的真假,因为 nil 解析为 NO,所以没有必要在条件中与它进行比较。永远不要直接和 YES 和 NO进行比较,因为 YES 被定义为 1,而 BOOL 可以多达 8 位。
  • [必须]使用switch…case…语句的时候,不要丢掉default:。除非switch枚举。
  • [必须] switch…case…语句的每个case都要添加break关键字,避免出现fall-through。
  • [必须] 不可在for循环内修改循环变量,防止for循环失去控制。

    for (int index = 0; index < 10; index++){
    ...
    logicToChange(index)
    }
  • [建议] 在使用?:的时候不要嵌套多重?: 这里顺便提一个?: 另外一个用得比较多的场景–为某个属性添加默认值:

    dotImage = [_delegate pageControl:self selectedImageForDotAtIndex:i] ?: _selectedDotImage;
  • [必须] 在访问CGRect 的 x,y,width,height 属性的时候不要直接访问,建议使用CGRectGetxxxx方法。

  • [必须] 如果某个方法的错误传递方式包括引用和返回两种方式的时候,建议使用返回的那个值:
    建议:
    NSError *error;
    if (![self trySomethingWithError:&error]) {
    // Handle Error
    }

    不建议:

    NSError *error;
    [self trySomethingWithError:&error];
    if (error) {
    // Handle Error
    }
对象判等规范
 - (BOOL)isEqual:(id)object {
  if (self == object) {
  return YES; //判断内存地址
  } 
    if (![object isKindOfClass:[ZOCPerson class]]) { 
        return NO; //是否为当前类或派生类
    }                                                                      
    return [self isEqualToPerson:(ZOCPerson *)object];
}
//自定义的判断相等性的方法
- (BOOL)isEqualToPerson:(Person *)person { 
if (!person) { 
return NO;
}
BOOL namesMatch = (!self.name && !person.name) || [self.name isEqualToString:person.name];
BOOL birthdaysMatch = (!self.birthday && !person.birthday)
|| [self.birthday isEqualToDate:person.birthday];
return haveEqualNames && haveEqualBirthdays; 
}
懒加载规范
懒加载适合的场景:
一个对象在整个app过程中,可能被使用,也可能不被使用。
一个对象的创建需要经过大量的计算或者比较消耗性能。除以上情形之外,请不要使用懒加载。
  • [建议] 懒加载本质上就是延迟初始化某个对象,所以,懒加载仅仅是初始化一个对象,然后对这个对象的属性赋值。懒加载中不应该有其他的不必要的逻辑性的代码,如果有,请把那些逻辑性代码放到合适的地方。
  • [必须] 不要滥用懒加载,只对那些真正需要懒加载的对象采用懒加载。
  • [必须] 如果一个对象在懒加载后,某些场景下又被设置为nil。我们很难保证这个懒加载不被再次触发。
    内存管理规范
  • [必须] 函数体提前return时,要注意是否有对象没有被释放掉(常见于CF对象),避免造成内存泄露。
  • [建议] 请慎重使用单例,避免产生不必要的常驻内存。
  • [建议] 除非你清除的知道自己在做什么。否则不建议将UIView类的对象加入到NSArray、NSDictionary、NSSet中。如有需要可以添加到NSMapTable 和 NSHashTable。因为NSArray、NSDictionary、NSSet会对加入的对象做strong引用(即使你把加入的对象进行了weak)。而NSMapTable、NSHashTable会对加入的对象做weak引用。说明:简单的说,NSHashTable相当于weak的NSMutableArray;NSMapTable相当于weak的NSMutableDictionary.
其他规范
  • [必须] performSelector:withObject:afterDelay:要在有Runloop的线程里调用,否则调用无法生效。

项目设计规范

文件布局
-.h
-- 文件注释
-- #import // 导入类
-- NS_ENUM // 枚举类
-- @protocol // 代理
-- @interface // 文件入口
-- @property // 属性
-- methods // 方法

-.m
-- 文件注释
-- #import // 导入类
-- #define // 宏定义
-- static // 静态变量
-- @interface // 文件入口
-- @property // 属性
-- @implementation // 实现
-- methods // 方法

源码注释规范

优秀的代码大部分是可以自描述的,我们完全可以用代码本身来表达它到底在干什么,而不需要注释的辅助。
但并不是说一定不能写注释,有以下三种情况比较适合写注释:

  • 公共接口(注释要告诉阅读代码的人,当前类能实现什么功能)。
  • 涉及到比较深层专业知识的代码(注释要体现出实现原理和思想)。
  • 容易产生歧义的代码(但是严格来说,容易让人产生歧义的代码是不允许存在的)。
    除了上述这三种情况,如果别人只能依靠注释才能读懂你的代码的时候,就要反思代码出现了什么问题。
    最后,对于注释的内容,相对于“做了什么”,更应该说明“为什么这么做”。

  • [建议] 注释符与注释内容之间要用一个空格进行分割。

较好的注释例子

#import <Foundation/Foundation.h>

@class Bar;

/**
* 接口,分类,和协议都必须添加块注释用于说明该模块的作用
*/
@interface Foo : NSObject

/** 属性定义注释 */
@property(nonatomic) Bar *bar;

/**
* 方法说明
* See -initWithBar: for details about @c bar.
*
* @param bar The string for fooing.
* @return An instance of Foo.
*/
+ (instancetype)fooWithBar:(Bar *)bar;

/**
* Initializes and returns a Foo object using the provided Bar instance.
*
* @param bar A string that represents a thing that does a thing.
*/
- (instancetype)initWithBar:(Bar *)bar NS_DESIGNATED_INITIALIZER;

/**
* Does some work with @c blah.
*
* @param blah
* @return YES if the work was completed; NO otherwise.
*/
- (BOOL)doWorkWithBlah:(NSString *)blah;

@end

方法的注释使用Xcode自带注释快捷键:Commond+option+/

文件导入规范
  • [建议] 在类的头文件中尽量少引用其他头文件,有时,类A需要将类B的实例变量作为它公共API的属性。这个时候,我们不应该引入类B的头文件,而应该使用向前声明(forward declaring)使用class关键字,并且在A的实现文件引用B的头文件。
  • [必须] 使用import 导入Objective-C 和 Objective-C++ 头文件,使用include 来导入C/C++ 头文件
  • [建议] 优先导入框架的头文件,再导入自己的头文件,最后导入三方的头文件。每个类别之间使用一个空行隔开,每个类别的头文件导入中使用//类别进行分类。
  • [必须] 务必保持头文件导入没有多余的内容,不需要的头文件导入,切记删除。
  • [建议] 共同的接口、结构体、常量和数据类型要定义在同一个头文件里
代码布局规范

项目中的代码需要根据每个方法的具体功能使用#pragma mark - 进行分类,#pragma mark - 上空两行,下空一行

#pragma mark - Life cycle

- (void)dealloc {}
- (instancetype)init {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}

#pragma mark - Public methods

#pragma mark - Override methods

#pragma mark - Getters / Setters

#pragma mark - Others(Delegate/DataSource等)

#pragma mark - Private methods

  • [建议] 方法和方法之间建议使用一行空行隔开
  • [建议] 方法内部一类的代码之间不能有空行,一类代码之间需要添加空行来隔开,这样可以比较清晰

    - (void)awakeFromNib {
    UIStoryboard *signatureStoryboard = [UIStoryboard storyboardWithName:@"BBPopoverSignature" bundle:nil];
    self.signatureViewController = [signatureStoryboard instantiateViewControllerWithIdentifier:@"BBPopoverSignature"];
    self.signatureViewController.modalPresentationStyle = UIModalPresentationPopover;
    self.signatureViewController.preferredContentSize = CGSizeMake(BBPopoverSignatureWidth, BBPopoverSignatureHeight);
    self.signatureViewController.signatureImageView = self;

    UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(initiateSignatureCapture)];
    [self addGestureRecognizer:tapRecognizer];
    }
  • [必须] 代码块换行,建议使用

    if (user.isHappy) {
    //Do something
    } else {
    //Do something else
    }

    而不是

    if (user.isHappy)
    {
    //Do something
    }
    else {
    //Do something else
    }
  • [必须] 代码块冒号对齐,使用

    [UIView animateWithDuration:1.0 animations:^{
    // something
    } completion:^(BOOL finished) {
    // something
    }];

    而不是

    [UIView animateWithDuration:1.0
    animations:^{
    // something
    }
    completion:^(BOOL finished) {
    // something
    }];
  • [建议] 在类扩展和实现文件中保证一行的空行

    @interface MyClass ()

    // Properties - 在这里保证前后行有一个空行

    @end

    @implementation MyClass

    // Body - 在这里保证前后行有一个空行

    @end
    //这里也要留个空行
  • [建议] 在类声明中包含多个protocal,每个protocal占用一行并对齐。

    @interface CustomBackButtonViewController () <UITextFieldDelegate,
    MyProtocalDelegate,
    UITabBarControllerDelegate,
    UITabBarDelegate>
interface接口文件布局规范
  • [建议] interface接口的排列顺序建议如下:属性, 类方法, 初始化方法, 实例方法.
类设计规范
  • [建议] 尽量减少继承,类的继承关系不要超过3层。可以考虑使用category、protocol来代替继承。
  • [建议] 把一些稳定的、公共的变量或者方法抽取到父类中。子类尽量只维持父类所不具备的特性和功能。
  • [建议] .h文件中的属性尽量声明为只读。
  • [建议] .h文件中只暴露出一些必要的类、公开的方法、只读属性;私有类、私有方法和私有属性以及成员变量,尽量写在.m文件中。
  • [建议] 如果某个类的方法较多可以考虑把类的实现代码分散到便于管理的多个分类中

[参考文档]
The Objective-C Programming Language
Cocoa Fundamentals Guide
Coding Guidelines for Cocoa
iOS App Programming Guide
Raywenderlich Objective C Style Guide
Robots & Pencils
New York Times
Google
GitHub
Adium
Sam Soffes
CocoaDevCentral
Luke Redpath
Marcus Zarra
一份走心的iOS开发规范前言约定
Coding Guidelines for Cocoa

Core Graphics,也称为Quartz 2D,是一种先进的二维绘图引擎,可用于iOS,tvOS和macOS应用程序开发。Quartz 2D提供轻量级2D渲染,它与分辨率和设备无关,无论设备如何都可以输出具有较高保真度的图像。
Quartz 2D API易于使用,并提供对如透明层,基于路径的绘图,屏幕外渲染,高级颜色管理,消除锯齿渲染以及PDF文档创建,显示和解析等强大功能的访问。


谁应该阅读这份文件?
本文档适用于需要处理如下任务的开发人员:

  • 绘制图形
  • 在应用程序中提供图形编辑功能
  • 创建或显示位图图像
  • 使用PDF文档

本文件的组织
本文档分为以下章节:

  • Quartz 2D概述 描述了页面,绘图目的地,Quartz不透明数据类型,图形状态,坐标和内存管理,并且它介绍了Quartz如何在“引擎盖下”工作。
  • 图形上下文 描述了绘图目标的种类,并提供了创建所有图形上下文的逐步说明。
  • 路径 讨论构成路径的基本元素,显示如何创建和绘制它们,显示如何设置剪切区域,以及解释混合模式如何影响绘制。
  • 颜色和颜色空间讨论颜色值并使用透明度的alpha值,它描述了如何创建颜色空间,设置颜色,创建颜色对象和设置渲染意图。
  • 变换描述了当前的变换矩阵,并解释了如何对其进行修改,演示如何设置仿射变换,演示如何在用户和设备空间之间进行转换,并提供有关Quartz执行的数学运算的背景信息。
  • 模式定义了模式及其部分是什么,告诉Quartz如何呈现它们,并展示如何创建彩色和模板模式。
  • 阴影描述阴影是什么,解释它们是如何工作的,并展示如何用它们绘画。
  • 渐变讨论了轴向和径向渐变,并展示了如何创建和使用CGShading和CGGradient对象。
  • 透明度图层提供了透明层的外观示例,讨论了它们的工作原理,并提供了实施它们的逐步说明。
  • Quartz 2D中的数据管理讨论了如何将数据移入和移出Quartz。
  • 位图图像和图像蒙版描述了构成位图图像定义的内容,并展示了如何将位图图像用作Quartz绘图基元。它还描述了可以在图像上使用的遮罩技术,并显示了在绘制图像时使用混合模式可以实现的各种效果。
  • Core Graphics Layer 绘图描述了如何创建和使用绘图层以实现高性能图案绘制或绘制屏幕外。
  • PDF文档创建,查看和转换显示了如何打开和查看PDF文档,对其应用转换,创建PDF文件,访问PDF元数据,添加链接以及添加安全功能(如密码保护)。
  • PDF文档解析 描述了如何使用CGPDFScanner和CGPDFContentStream对象来解析和检查PDF文档。
  • PostScript Conversion 概述了可以在Mac OS X中将PostScript文件转换为PDF文档的功能。这些功能在iOS中不可用。
  • Text 描述了Quartz 2D对文本和字形的低级支持,以及提供更高级别和Unicode文本支持的替代方案。它还讨论了如何复制字体变体。
  • 词汇表定义了本指南中使用的术语。

Core Graphics Framework Reference为Quartz 2D应用程序编程接口提供了完整的参考。
色彩管理概述简要介绍了色彩感知,色彩空间和色彩管理系统的原理。