├── images ├── yoda.png ├── zen-logo-big.png ├── zen-logo-thumb.png └── blocks_debugger.png ├── LICENSE ├── scripts └── make_toc.rb ├── ABOUT_TRANSLATION.md ├── README.md └── README-En.md /images/yoda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oa414/objc-zen-book-cn/HEAD/images/yoda.png -------------------------------------------------------------------------------- /images/zen-logo-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oa414/objc-zen-book-cn/HEAD/images/zen-logo-big.png -------------------------------------------------------------------------------- /images/zen-logo-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oa414/objc-zen-book-cn/HEAD/images/zen-logo-thumb.png -------------------------------------------------------------------------------- /images/blocks_debugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oa414/objc-zen-book-cn/HEAD/images/blocks_debugger.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | "Zen and the Art of the Objective-C Craftsmanship" (c) by Luca Bernardi, Alberto De Bortoli 2 | 3 | "Zen and the Art of the Objective-C Craftsmanship" is licensed under a 4 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 5 | 6 | You should have received a copy of the license along with this 7 | work. If not, see . -------------------------------------------------------------------------------- /scripts/make_toc.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | File.open("../README.md", 'r') do |f| 4 | f.each_line do |line| 5 | forbidden_words = [ 6 | 'Table of contents', 7 | 'define', 8 | 'pragma', 9 | 'Zen and the Art of the Objective-C Craftsmanship'] 10 | 11 | next if !line.start_with?("#") || forbidden_words.any? { |w| line =~ /#{w}/ } 12 | 13 | title = line.gsub("#", "").strip 14 | href = title.gsub(" ", "-").downcase 15 | puts " " * (line.count("#")-1) + "* [#{title}](\##{href})" 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /ABOUT_TRANSLATION.md: -------------------------------------------------------------------------------- 1 | # ObjC Zen Book 中文翻译(禅与 Objective-C 编写的艺术) 2 | 3 | 4 | ![](./images/zen-logo-thumb.png) 5 | 6 | [ObjC Zen Book 原始链接](https://github.com/objc-zen/objc-zen-book) 7 | 8 | 9 | ObjC Zen Book 非常棒,是对 Objective-C 语言程序书写的进阶佳作,相比于其他风格指南,本书正如作者介绍的: 10 | 11 | > 现在虽然有很多指南,但是它们都是存在一些问题的。我们不想介绍一些死板的规定,我们想提供一个在不同开发者之间能达成一致的写代码的方法。随时间的推移,这本书开始转向面向如何设计和构建优秀的代码。 12 | 13 | 之前没有在网上看到相关的翻译,所以我希望能够通过翻译此书,惠及国内开发者朋友,同时也是对自己的锻炼。水平浅薄,有所不足之处请在 issue 批评指正,也欢迎各位朋友来贡献翻译。 14 | 15 | /translation 文件夹下是翻译过程中为了分出来的章节,*已经废弃* 16 | 17 | **如果有更好的修订和句子请直接 fork 本仓库,在 README.md 文件中修改,并申请 pull request 到 。** 18 | 19 | **本译文已取得原文作者授权允许翻译,并且将在进一步校对后,以 Fork 原仓库方式提交中文翻译的形式合并到官方仓库** 20 | 21 | ## 翻译标准 22 | 23 | 为了追求中文通顺,不影响原文意思的情况下,部分内容可能会改变一些描述,或者去掉一些英文的难以翻译的幽默。 24 | 25 | 此外,一些技术名词的翻译,可能没有明确的标准,但是会追求在这本小书内的统一,翻译的对应名词表格: 26 | 27 | |中文 | 英文 | 28 | | ----- | --------| 29 | |分类 | Categories | 30 | |协议 | Protocols | 31 | |接口 | interface | 32 | |实践 | practice | 33 | |签名 | signature | 34 | 35 | 36 | ## 排版规范和注意事项 37 | 38 | 39 | 40 | - 参考 41 | 42 | - 文章标题使用 \# 标记 43 | 章节标题从 \#\# 标记开始使用,按照层次每层增加一级,即 \#\#,\#\#\# 等。 44 | 代码可以在代码内容每行前使用四个空格开始,或是在代码顶格开始并前后使用三个 \`\`\` 符号。注意两种方式不要混用,另外无论使用哪种方式,代码缩进为4个空格。一行太长的代码(包括缩进80个字符以上)请注意在合适的地方进行换行和对齐。 45 | 46 | - 较为专业术语请保留英文原文,特别是对于普遍习惯使用的短语或者英文内容更容易理解的词。比如,虽然有很多人将 Key Value Observing 翻译为 键值观察,但是应该更偏向于选择 KVO 这样通用的缩略形式。另外,千万不要将诸如 Grand Central Dispatch 翻译为大中枢派发之类的闻所未闻的叫法,请将其简单地写作 GCD 即可。 47 | 48 | - 中文和英文混编时,如果一个英文单词/短语前后是中文字符,请留意随手在英文和中文之间留出一个半角空格,就像上面这条中做的这样 49 | 50 | - 中文正文及标题中出现的英文及数字应该使用半角方式输入,并且在左右各留一个半角空格。如果这些这些半角英文及数字的左边或者右边紧接着任何的中文全角括号或者其他标点符号的话,则不需要加入半角空格。 51 | 代码中的注释我们希望您也能顺手进行翻译。 52 | 53 | 54 | ## 进度 55 | 56 | 已经完成所有的翻译并做了自我校对。 57 | 58 | ## 作者 59 | 60 | 原作者: 61 | 62 | **Luca Bernardi** 63 | 64 | - http://lucabernardi.com 65 | - @luka_bernardi 66 | - http://github.com/lukabernardi 67 | 68 | **Alberto De Bortoli** 69 | 70 | - http://albertodebortoli.com 71 | - @albertodebo 72 | - http://github.com/albertodebortoli 73 | 74 | 翻译和校队: 75 | 76 | - 林翔宇 linxiangyu@nupter.org 77 | - 庞博 bopang@sohu-inc.com 78 | 79 | 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 禅与 Objective-C 编程艺术 (Zen and the Art of the Objective-C Craftsmanship 中文翻译) 2 | 3 | - 原文 4 | - Gitbook 链接(包含 PDF, mobi, epub 格式): (感谢 [yourtion](https://github.com/yourtion) 整理 ) 5 | 6 | 7 | ![](./images/zen-logo-thumb.png) 8 | 9 | 10 | * [前言](#前言) 11 | * [Swift](#swift) 12 | * [贡献给社区](#贡献给社区) 13 | * [作者](#作者) 14 | * [关于中文翻译](#关于中文翻译) 15 | * [条件语句](#条件语句) 16 | * [尤达表达式](#尤达表达式) 17 | * [nil 和 BOOL 检查](#nil-和-bool-检查) 18 | * [黄金大道](#黄金大道) 19 | * [复杂的表达式](#复杂的表达式) 20 | * [三元运算符](#三元运算符) 21 | * [错误处理](#错误处理) 22 | * [Case语句](#case语句) 23 | * [枚举类型](#枚举类型) 24 | * [命名](#命名) 25 | * [通用的约定](#通用的约定) 26 | * [常量](#常量) 27 | * [方法](#方法) 28 | * [字面值](#字面值) 29 | * [类](#类) 30 | * [类名](#类名) 31 | * [Initializer 和 dealloc](#initializer-和-dealloc) 32 | * [Designated 和 Secondary 初始化方法](#designated-和-secondary初始化方法) 33 | * [Designated Initializer](#designated-initializer) 34 | * [Secondary Initializer](#secondary-initializer) 35 | * [参考](#参考) 36 | * [instancetype](#instancetype) 37 | * [参考](#参考) 38 | * [初始化模式](#初始化模式) 39 | * [类簇 (class cluster)](#类簇-(class-cluster)) 40 | * [单例](#单例) 41 | * [属性](#属性) 42 | * [Init 和 Dealloc](#init-和-dealloc) 43 | * [点符号](#点符号) 44 | * [属性定义](#属性定义) 45 | * [私有属性](#私有属性) 46 | * [可变对象](#可变对象) 47 | * [懒加载 (Lazy Loading)](#懒加-(lazy-loading)) 48 | * [方法](#方法) 49 | * [参数断言](#参数断言) 50 | * [私有方法](#私有方法) 51 | * [相等性](#相等性) 52 | * [Categories](#categories) 53 | * [Protocols](#protocols) 54 | * [NSNotification](#nsnotification) 55 | * [美化代码](#美化代码) 56 | * [空格](#空格) 57 | * [换行](#换行) 58 | * [括号](#括号) 59 | * [代码组织](#代码组织) 60 | * [利用代码块](#利用代码块) 61 | * [Pragma](#pragma) 62 | * [Pragma Mark](#pragma-mark) 63 | * [忽略没用使用变量的编译警告](#忽略没用使用变量的编译警告) 64 | * [明确编译器警告和错误](#明确编译器警告和错误) 65 | * [字符串文档](#字符串文档) 66 | * [注释](#注释) 67 | * [头文档](#头文档) 68 | * [对象间的通讯](#对象间的通讯) 69 | * [Block](#blocks) 70 | * [深入 Block](#深入-block) 71 | * [self 的循环引用](#self-的循环引用) 72 | * [委托和数据源](#委托和数据源) 73 | * [继承](#继承) 74 | * [多重委托](#多重委托) 75 | * [面向切面编程](#面向切面编程) 76 | * [参考资料](#参考资料) 77 | * [其他的 Objective-C 风格指南](#其他的-objective-c-风格指南) 78 | 79 | # 前言 80 | 81 | 我们在 2013 年 11 月份开始写这本书,最初的目标是提供一份如何编写干净漂亮的 Objective-C 代码的指南:现在虽然有很多指南,但是它们都是有一些问题的。我们不想介绍一些死板的规定,我们想提供一个在开发者们之间写更一致的代码的途径。随时间的推移,这本书开始转向介绍如何设计和构建优秀的代码。 82 | 83 | 这本书的观点是代码不仅是可以编译的,同时应该是 “有效” 的。好的代码有一些特性:简明,自我解释,优秀的组织,良好的文档,良好的命名,优秀的设计以及可以被久经考验。 84 | 85 | 本书的一个理念是是代码的清晰性优先于性能,同时阐述为什么应该这么做。 86 | 87 | 虽然所有的代码都是 Objective-C 写的,但是一些主题是通用的,并且独立于编程语言。 88 | 89 | ## Swift 90 | 91 | 在 2014 年 6 月 6 日,苹果发布了面向 iOS 和 Mac 开发的新语言: Swift。 92 | 这个新语言与 Objective-C 截然不同。所以,我们改变了写这本书的计划。我们决定发布这本书当前的状态,而不是继续书写我们原来计划写下去的主题。 93 | 94 | Objective-C 没有消失,但是现在用一个慢慢失去关注的语言来继续写这本书并不是一个明智的选择。 95 | 96 | ## 贡献给社区 97 | 98 | 我们将这本书免费发布并且贡献给社区,因为我们希望提供给读者一些有价值的内容。如果你能学到至少一条最佳实践,我们的目的就达到了。 99 | 100 | 我们已经非常用心地打磨了这些文字,但是仍然可能有一些拼写或者其他错误。我们非常希望读者给我们一个反馈或者建议,以来改善本书。所以如果有什么问题的话,请联系我们。我们非常欢迎各种 pull-request。 101 | 102 | ## 作者 103 | 104 | **Luca Bernardi** 105 | 106 | - http://lucabernardi.com 107 | - @luka_bernardi 108 | - https://github.com/lukabernardi 109 | 110 | **Alberto De Bortoli** 111 | 112 | - http://albertodebortoli.com 113 | - @albertodebo 114 | - http://github.com/albertodebortoli 115 | 116 | ## 关于中文翻译 117 | 118 | 译者 119 | 120 | **林翔宇** 121 | - http://www.linxiangyu.org/ 122 | - linxiangyu@nupter.org 123 | - https://github.com/oa414 124 | 125 | **庞博** 126 | - bopang@sohu-inc.com 127 | - https://github.com/heistings 128 | 129 | **Kevin.Xiao** 130 | - kevinxiao1919@gmail.com 131 | - https://github.com/KevinHM 132 | 133 | 翻译已得到原作者许可,并且会在更加完善后申请合并到原文仓库。 134 | 135 | 136 | 137 | **部分译文表达可能存在不妥之处,非常欢迎各种修订建议和校对。 请直接 fork 本仓库,在 README.md 文件中修改,并申请 pull request 到 。** 138 | 139 | 140 | # 条件语句 141 | 142 | 条件语句体应该总是被大括号包围。尽管有时候你可以不使用大括号(比如,条件语句体只有一行内容),但是这样做会带来问题隐患。比如,增加一行代码时,你可能会误以为它是 if 语句体里面的。此外,更危险的是,如果把 if 后面的那行代码注释掉,之后的一行代码会成为 if 语句里的代码。 143 | 144 | 145 | **推荐:** 146 | 147 | ```objective-c 148 | if (!error) { 149 | return success; 150 | } 151 | ``` 152 | 153 | **不推荐:** 154 | 155 | ```objective-c 156 | if (!error) 157 | return success; 158 | ``` 159 | 160 | 和 161 | 162 | ```objective-c 163 | if (!error) return success; 164 | ``` 165 | 166 | 167 | 在 2014年2月 苹果的 SSL/TLS 实现里面发现了知名的 [goto fail](https://gotofail.com/) 错误。 168 | 169 | 代码在这里: 170 | 171 | 172 | ```objective-c 173 | static OSStatus 174 | SSLVerifySignedServerKeyExchange(SSLContext *ctx, bool isRsa, SSLBuffer signedParams, 175 | uint8_t *signature, UInt16 signatureLen) { 176 | OSStatus err; 177 | ... 178 | 179 | if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0) 180 | goto fail; 181 | if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0) 182 | goto fail; 183 | goto fail; 184 | if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0) 185 | goto fail; 186 | ... 187 | 188 | fail: 189 | SSLFreeBuffer(&signedHashes); 190 | SSLFreeBuffer(&hashCtx); 191 | return err; 192 | } 193 | ``` 194 | 195 | 196 | 显而易见,这里有没有括号包围的2行连续的 `goto fail;` 。我们当然不希望写出上面的代码导致错误。 197 | 198 | 此外,在其他条件语句里面也应该按照这种风格统一,这样更便于检查。 199 | 200 | ## 尤达表达式 201 | 202 | 203 | 不要使用尤达表达式。尤达表达式是指,拿一个常量去和变量比较而不是拿变量去和常量比较。它就像是在表达 “蓝色是不是天空的颜色” 或者 “高个是不是这个男人的属性” 而不是 “天空是不是蓝的” 或者 “这个男人是不是高个子的” 204 | 205 | ![Yoda](./images/yoda.png) 206 | 207 | (译者注:名字起源于星球大战中尤达大师的讲话方式,总是用倒装的语序) 208 | 209 | **推荐:** 210 | ```objective-c 211 | if ([myValue isEqual:@42]) { ... 212 | ``` 213 | 214 | **不推荐:** 215 | ```objective-c 216 | if ([@42 isEqual:myValue]) { ... 217 | ``` 218 | 219 | ## nil 和 BOOL 检查 220 | 221 | 222 | 类似于 Yoda 表达式,nil 检查的方式也是存在争议的。一些 notous 库像这样检查对象是否为 nil: 223 | 224 | ```objective-c 225 | if (nil == myValue) { ... 226 | ``` 227 | 228 | 或许有人会提出这是错的,因为在 nil 作为一个常量的情况下,这样做就像 Yoda 表达式了。 但是一些程序员这么做的原因是为了避免调试的困难,看下面的代码: 229 | 230 | ```objective-c 231 | if (myValue == nil) { ... 232 | ``` 233 | 234 | 如果程序员敲错成这样: 235 | 236 | ```objective-c 237 | if (myValue = nil) { ... 238 | ``` 239 | 240 | 241 | 这是合法的语句,但是即使你是一个丰富经验的程序员,即使盯着眼睛瞧上好多遍也很难调试出错误。但是如果把 nil 放在左边,因为它不能被赋值,所以就不会发生这样的错误。 如果程序员这样做,他/她就可以轻松检查出可能的原因,比一遍遍检查敲下的代码要好很多。 242 | 243 | 为了避免这些奇怪的问题,可以用感叹号来作为运算符。因为 nil 是 解释到 NO,所以没必要在条件语句里面把它和其他值比较。同时,不要直接把它和 `YES` 比较,因为 `YES` 的定义是 1, 而 `BOOL` 是 8 bit的,实际上是 char 类型。 244 | 245 | **推荐:** 246 | ```objective-c 247 | if (someObject) { ... 248 | if (![someObject boolValue]) { ... 249 | if (!someObject) { ... 250 | ``` 251 | 252 | **不推荐:** 253 | ```objective-c 254 | if (someObject == YES) { ... // Wrong 255 | if (myRawValue == YES) { ... // Never do this. 256 | if ([someObject boolValue] == NO) { ... 257 | ``` 258 | 同时这样也能提高一致性,以及提升可读性。 259 | 260 | ## 黄金大道 261 | 262 | 在使用条件语句编程时,代码的左边距应该是一条“黄金”或者“快乐”的大道。 也就是说,不要嵌套 `if` 语句。使用多个 return 可以避免增加循环的复杂度,并提高代码的可读性。因为方法的重要部分没有嵌套在分支里面,并且你可以很清楚地找到相关的代码。 263 | 264 | **推荐:** 265 | 266 | ```objective-c 267 | - (void)someMethod { 268 | if (![someOther boolValue]) { 269 | return; 270 | } 271 | 272 | // Do something important 273 | } 274 | ``` 275 | 276 | **不推荐:** 277 | 278 | ```objective-c 279 | - (void)someMethod { 280 | if ([someOther boolValue]) { 281 | // Do something important 282 | } 283 | } 284 | ``` 285 | ## 复杂的表达式 286 | 287 | 当你有一个复杂的 if 子句的时候,你应该把它们提取出来赋给一个 BOOL 变量,这样可以让逻辑更清楚,而且让每个子句的意义体现出来。 288 | 289 | 290 | ```objective-c 291 | BOOL nameContainsSwift = [sessionName containsString:@"Swift"]; 292 | BOOL isCurrentYear = [sessionDateCompontents year] == 2014; 293 | BOOL isSwiftSession = nameContainsSwift && isCurrentYear; 294 | 295 | if (isSwiftSession) { 296 | // Do something very cool 297 | } 298 | ``` 299 | 300 | ## 三元运算符 301 | 302 | 303 | 三元运算符 ? 应该只用在它能让代码更加清楚的地方。 一个条件语句的所有的变量应该是已经被求值了的。类似 if 语句,计算多个条件子句通常会让语句更加难以理解。或者可以把它们重构到实例变量里面。 304 | 305 | 306 | **推荐:** 307 | ```objective-c 308 | result = a > b ? x : y; 309 | ``` 310 | 311 | **不推荐:** 312 | ```objective-c 313 | result = a > b ? x = c > d ? c : d : y; 314 | ``` 315 | 316 | 当三元运算符的第二个参数(if 分支)返回和条件语句中已经检查的对象一样的对象的时候,下面的表达方式更灵巧: 317 | 318 | **推荐:** 319 | ```objective-c 320 | result = object ? : [self createObject]; 321 | ``` 322 | 323 | **不推荐:** 324 | ```objective-c 325 | result = object ? object : [self createObject]; 326 | ``` 327 | 328 | ## 错误处理 329 | 330 | 有些方法通过参数返回 error 的引用,使用这样的方法时应当检查方法的返回值,而非 error 的引用。 331 | 332 | **推荐:** 333 | ```objective-c 334 | NSError *error = nil; 335 | if (![self trySomethingWithError:&error]) { 336 | // Handle Error 337 | } 338 | ``` 339 | 340 | 此外,一些苹果的 API 在成功的情况下会对 error 参数(如果它非 NULL)写入垃圾值(garbage values),所以如果检查 error 的值可能导致错误 (甚至崩溃)。 341 | 342 | 343 | # Case语句 344 | 345 | 除非编译器强制要求,括号在 case 语句里面是不必要的。但是当一个 case 包含了多行语句的时候,需要加上括号。 346 | 347 | 348 | ```objective-c 349 | switch (condition) { 350 | case 1: 351 | // ... 352 | break; 353 | case 2: { 354 | // ... 355 | // Multi-line example using braces 356 | break; 357 | } 358 | case 3: 359 | // ... 360 | break; 361 | default: 362 | // ... 363 | break; 364 | } 365 | ``` 366 | 367 | 368 | 有时候可以使用 fall-through 在不同的 case 里面执行同一段代码。一个 fall-through 是指移除 case 语句的 “break” 然后让下面的 case 继续执行。 369 | 370 | ```objective-c 371 | switch (condition) { 372 | case 1: 373 | case 2: 374 | // code executed for values 1 and 2 375 | break; 376 | default: 377 | // ... 378 | break; 379 | } 380 | ``` 381 | 382 | 383 | 当在 switch 语句里面使用一个可枚举的变量的时候,`default` 是不必要的。比如: 384 | 385 | ```objective-c 386 | switch (menuType) { 387 | case ZOCEnumNone: 388 | // ... 389 | break; 390 | case ZOCEnumValue1: 391 | // ... 392 | break; 393 | case ZOCEnumValue2: 394 | // ... 395 | break; 396 | } 397 | ``` 398 | 399 | 400 | 此外,为了避免使用默认的 case,如果新的值加入到 enum,程序员会马上收到一个 warning 通知 401 | 402 | `Enumeration value 'ZOCEnumValue3' not handled in switch.(枚举类型 'ZOCEnumValue3' 没有被 switch 处理)` 403 | 404 | ### 枚举类型 405 | 406 | 407 | 当使用 `enum` 的时候,建议使用新的固定的基础类型定义,因为它有更强大的类型检查和代码补全。 SDK 现在有一个 宏来鼓励和促进使用固定类型定义 - `NS_ENUM()` 408 | 409 | **例子:** 410 | 411 | ```objective-c 412 | typedef NS_ENUM(NSUInteger, ZOCMachineState) { 413 | ZOCMachineStateNone, 414 | ZOCMachineStateIdle, 415 | ZOCMachineStateRunning, 416 | ZOCMachineStatePaused 417 | }; 418 | ``` 419 | 420 | # 命名 421 | 422 | ## 通用的约定 423 | 424 | 425 | 尽可能遵守 Apple 的命名约定,尤其是和 [内存管理规则](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html) ([NARC](http://stackoverflow.com/a/2865194/340508)) 相关的地方。 426 | 427 | 推荐使用长的、描述性的方法和变量名。 428 | 429 | **推荐:** 430 | ```objective-c 431 | UIButton *settingsButton; 432 | ``` 433 | 434 | **不推荐:** 435 | ```objective-c 436 | UIButton *setBut; 437 | ``` 438 | 439 | ## 常量 440 | 441 | 常量应该以驼峰法命名,并以相关类名作为前缀。 442 | 443 | **推荐:** 444 | ```objective-c 445 | static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4; 446 | ``` 447 | 448 | **不推荐:** 449 | ```objective-c 450 | static const NSTimeInterval fadeOutTime = 0.4; 451 | ``` 452 | 453 | 推荐使用常量来代替字符串字面值和数字,这样能够方便复用,而且可以快速修改而不需要查找和替换。常量应该用 `static` 声明为静态常量,而不要用 `#define`,除非它明确的作为一个宏来使用。 454 | 455 | **推荐:** 456 | 457 | ```objective-c 458 | static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification"; 459 | static const CGFloat ZOCImageThumbnailHeight = 50.0f; 460 | ``` 461 | 462 | **不推荐:** 463 | 464 | ```objective-c 465 | #define CompanyName @"Apple Inc." 466 | #define magicNumber 42 467 | ``` 468 | 469 | 常量应该在头文件中以这样的形式暴露给外部: 470 | 471 | ```objective-c 472 | extern NSString *const ZOCCacheControllerDidClearCacheNotification; 473 | ``` 474 | 并在实现文件中为它赋值。 475 | 476 | 只有公有的常量才需要添加命名空间作为前缀。尽管实现文件中私有常量的命名可以遵循另外一种模式,你仍旧可以遵循这个规则。 477 | 478 | 479 | ## 方法 480 | 481 | 方法名与方法类型 (`-`/`+` 符号)之间应该以空格间隔。方法段之间也应该以空格间隔(以符合 Apple 风格)。参数前应该总是有一个描述性的关键词。 482 | 483 | 尽可能少用 "and" 这个词。它不应该用来阐明有多个参数,比如下面的 `initWithWidth:height:` 这个例子: 484 | 485 | **推荐:** 486 | ```objective-c 487 | - (void)setExampleText:(NSString *)text image:(UIImage *)image; 488 | - (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag; 489 | - (id)viewWithTag:(NSInteger)tag; 490 | - (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height; 491 | ``` 492 | 493 | **不推荐:** 494 | 495 | ```objective-c 496 | - (void)setT:(NSString *)text i:(UIImage *)image; 497 | - (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag; 498 | - (id)taggedView:(NSInteger)tag; 499 | - (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height; 500 | - (instancetype)initWith:(int)width and:(int)height; // Never do this. 501 | ``` 502 | 503 | ## 字面值 504 | 505 | 使用字面值来创建不可变的 `NSString`, `NSDictionary`, `NSArray`, 和 `NSNumber` 对象。注意不要将 `nil` 传进 `NSArray` 和 `NSDictionary` 里,因为这样会导致崩溃。 506 | 507 | **例子:** 508 | 509 | ```objective-c 510 | NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"]; 511 | NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"}; 512 | NSNumber *shouldUseLiterals = @YES; 513 | NSNumber *buildingZIPCode = @10018; 514 | ``` 515 | 516 | **不要这样:** 517 | 518 | ```objective-c 519 | NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil]; 520 | NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil]; 521 | NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES]; 522 | NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018]; 523 | ``` 524 | 525 | 如果要用到这些类的可变副本,我们推荐使用 `NSMutableArray`, `NSMutableString` 这样的类。 526 | 527 | **应该避免**下面这样: 528 | 529 | ```objective-c 530 | NSMutableArray *aMutableArray = [@[] mutableCopy]; 531 | ``` 532 | 533 | 上面这种书写方式的效率和可读性的都存在问题。 534 | 535 | 效率方面,一个不必要的不可变对象被创建后立马被废弃了;虽然这并不会让你的 App 变慢(除非这个方法被频繁调用),但是确实没必要为了少打几个字而这样做。 536 | 537 | 可读性方面,存在两个问题:第一个问题是当你浏览代码并看见 `@[]` 的时候,你首先联想到的是 `NSArray` 实例,但是在这种情形下你需要停下来深思熟虑的检查;另一个问题是,一些新手以他的水平看到你的代码后可能会对这是一个可变对象还是一个不可变对象产生分歧。他/她可能不熟悉可变拷贝构造的含义(这并不是说这个知识不重要)。当然,不存在绝对的错误,我们只是讨论代码的可用性(包括可读性)。 538 | 539 | # 类 540 | 541 | ## 类名 542 | 543 | 类名应该以**三**个大写字母作为前缀(双字母前缀为 Apple 的类预留)。尽管这个规范看起来有些古怪,但是这样做可以减少 Objective-C 没有命名空间所带来的问题。 544 | 545 | 一些开发者在定义模型对象时并不遵循这个规范(对于 Core Data 对象,我们更应该遵循这个规范)。我们建议在定义 Core Data 对象时严格遵循这个约定,因为最终你可能需要把你的 Managed Object Model(托管对象模型)与其他(第三方库)的 MOMs(Managed Object Model)合并。 546 | 547 | 你可能注意到了,这本书里类的前缀(不仅仅是类,也包括公开的常量、Protocol 等的前缀)是`ZOC`。 548 | 549 | 另一个好的类的命名规范:当你创建一个子类的时候,你应该把说明性的部分放在前缀和父类名的在中间。 550 | 551 | 举个例子:如果你有一个 `ZOCNetworkClient` 类,子类的名字会是`ZOCTwitterNetworkClient` (注意 "Twitter" 在 "ZOC" 和 "NetworkClient" 之间); 按照这个约定, 一个`UIViewController` 的子类会是 `ZOCTimelineViewController`. 552 | 553 | 554 | ## Initializer 和 dealloc 555 | 556 | 推荐的代码组织方式是将 `dealloc` 方法放在实现文件的最前面(直接在 `@synthesize` 以及 `@dynamic` 之后),`init` 应该跟在 `dealloc` 方法后面。 557 | 558 | 如果有多个初始化方法, 指定初始化方法 (designated initializer) 应该放在最前面,间接初始化方法 (secondary initializer) 跟在后面,这样更有逻辑性。如今有了 ARC,dealloc 方法几乎不需要实现,不过把 init 和 dealloc 放在一起可以从视觉上强调它们是一对的。通常,在 init 方法中做的事情需要在 dealloc 方法中撤销。 559 | 560 | `init` 方法应该是这样的结构: 561 | 562 | ```objective-c 563 | - (instancetype)init { 564 | self = [super init]; // call the designated initializer 565 | if (self) { 566 | // Custom initialization 567 | } 568 | return self; 569 | } 570 | ``` 571 | 572 | 573 | 为什么设置 `self` 为 `[super init]` 的返回值,以及中间发生了什么呢?这是一个十分有趣的话题。 574 | 575 | 我们退一步讲:我们常常写 `[[NSObject alloc] init]` 这样的代码,从而淡化了 `alloc` 和 `init` 的区别。Objective-C 的这个特性叫做 *两步创建* 。 576 | 577 | 这意味着申请分配内存和初始化被分离成两步,`alloc` 和 `init`。 578 | - `alloc` 负责创建对象,这个过程包括分配足够的内存来保存对象,写入 `isa` 指针,初始化引用计数,以及重置所有实例变量。 579 | - `init` 负责初始化对象,这意味着使对象处于可用状态。这通常意味着为对象的实例变量赋予合理有用的值。 580 | 581 | `alloc` 方法将返回一个有效的未初始化的对象实例。每一个对这个实例发送的消息会被转换成一次 `objc_msgSend()` 函数的调用,形参 `self` 的实参是 `alloc` 返回的指针;这样 `self` 在所有方法的作用域内都能够被访问。 582 | 583 | 按照惯例,为了完成两步创建,新创建的实例第一个被调用的方法将是 `init` 方法。注意,`NSObject` 在实现 `init` 时,只是简单的返回了 `self`。 584 | 585 | 关于 `init` 的约定还有一个重要部分:这个方法可以(并且应该)通过返回 `nil` 来告诉调用者,初始化失败了;初始化可能会因为各种原因失败,比如一个输入的格式错误了,或者另一个需要的对象初始化失败了。 586 | 这样我们就能理解为什么总是需要调用 `self = [super init]`。如果你的父类说初始化自己的时候失败了,那么你必须假定你正处于一个不稳定的状态,因此在你的实现里不要继续你自己的初始化并且也返回 `nil`。如果不这样做,你可能会操作一个不可用的对象,它的行为是不可预测的,最终可能会导致你的程序崩溃。 587 | 588 | `init` 方法在被调用的时候可以通过重新给 `self` 重新赋值来返回另一个实例,而非调用的那个实例。例如[类簇](#类簇),还有一些 Cocoa 类为相等的(不可变的)对象返回同一个实例。 589 | 590 | ### Designated 和 Secondary 初始化方法 591 | 592 | Objective-C 有指定初始化方法(designated initializer)和间接(secondary initializer)初始化方法的观念。 593 | designated 初始化方法是提供所有的参数,secondary 初始化方法是一个或多个,并且提供一个或者更多的默认参数来调用 designated 初始化的初始化方法。 594 | 595 | ```objective-c 596 | @implementation ZOCEvent 597 | 598 | - (instancetype)initWithTitle:(NSString *)title 599 | date:(NSDate *)date 600 | location:(CLLocation *)location { 601 | self = [super init]; 602 | if (self) { 603 | _title = title; 604 | _date = date; 605 | _location = location; 606 | } 607 | return self; 608 | } 609 | 610 | - (instancetype)initWithTitle:(NSString *)title 611 | date:(NSDate *)date { 612 | return [self initWithTitle:title date:date location:nil]; 613 | } 614 | 615 | - (instancetype)initWithTitle:(NSString *)title { 616 | return [self initWithTitle:title date:[NSDate date] location:nil]; 617 | } 618 | 619 | @end 620 | ``` 621 | 622 | 623 | 624 | `initWithTitle:date:location:` 就是 designated 初始化方法,另外的两个是 secondary 初始化方法。因为它们仅仅是调用类实现的 designated 初始化方法 625 | 626 | #### Designated Initializer 627 | 628 | 629 | 一个类应该有且只有一个 designated 初始化方法,其他的初始化方法应该调用这个 designated 的初始化方法(虽然这个情况有一个例外) 630 | 631 | 这个分歧没有要求那个初始化函数需要被调用。 632 | 633 | 634 | 在类继承中调用任何 designated 初始化方法都是合法的,而且应该保证 *所有的* designated initializer 在类继承中是从祖先(通常是 `NSObject`)到你的类向下调用的。 635 | 636 | 实际上这意味着第一个执行的初始化代码是最远的祖先,然后从顶向下的类继承,所有类都有机会执行他们特定初始化代码。这样,你在做特定初始化工作前,所有从超类继承的东西都是不可用的状态。 虽然这没有明确的规定,但是所有 Apple 的框架都保证遵守这个约定,你的类也应该这样做。 637 | 638 | 639 | 当定义一个新类的时候有三个不同的方式: 640 | 641 | 1. 不需要重写任何初始化函数 642 | 2. 重写 designated initializer 643 | 3. 定义一个新的 designated initializer 644 | 645 | 646 | 第一个方案是最简单的:你不需要增加类的任何初始化逻辑,只需要依照父类的designated initializer。 647 | 648 | 当你希望提供额外的初始化逻辑的时候,你可以重写 designated initializer。你只需要重写直接超类的 designated initializer 并且确认你的实现调用了超类的方法。 649 | 650 | 一个典型的例子是你创造`UIViewController`子类的时候重写`initWithNibName:bundle:`方法。 651 | 652 | ```objective-c 653 | @implementation ZOCViewController 654 | 655 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { 656 | // call to the superclass designated initializer 657 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 658 | if (self) { 659 | // Custom initialization (自定义的初始化过程) 660 | } 661 | return self; 662 | } 663 | 664 | @end 665 | ``` 666 | 667 | 在 `UIViewController` 子类的例子里面如果重写 `init` 会是一个错误,这个情况下调用者会尝试调用 `initWithNib:bundle` 初始化你的类,你的类实现不会被调用。这同样违背了它应该是合法调用任何 designated initializer 的规则。 668 | 669 | 670 | 在你希望提供你自己的初始化函数的时候,你应该遵守这三个步骤来保证获得正确的行为: 671 | 672 | 673 | 1. 定义你的 designated initializer,确保调用了直接超类的 designated initializer。 674 | 2. 重写直接超类的 designated initializer。调用你的新的 designated initializer。 675 | 3. 为新的 designated initializer 写文档。 676 | 677 | 678 | 很多开发者忽略了后两步,这不仅仅是一个粗心的问题,而且这样违反了框架的规则,可能导致不确定的行为和bug。 679 | 让我们看看正确的实现的例子: 680 | 681 | ```objective-c 682 | @implementation ZOCNewsViewController 683 | 684 | - (id)initWithNews:(ZOCNews *)news { 685 | // call to the immediate superclass's designated initializer (调用直接超类的 designated initializer) 686 | self = [super initWithNibName:nil bundle:nil]; 687 | if (self) { 688 | _news = news; 689 | } 690 | return self; 691 | } 692 | 693 | // Override the immediate superclass's designated initializer (重写直接父类的 designated initializer) 694 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { 695 | // call the new designated initializer 696 | return [self initWithNews:nil]; 697 | } 698 | 699 | @end 700 | ``` 701 | 702 | 703 | 如果你没重写 `initWithNibName:bundle:` ,而且调用者决定用这个方法初始化你的类(这是完全合法的)。 `initWithNews:` 永远不会被调用,所以导致了不正确的初始化流程,你的类的特定初始化逻辑没有被执行。 704 | 705 | 706 | 707 | 708 | 即使可以推断那个方法是 designated initializer,也最好清晰地明确它(未来的你或者其他开发者在改代码的时候会感谢你的)。 709 | 710 | 你应该考虑来用这两个策略(不是互斥的):第一个是你在文档中明确哪一个初始化方法是 designated 的,你可以用编译器的指令 `__attribute__((objc_designated_initializer))` 来标记你的意图。 711 | 712 | 用这个编译指令的时候,编译器会来帮你。如果你的新的 designated initializer 没有调用超类的 designated initializer,那么编译器会发出警告。 713 | 714 | 然而,当没有调用类的 designated initializer 的时候(并且依次提供必要的参数),并且调用其他父类中的 designated initialize 的时候,会变成一个不可用的状态。参考之前的例子,当实例化一个 `ZOCNewsViewController` 展示一个新闻而那条新闻没有展示的话,就会毫无意义。这个情况下你应该只需要让其他的 designated initializer 失效,来强制调用一个非常特别的 designated initializer。通过使用另外一个编译器指令 `__attribute__((unavailable("Invoke the designated initializer"))) ` 来修饰一个方法,通过这个属性,会让你在试图调用这个方法的时候产生一个编译错误。 715 | 716 | 这是之前的例子相关的实现的头文件(这里使用宏来让代码没有那么啰嗦) 717 | 718 | ```objective-c 719 | 720 | @interface ZOCNewsViewController : UIViewController 721 | 722 | - (instancetype)initWithNews:(ZOCNews *)news ZOC_DESIGNATED_INITIALIZER; 723 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil ZOC_UNAVAILABLE_INSTEAD(initWithNews:); 724 | - (instancetype)init ZOC_UNAVAILABLE_INSTEAD(initWithNews:); 725 | 726 | @end 727 | 728 | ``` 729 | 730 | 731 | 上述的一个推论是:你应该永远不从 designated initializer 里面调用一个 secondary initializer (如果secondary initializer 遵守约定,它会调用 designated initializer)。如果这样,调用很可能会调用一个子类重写的 init 方法并且陷入无限递归之中。 732 | 733 | 734 | 不过一个例外是一个对象是否遵守 `NSCoding` 协议,并且它通过方法 `initWithCoder:` 初始化。 735 | 我们应该看超类是否符合 `NSCoding` 协议来区别对待。 736 | 符合的时候,如果你只是调用 `[super initWithCoder:]` ,你可能需要在 designated initializer 里面写一些通用的初始化代码,处理这种情况的一个好方法是把这些代码放在私有方法里面(比如 `p_commonInit` )。 737 | 当你的超类不符合 `NSCoding` 协议的时候,推荐把 `initWithCoder:` 作为 secondary initializer 来对待,并且调用 `self` 的 designated initializer。 注意这违反了 Apple 写在 [Archives and Serializations Programming Guide](https://developer.apple.com/library/mac/documentation/cocoa/Conceptual/Archiving/Articles/codingobjects.html#//apple_ref/doc/uid/20000948-BCIHBJDE) 上面的规定: 738 | 739 | 740 | > the object should first invoke its superclass's designated initializer to initialize inherited state(对象总是应该首先调用超类的 designated initializer 来初始化继承的状态) 741 | 742 | 743 | 如果你的类不是 `NSObject` 的直接子类,这样做的话,会导致不可预测的行为。 744 | 745 | #### Secondary Initializer 746 | 747 | 748 | 正如之前的描述,secondary initializer 是一种提供默认值、行为到 designated initializer的方法。也就是说,在这样的方法里面你不应该有初始化实例变量的操作,并且你应该一直假设这个方法不会得到调用。我们保证的是唯一被调用的方法是 designated initializer。 749 | 这意味着你的 secondary initializer 总是应该调用 Designated initializer 或者你自定义(上面的第三种情况:自定义Designated initializer)的 `self`的 designated initializer。有时候,因为错误,可能打成了 `super`,这样会导致不符合上面提及的初始化顺序(在这个特别的例子里面,是跳过当前类的初始化) 750 | 751 | ##### 参考 752 | 753 | - https://developer.apple.com/library/ios/Documentation/General/Conceptual/DevPedia-CocoaCore/ObjectCreation.html 754 | - https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaEncyclopedia/Initialization/Initialization.html 755 | - https://developer.apple.com/library/ios/Documentation/General/Conceptual/DevPedia-CocoaCore/MultipleInitializers.html 756 | - https://blog.twitter.com/2014/how-to-objective-c-initializer-patterns 757 | 758 | 759 | 760 | ### instancetype 761 | 762 | 763 | 我们经常忽略 Cocoa 充满了约定,并且这些约定可以帮助编译器变得更加聪明。无论编译器是否遭遇 `alloc` 或者 `init` 方法,他会知道,即使返回类型都是 `id` ,这些方法总是返回接受到的类类型的实例。因此,它允许编译器进行类型检查。(比如,检查方法返回的类型是否合法)。Clang的这个好处来自于 [related result type](http://clang.llvm.org/docs/LanguageExtensions.html#related-result-types), 意味着: 764 | 765 | 766 | > messages sent to one of alloc and init methods will have the same static type as the instance of the receiver class (发送到 alloc 或者 init 方法的消息会有同样的静态类型检查是否为接受类的实例。) 767 | 768 | 769 | 更多的关于这个自动定义相关返回类型的约定请查看 Clang Language Extensions guide 的[appropriate section]((http://clang.llvm.org/docs/LanguageExtensions.html#related-result-types)) 770 | 771 | 772 | 一个相关的返回类型可以明确地规定用 `instancetype` 关键字作为返回类型,并且它可以在一些工厂方法或者构造器方法的场景下很有用。它可以提示编译器正确地检查类型,并且更加重要的是,这同时适用于它的子类。 773 | 774 | ```objective-c 775 | @interface ZOCPerson 776 | + (instancetype)personWithName:(NSString *)name; 777 | @end 778 | ``` 779 | 780 | 虽然如此,根据 clang 的定义,`id` 可以被编译器提升到 `instancetype` 。在 `alloc` 或者 `init` 中,我们强烈建议对所有返回类的实例的类方法和实例方法使用 `instancetype` 类型。 781 | 782 | 783 | 在你的 API 中要构成习惯以及保持始终如一的,此外,通过对你代码的小调整你可以提高可读性:在简单的浏览的时候你可以区分哪些方法是返回你类的实例的。你以后会感谢这些注意过的小细节的。 784 | ##### 参考 785 | - http://tewha.net/2013/02/why-you-should-use-instancetype-instead-of-id/ 786 | - http://tewha.net/2013/01/when-is-id-promoted-to-instancetype/ 787 | - http://clang.llvm.org/docs/LanguageExtensions.html#related-result-types 788 | - http://nshipster.com/instancetype/ 789 | 790 | ------------------- 791 | 792 | ### 初始化模式 793 | 794 | #### 类簇 (class cluster) 795 | 796 | 类簇在Apple的文档中这样描述: 797 | 798 | > an architecture that groups a number of private, concrete subclasses under a public, abstract superclass. (一个在共有的抽象超类下设置一组私有子类的架构) 799 | 800 | 如果这个描述听起来很熟悉,说明你的直觉是对的。 Class cluster 是 Apple 对[抽象工厂](https://en.wikipedia.org/wiki/Abstract_factory_pattern)设计模式的称呼。 801 | 802 | 803 | 804 | class cluster 的想法很简单: 使用信息进行(类的)初始化处理期间,会使用一个抽象类(通常作为初始化方法的参数或者判定环境的可用性参数)来完成特定的逻辑或者实例化一个具体的子类。而这个"Public Facing(面向公众的)"类,必须非常清楚他的私有子类,以便在面对具体任务的时候有能力返回一个恰当的私有子类实例。对调用者来说只需知道对象的各种API的作用即可。这个模式隐藏了他背后复杂的初始化逻辑,调用者也不需要关心背后的实现。 805 | 806 | 807 | Class clusters 在 Apple 的Framework 中广泛使用:一些明显的例子比如 `NSNumber` 可以返回不同类型给你的子类,取决于 数字类型如何提供 (Integer, Float, etc...) 或者 `NSArray` 返回不同的最优存储策略的子类。 808 | 809 | 810 | 这个模式的精妙的地方在于,调用者可以完全不管子类,事实上,这可以用在设计一个库,可以用来交换实际的返回的类,而不用去管相关的细节,因为它们都遵从抽象超类的方法。 811 | 812 | 813 | 我们的经验是使用类簇可以帮助移除很多条件语句。 814 | 815 | 816 | 817 | 一个经典的例子是如果你有为 iPad 和 iPhone 写的一样的 UIViewController 子类,但是在不同的设备上有不同的行为。 818 | 819 | 820 | 821 | 822 | 比较基础的实现是用条件语句检查设备,然后执行不同的逻辑。虽然刚开始可能不错,但是随着代码的增长,运行逻辑也会趋于复杂。 823 | 一个更好的实现的设计是创建一个抽象而且宽泛的 view controller 来包含所有的共享逻辑,并且对于不同设备有两个特别的子例。 824 | 825 | 826 | 通用的 view controller 会检查当前设备并且返回适当的子类。 827 | 828 | 829 | ```objective-c 830 | @implementation ZOCKintsugiPhotoViewController 831 | 832 | - (id)initWithPhotos:(NSArray *)photos { 833 | if ([self isMemberOfClass:ZOCKintsugiPhotoViewController.class]) { 834 | self = nil; 835 | 836 | if ([UIDevice isPad]) { 837 | self = [[ZOCKintsugiPhotoViewController_iPad alloc] initWithPhotos:photos]; 838 | } 839 | else { 840 | self = [[ZOCKintsugiPhotoViewController_iPhone alloc] initWithPhotos:photos]; 841 | } 842 | return self; 843 | } 844 | return [super initWithNibName:nil bundle:nil]; 845 | } 846 | 847 | @end 848 | ``` 849 | 850 | 851 | 这个子例程展示了如何创建一个类簇。 852 | 853 | 1. 使用`[self isMemberOfClass:ZOCKintsugiPhotoViewController.class]`防止子类中重写初始化方法,避免无限递归。当`[[ZOCKintsugiPhotoViewController alloc] initWithPhotos:photos]`被调用时,上面条件表达式的结果将会是True。 854 | 855 | 2. `self = nil`的目的是移除`ZOCKintsugiPhotoViewController`实例上的所有引用,实例(抽象类的实例)本身将会解除分配( 当然ARC也好MRC也好dealloc都会发生在Main Runloop这一次的结束时)。 856 | 857 | 3. 接下来的逻辑就是判断哪一个私有子类将被初始化。我们假设在iPhone上运行这段代码并且`ZOCKintsugiPhotoViewController_iPhone`没有重写`initWithPhotos:`方法。这种情况下,当执行`self = [[ZOCKintsugiPhotoViewController_iPhone alloc] initWithPhotos:photos];`,`ZOCKintsugiPhotoViewController`将会被调用,第一次检查将会在这里发生,鉴于`ZOCKintsugiPhotoViewController_iPhone`不完全是`ZOCKintsugiPhotoViewController`,表达式`[self isMemberOfClass:ZOCKintsugiPhotoViewController.class]`将会是False,于是就会调用`[super initWithNibName:nil bundle:nil]`,于是就会进入`ZOCKintsugiPhotoViewController`的初始化过程,这时候因为调用者就是`ZOCKintsugiPhotoViewController`本身,这一次的检查必定为True,接下来就会进行正确的初始化过程。(NOTE:这里必须是完全遵循Designated initializer 以及Secondary initializer的设计规范的前提下才会其效果的!不明白这个规范的可以后退一步熟悉这种规范在回头来看这个说明) 858 | 859 | > NOTE: 这里的意思是,代码是在iPhone上调试的,程序员使用了`self = [[ZOCKintsugiPhotoViewController_iPhone alloc] initWithPhotos:photos];`来初始化某个view controller的对象,当代码运行在iPad上时,这个初始化过程也是正确的,因为无论程序员的代码中使用`self = [[ZOCKintsugiPhotoViewController_iPhone alloc] initWithPhotos:photos];`来初始化viewController(iPhone上编写运行在iPad上),还是使用`self = [[ZOCKintsugiPhotoViewController_iPad alloc] initWithPhotos:photos];`来初始化viewController(iPad上编写,运行在iPhone上),都会因为ZOCKintsugiPhotoViewController的`initWithPhotos:`方法的存在而变得通用起来。 860 | 861 | 862 | #### 单例 863 | 864 | 如果可能,请尽量避免使用单例而是依赖注入。 865 | 然而,如果一定要用,请使用一个线程安全的模式来创建共享的实例。对于 GCD,用 `dispatch_once()` 函数就可以咯。 866 | 867 | 868 | ```objective-c 869 | + (instancetype)sharedInstance { 870 | static id sharedInstance = nil; 871 | static dispatch_once_t onceToken = 0; 872 | dispatch_once(&onceToken, ^{ 873 | sharedInstance = [[self alloc] init]; 874 | }); 875 | return sharedInstance; 876 | } 877 | ``` 878 | 879 | 880 | 使用 dispatch_once(),来控制代码同步,取代了原来的约定俗成的用法。 881 | 882 | 883 | ```objective-c 884 | + (instancetype)sharedInstance { 885 | static id sharedInstance; 886 | @synchronized(self) { 887 | if (sharedInstance == nil) { 888 | sharedInstance = [[MyClass alloc] init]; 889 | } 890 | } 891 | return sharedInstance; 892 | } 893 | ``` 894 | 895 | 896 | `dispatch_once()` 的优点是,它更快,而且语法上更干净,因为dispatch_once()的意思就是 “把一些东西执行一次”,就像我们做的一样。 这样同时可以避免 [possible and sometimes prolific crashes][singleton_cocoasamurai]. 897 | 898 | 899 | 经典的单例对象是:一个设备的GPS以及它的加速度传感器(也称动作感应器)。 900 | 虽然单例对象可以子类化,但这种方式能够有用的情况非常少见。 901 | 必须有证据表明,给定类的接口趋向于作为单例来使用。 902 | 所以,单例通常公开一个`sharedInstance`的类方法就已经足够了,没有任何的可写属性需要被暴露出来。 903 | 904 | 尝试着把单例作为一个对象的容器,在代码或者应用层面上共享,是一个糟糕和丑陋的设计。 905 | 906 | > NOTE:单例模式应该运用于类及类的接口趋向于作为单例来使用的情况 (译者注) 907 | 908 | [singleton_cocoasamurai]: http://cocoasamurai.blogspot.com/2011/04/singletons-your-doing-them-wrong.html 909 | 910 | ## 属性 911 | 912 | 属性应该尽可能描述性地命名,避免缩写,并且是小写字母开头的驼峰命名。我们的工具可以很方便地帮我们自动补全所有东西(嗯。。几乎所有的,Xcode 的Derived Data 会索引这些命名)。所以没理由少打几个字符了,并且最好尽可能在你源码里表达更多东西。 913 | 914 | **例子 :** 915 | ```objective-c 916 | NSString *text; 917 | ``` 918 | 919 | **不要这样 :** 920 | ```objective-c 921 | NSString* text; 922 | NSString * text; 923 | ``` 924 | 925 | 926 | (注意:这个习惯和常量不同,这是主要从常用和可读性考虑。 C++ 的开发者偏好从变量名中分离类型,作为类型它应该是 927 | `NSString*` (对于从堆中分配的对象,对于C++是能从栈上分配的)格式。) 928 | 929 | 930 | 使用属性的自动同步 (synthesize) 而不是手动的 `@synthesize` 语句,除非你的属性是 protocol 的一部分而不是一个完整的类。如果 Xcode 可以自动同步这些变量,就让它来做吧。否则只会让你抛开 Xcode 的优点,维护更冗长的代码。 931 | 932 | 你应该总是使用 setter 和 getter 方法访问属性,除了 `init` 和 `dealloc` 方法。通常,使用属性让你增加了在当前作用域之外的代码块的可能所以可能带来更多副作用。 933 | 934 | 你总应该用 getter 和 setter ,因为: 935 | 936 | - 使用 setter 会遵守定义的内存管理语义(`strong`, `weak`, `copy` etc...) ,这个在 ARC 之前就是相关的内容。举个例子,`copy` 属性定义了每个时候你用 setter 并且传送数据的时候,它会复制数据而不用额外的操作。 937 | - KVO 通知(`willChangeValueForKey`, `didChangeValueForKey`) 会被自动执行。 938 | - 更容易debug:你可以设置一个断点在属性声明上并且断点会在每次 getter / setter 方法调用的时候执行,或者你可以在自己的自定义 setter/getter 设置断点。 939 | - 允许在一个单独的地方为设置值添加额外的逻辑。 940 | 941 | 你应该倾向于用 getter: 942 | 943 | - 它是对未来的变化有扩展能力的(比如,属性是自动生成的)。 944 | - 它允许子类化。 945 | - 更简单的debug(比如,允许拿出一个断点在 getter 方法里面,并且看谁访问了特别的 getter 946 | - 它让意图更加清晰和明确:通过访问 ivar `_anIvar` 你可以明确的访问 `self->_anIvar`.这可能导致问题。在 block 里面访问 ivar (你捕捉并且 retain 了 self,即使你没有明确的看到 self 关键词)。 947 | - 它自动产生KVO 通知。 948 | - 在消息发送的时候增加的开销是微不足道的。更多关于性能问题的介绍你可以看 [Should I Use a Property or an Instance Variable?](https://www.bignerdranch.com/blog/should-i-use-a-property-or-an-instance-variable/)。 949 | 950 | #### Init 和 Dealloc 951 | 952 | 953 | 有一个例外:永远不要在 init 方法(以及其他初始化方法)里面用 getter 和 setter 方法,你应当直接访问实例变量。这样做是为了防止有子类时,出现这样的情况:它的子类最终重写了其 setter 或者 getter 方法,因此导致该子类去调用其他的方法、访问那些处于不稳定状态,或者称为没有初始化完成的属性或者 ivar 。记住一个对象仅仅在 init 返回的时候,才会被认为是达到了初始化完成的状态。 954 | 955 | 同样在 dealloc 方法中(在 dealloc 方法中,一个对象可以在一个 不确定的状态中)这是同样需要被注意的。 956 | 957 | * [Advanced Memory Management Programming Guide](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW6) under the self-explanatory section "Don't Use Accessor Methods in Initializer Methods and dealloc"; 958 | * [Migrating to Modern Objective-C](http://adcdownload.apple.com//wwdc_2012/wwdc_2012_session_pdfs/session_413__migrating_to_modern_objectivec.pdf) at WWDC 2012 at slide 27; 959 | * in a [pull request](https://github.com/NYTimes/objective-c-style-guide/issues/6) form Dave DeLong's. 960 | 961 | 962 | 此外,在 init 中使用 setter 不会很好执行 `UIAppearence` 代理(参见 [UIAppearance for Custom Views](http://petersteinberger.com/blog/2013/uiappearance-for-custom-views/) 看更多相关信息)。 963 | 964 | #### 点符号 965 | 966 | 967 | 当使用 setter getter 方法的时候尽量使用点符号。应该总是用点符号来访问以及设置属性。 968 | 969 | **例子:** 970 | 971 | ```Objective-C 972 | view.backgroundColor = [UIColor orangeColor]; 973 | [UIApplication sharedApplication].delegate; 974 | 975 | ``` 976 | 977 | **不要这样:** 978 | 979 | ```Objective-C 980 | [view setBackgroundColor:[UIColor orangeColor]]; 981 | UIApplication.sharedApplication.delegate; 982 | ``` 983 | 984 | 使用点符号会让表达更加清晰并且帮助区分属性访问和方法调用 985 | 986 | ### 属性定义 987 | 988 | 989 | 推荐按照下面的格式来定义属性 990 | 991 | ```objective-c 992 | @property (nonatomic, readwrite, copy) NSString *name; 993 | ``` 994 | 995 | 属性的参数应该按照下面的顺序排列: 原子性,读写 和 内存管理。 这样做你的属性更容易修改正确,并且更好阅读。(译者注:习惯上修改某个属性的修饰符时,一般从属性名从右向左搜索需要修动的修饰符。最可能从最右边开始修改这些属性的修饰符,根据经验这些修饰符被修改的可能性从高到底应为:内存管理 > 读写权限 >原子操作) 996 | 997 | 998 | 你必须使用 `nonatomic`,除非特别需要的情况。在iOS中,`atomic`带来的锁特别影响性能。 999 | 1000 | 属性可以存储一个代码块。为了让它存活到定义的块的结束,必须使用 `copy` (block 最早在栈里面创建,使用 `copy`让 block 拷贝到堆里面去) 1001 | 1002 | 1003 | 为了完成一个共有的 getter 和一个私有的 setter,你应该声明公开的属性为 `readonly` 并且在类扩展中重新定义通用的属性为 `readwrite` 的。 1004 | 1005 | ```objective-c 1006 | // .h文件中 1007 | @interface MyClass : NSObject 1008 | @property (nonatomic, readonly, strong) NSObject *object; 1009 | @end 1010 | // .m文件中 1011 | @interface MyClass () 1012 | @property (nonatomic, readwrite, strong) NSObject *object; 1013 | @end 1014 | 1015 | @implementation MyClass 1016 | // Do Something cool 1017 | @end 1018 | 1019 | ``` 1020 | 1021 | 描述`BOOL`属性的词如果是形容词,那么setter不应该带`is`前缀,但它对应的 getter 访问器应该带上这个前缀,如: 1022 | 1023 | ```objective-c 1024 | @property (assign, getter=isEditable) BOOL editable; 1025 | ``` 1026 | 1027 | 文字和例子引用自 [Cocoa Naming Guidelines](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingIvarsAndTypes.html#//apple_ref/doc/uid/20001284-BAJGIIJE)。 1028 | 1029 | 在实现文件中应避免使用`@synthesize`,因为Xcode已经自动为你添加了。 1030 | 1031 | #### 私有属性 1032 | 1033 | 1034 | 私有属性应该定义在类的实现文件的类的扩展 (匿名的 category) 中。不允许在有名字的 category(如 `ZOCPrivate`)中定义私有属性,除非你扩展其他类。 1035 | 1036 | **例子:** 1037 | 1038 | ```objective-c 1039 | @interface ZOCViewController () 1040 | @property (nonatomic, strong) UIView *bannerView; 1041 | @end 1042 | ``` 1043 | 1044 | ### 可变对象 1045 | 1046 | 1047 | 任何可以用一个可变的对象设置的((比如 `NSString`,`NSArray`,`NSURLRequest`))属性的内存管理类型必须是 `copy` 的。 1048 | 1049 | 这是为了确保防止在不明确的情况下修改被封装好的对象的值(译者注:比如执行 array(定义为 copy 的 NSArray 实例) = mutableArray,copy 属性会让 array 的 setter 方法为 array = [mutableArray copy], [mutableArray copy] 返回的是不可变的 NSArray 实例,就保证了正确性。用其他属性修饰符修饰,容易在直接赋值的时候,array 指向的是 NSMuatbleArray 的实例,在之后可以随意改变它的值,就容易出错)。 1050 | 1051 | 你应该同时避免暴露在公开的接口中可变的对象,因为这允许你的类的使用者改变类自己的内部表示并且破坏类的封装。你可以提供可以只读的属性来返回你对象的不可变的副本。 1052 | 1053 | ```objective-c 1054 | /* .h */ 1055 | @property (nonatomic, readonly) NSArray *elements 1056 | 1057 | /* .m */ 1058 | - (NSArray *)elements { 1059 | return [self.mutableElements copy]; 1060 | } 1061 | ``` 1062 | 1063 | ### 懒加载(Lazy Loading) 1064 | 1065 | 当实例化一个对象需要耗费很多资源,或者配置一次就要调用很多配置相关的方法而你又不想弄乱这些方法时,我们需要重写 getter 方法以延迟实例化,而不是在 init 方法里给对象分配内存。通常这种操作使用下面这样的模板: 1066 | 1067 | 1068 | ```objective-c 1069 | 1070 | - (NSDateFormatter *)dateFormatter { 1071 | if (!_dateFormatter) { 1072 | _dateFormatter = [[NSDateFormatter alloc] init]; 1073 | NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; 1074 | [_dateFormatter setLocale:enUSPOSIXLocale]; 1075 | [_dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS"]; // 毫秒是SSS,而非SSSSS 1076 | } 1077 | return _dateFormatter; 1078 | } 1079 | 1080 | ``` 1081 | 1082 | 即使这样做在某些情况下很不错,但是在实际这样做之前应当深思熟虑。事实上,这样的做法是可以避免的。下面是使用延迟实例化的争议。 1083 | 1084 | 1085 | 1086 | * getter 方法应该避免副作用。看到 getter 方法的时候,你不会想到会因此创建一个对象或导致副作用,实际上如果调用 getter 方法而不使用其返回值编译器会报警告 “Getter 不应该仅因它产生的副作用而被调用”。 1087 | 1088 | > 副作用指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。例如修改全局变量(函数外的变量)或修改参数。函数副作用会给程序设计带来不必要的麻烦,给程序带来十分难以查找的错误,并且降低程序的可读性。(译者注) 1089 | 1090 | * 你在第一次访问的时候改变了初始化的消耗,产生了副作用,这会让优化性能变得困难(以及测试) 1091 | * 这个初始化可能是不确定的:比如你期望属性第一次被一个方法访问,但是你改变了类的实现,访问器在你预期之前就得到了调用,这样可以导致问题,特别是初始化逻辑可能依赖于类的其他不同状态的时候。总的来说最好明确依赖关系。 1092 | * 这个行为不是 KVO 友好的。如果 getter 改变了引用,他应该通过一个 KVO 通知来通知改变。当访问 getter 的时候收到一个改变的通知很奇怪。 1093 | 1094 | 1095 | ## 方法 1096 | 1097 | ### 参数断言 1098 | 1099 | 1100 | 你的方法可能要求一些参数来满足特定的条件(比如不能为nil),在这种情况下最好使用 `NSParameterAssert()` 来断言条件是否成立或是抛出一个异常。 1101 | 1102 | ### 私有方法 1103 | 1104 | 1105 | 永远不要在你的私有方法前加上 `_` 前缀。这个前缀是 Apple 保留的。不要冒重写苹果的私有方法的险。 1106 | 1107 | ## 相等性 1108 | 1109 | 当你要实现相等性的时候记住这个约定:你需要同时实现`isEqual` 和 `hash`方法。如果两个对象是被`isEqual`认为相等的,它们的 `hash` 方法需要返回一样的值。但是如果 `hash` 返回一样的值,并不能确保他们相等。 1110 | 1111 | 这个约定当对象被存储在集合中(如 `NSDictionary` 和 `NSSet` 在底层使用 hash 表数据的数据结构)的时候,用来查找这些对象的。 1112 | 1113 | ```objective-c 1114 | @implementation ZOCPerson 1115 | 1116 | - (BOOL)isEqual:(id)object { 1117 | if (self == object) { 1118 | return YES; 1119 | } 1120 | 1121 | if (![object isKindOfClass:[ZOCPerson class]]) { 1122 | return NO; 1123 | } 1124 | 1125 | // check objects properties (name and birthday) for equality (检查对象属性(名字和生日)的相等性 1126 | ... 1127 | return propertiesMatch; 1128 | } 1129 | 1130 | - (NSUInteger)hash { 1131 | return [self.name hash] ^ [self.birthday hash]; 1132 | } 1133 | 1134 | @end 1135 | ``` 1136 | 1137 | 1138 | 一定要注意 hash 方法不能返回一个常量。这是一个典型的错误并且会导致严重的问题,因为实际上`hash`方法的返回值会作为对象在 hash 散列表中的 key,这会导致 hash 表 100% 的碰撞。 1139 | 1140 | 1141 | 你总是应该用 `isEqualTo<#class-name-without-prefix#>:` 这样的格式实现一个相等性检查方法。如果你这样做,会优先调用这个方法来避免上面的类型检查。 1142 | 1143 | 一个完整的 isEqual 方法应该是这样的: 1144 | 1145 | 1146 | ```objective-c 1147 | 1148 | 1149 | - (BOOL)isEqual:(id)object { 1150 | if (self == object) { 1151 | return YES; 1152 | } 1153 | 1154 | if (![object isKindOfClass:[ZOCPerson class]]) { 1155 | return NO; 1156 | } 1157 | 1158 | return [self isEqualToPerson:(ZOCPerson *)object]; 1159 | } 1160 | 1161 | - (BOOL)isEqualToPerson:(Person *)person { 1162 | if (!person) { 1163 | return NO; 1164 | } 1165 | 1166 | BOOL namesMatch = (!self.name && !person.name) || 1167 | [self.name isEqualToString:person.name]; 1168 | BOOL birthdaysMatch = (!self.birthday && !person.birthday) || 1169 | [self.birthday isEqualToDate:person.birthday]; 1170 | 1171 | return haveEqualNames && haveEqualBirthdays; 1172 | } 1173 | ``` 1174 | > 译者注: 1175 | > 一般而言我们会直接调用自定义的`isEqualTo__ClassName__:`方法,对类的实例判等。 1176 | > 1177 | > 像相等性的开篇已经提到的那样,这里应该复写`isEqual:`方法,因为NSObject的`isEqual:`方法显然不会考虑我们自定义类的类型判断及属性的相等性。当我们自定义的类的对象处在无序集合中被查找时,会自动调用`isEqual:`。同样的该类的`hash`方法,也会在集合查找对象的时候被使用,我们也可以通过复写`hash`方法以达到用自己的标准来判定对象是否`hash`等同。 1178 | > 1179 | > 我们实现的`hash`方法应该建立在系统提供的各种对象的`hash`方法之上(像开篇的例程那样)。不推荐自己去实现某种`hash`算法来替代系统提供的`hash`算法,这一般而言会大大影响性能或者准确性,系统提供的`hash`算法已经经过无数次修缮,足以满足你的要求。 1180 | 1181 | 一个对象实例的 `hash` 计算结果应该是确定的。当它被加入到一个容器对象(比如 `NSArray`, `NSSet`, 或者 `NSDictionary`)的时候这是很重要的,否则行为会无法预测(所有的容器对象使用对象的 hash 来查找或者实施特别的行为,如确定唯一性)这也就是说,应该用不可变的属性来计算 hash 值,或者,最好保证对象是不可变的。 1182 | 1183 | # Categories 1184 | 1185 | 1186 | 虽然我们知道这样写很丑, 但是我们应该要在我们的 category 方法前加上自己的小写前缀以及下划线,比如`- (id)zoc_myCategoryMethod`。 这种实践同样[被苹果推荐](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html#//apple_ref/doc/uid/TP40011210-CH6-SW4)。 1187 | 1188 | 这是非常必要的。因为如果在扩展的 category 或者其他 category 里面已经使用了同样的方法名,会导致不可预计的后果。实际上,实际被调用的是最后被加载的那个 category 中方法的实现(译者注:如果导入的多个 category 中有一些同名的方法导入到类里时,最终调用哪个是由编译时的加载顺序来决定的,最后一个加载进来的方法会覆盖之前的方法)。 1189 | 1190 | 1191 | 如果想要确认你的分类方法没有覆盖其他实现的话,可以把环境变量 OBJC_PRINT_REPLACED_METHODS 设置为 YES,这样那些被取代的方法名字会打印到 Console 中。现在 LLVM 5.1 不会为此发出任何警告和错误提示,所以自己小心不要在分类中重写方法。 1192 | 1193 | 一个好的实践是在 category 名中使用前缀。 1194 | 1195 | ** 例子 ** 1196 | 1197 | ```objective-c 1198 | @interface NSDate (ZOCTimeExtensions) 1199 | - (NSString *)zoc_timeAgoShort; 1200 | @end 1201 | ``` 1202 | 1203 | ** 不要这样 ** 1204 | 1205 | ```objective-c 1206 | @interface NSDate (ZOCTimeExtensions) 1207 | - (NSString *)timeAgoShort; 1208 | @end 1209 | ``` 1210 | 1211 | 1212 | 分类可以用来在头文件中定义一组功能相似的方法。这是在 Apple的 Framework 也很常见的一个实践(下面例子的取自`NSDate` 头文件)。我们也强烈建议在自己的代码中这样使用。 1213 | 1214 | 我们的经验是,创建一组分类对以后的重构十分有帮助。一个类的接口增加的时候,可能意味着你的类做了太多事情,违背了类的单一功能原则。 1215 | 1216 | 之前创造的方法分组可以用来更好地进行不同功能的表示,并且把类打破在更多自我包含的组成部分里。 1217 | 1218 | 1219 | ```objective-c 1220 | 1221 | @interface NSDate : NSObject 1222 | 1223 | @property (readonly) NSTimeInterval timeIntervalSinceReferenceDate; 1224 | 1225 | @end 1226 | 1227 | @interface NSDate (NSDateCreation) 1228 | 1229 | + (instancetype)date; 1230 | + (instancetype)dateWithTimeIntervalSinceNow:(NSTimeInterval)secs; 1231 | + (instancetype)dateWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti; 1232 | + (instancetype)dateWithTimeIntervalSince1970:(NSTimeInterval)secs; 1233 | + (instancetype)dateWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date; 1234 | // ... 1235 | @end 1236 | ``` 1237 | 1238 | 1239 | 1240 | # Protocols 1241 | 1242 | 在 Objective-C 的世界里面经常错过的一个东西是抽象接口。接口(interface)这个词通常指一个类的 `.h` 文件,但是它在 Java 程序员眼里有另外的含义: 一系列不依赖具体实现的方法的定义。(译者注:在OC中,类的接口对应在.m文件中都会有具体的实现,但Java中接口更接近于OC中的抽象接口或者说协议(protocol)) 1243 | 1244 | 在 Objective-C 里是通过 protocol 来实现抽象接口的。因为历史原因,protocol (使用方法类似java的接口)并没有大量地在Objective-C的代码中使用也没有在社区中普及(指的是那种像Java程序员使用接口那样来使用protocol的方式)。一个主要原因是大多数的 Apple 开发的代码没有采用这种的方式,而几乎所有的开发者都是遵从 Apple 的模式以及指南。Apple 几乎只是在委托模式下使用 protocol。 1245 | 1246 | 但是抽象接口的概念很强大,在计算机科学的历史中颇有渊源,没有理由不在 Objective-C 中使用。 1247 | 1248 | 这里通过一个具体的例子来解释 protocol 的强大力量(用作抽象接口):把非常糟糕的设计的架构改造为一个良好的可复用的代码。 1249 | 1250 | 这个例子是在实现一个 RSS 阅读器(它可是经常在技术面试中作为一个测试题呢)。 1251 | 1252 | 要求很简单:在TableView中展示一个远程的RSS订阅。 1253 | 1254 | 一个幼稚的方法是创建一个 `UITableViewController` 的子类,并且把所有的检索订阅数据,解析以及展示的逻辑放在一起,或者说是一个 MVC (Massive View Controller)。这可以跑起来,但是它的设计非常糟糕,不过它足够过一些要求不高的面试了。 1255 | 1256 | 最小的步骤是遵从单一功能原则,创建至少两个组成部分来完成这个任务: 1257 | 1258 | - 一个 feed 解析器来解析搜集到的结果 1259 | - 一个 feed 阅读器来显示结果 1260 | 1261 | 1262 | 这些类的接口可以是这样的: 1263 | 1264 | ```objective-c 1265 | 1266 | @interface ZOCFeedParser : NSObject 1267 | 1268 | @property (nonatomic, weak) id delegate; 1269 | @property (nonatomic, strong) NSURL *url; 1270 | 1271 | - (id)initWithURL:(NSURL *)url; 1272 | 1273 | - (BOOL)start; 1274 | - (void)stop; 1275 | 1276 | @end 1277 | 1278 | ``` 1279 | 1280 | ```objective-c 1281 | 1282 | @interface ZOCTableViewController : UITableViewController 1283 | 1284 | - (instancetype)initWithFeedParser:(ZOCFeedParser *)feedParser; 1285 | 1286 | @end 1287 | 1288 | ``` 1289 | 1290 | 1291 | `ZOCFeedParser` 用 `NSURL` 进行初始化,来获取 RSS 订阅(在这之下可能会使用 NSXMLParser 和 NSXMLParserDelegate 创建有意义的数据),`ZOCTableViewController` 会用这个 parser 来进行初始化。 我们希望它显示 parser 接受到的值并且我们用下面的 protocol 实现委托: 1292 | 1293 | 1294 | ```objective-c 1295 | 1296 | @protocol ZOCFeedParserDelegate 1297 | @optional 1298 | - (void)feedParserDidStart:(ZOCFeedParser *)parser; 1299 | - (void)feedParser:(ZOCFeedParser *)parser didParseFeedInfo:(ZOCFeedInfoDTO *)info; 1300 | - (void)feedParser:(ZOCFeedParser *)parser didParseFeedItem:(ZOCFeedItemDTO *)item; 1301 | - (void)feedParserDidFinish:(ZOCFeedParser *)parser; 1302 | - (void)feedParser:(ZOCFeedParser *)parser didFailWithError:(NSError *)error; 1303 | @end 1304 | 1305 | ``` 1306 | 1307 | 1308 | 我要说,这是一个处理RSS业务的完全合理而恰当的protocol。这个ViewController在Public接口中将遵循这个protocol: 1309 | 1310 | ```objective-c 1311 | @interface ZOCTableViewController : UITableViewController 1312 | ``` 1313 | 1314 | 1315 | 最后创建的代码是这样子的: 1316 | 1317 | ```objective-c 1318 | NSURL *feedURL = [NSURL URLWithString:@"http://www.bbc.co.uk/feed.rss"]; 1319 | 1320 | ZOCFeedParser *feedParser = [[ZOCFeedParser alloc] initWithURL:feedURL]; 1321 | 1322 | ZOCTableViewController *tableViewController = [[ZOCTableViewController alloc] initWithFeedParser:feedParser]; 1323 | feedParser.delegate = tableViewController; 1324 | ``` 1325 | 1326 | 到目前你可能觉得你的代码还是不错的,但是有多少代码是可以有效复用的呢?view controller 只能处理 `ZOCFeedParser` 类型的对象: 从这点来看我们只是把代码分离成了两个组成部分,而没有做任何其他有价值的事情。 1327 | 1328 | 1329 | view controller 的职责应该是“显示某些东西提供的内容”,但是如果我们只允许传递`ZOCFeedParser`的话,就不是这样的了。这就体现了需要传递给 view controller 一个更泛型的对象的需求。 1330 | 1331 | 1332 | 我们使用 `ZOCFeedParserProtocol` 这个 protocol (在 ZOCFeedParserProtocol.h 文件里面,同时文件里也有 `ZOCFeedParserDelegate` )。 1333 | 1334 | ```objective-c 1335 | 1336 | @protocol ZOCFeedParserProtocol 1337 | 1338 | @property (nonatomic, weak) id delegate; 1339 | @property (nonatomic, strong) NSURL *url; 1340 | 1341 | - (BOOL)start; 1342 | - (void)stop; 1343 | 1344 | @end 1345 | 1346 | @protocol ZOCFeedParserDelegate 1347 | @optional 1348 | - (void)feedParserDidStart:(id)parser; 1349 | - (void)feedParser:(id)parser didParseFeedInfo:(ZOCFeedInfoDTO *)info; 1350 | - (void)feedParser:(id)parser didParseFeedItem:(ZOCFeedItemDTO *)item; 1351 | - (void)feedParserDidFinish:(id)parser; 1352 | - (void)feedParser:(id)parser didFailWithError:(NSError *)error; 1353 | @end 1354 | 1355 | ``` 1356 | 1357 | 1358 | 注意这个代理 protocol 现在处理响应我们新的 protocol, 而且 ZOCFeedParser 的接口文件更加精炼了: 1359 | 1360 | ```objective-c 1361 | 1362 | @interface ZOCFeedParser : NSObject 1363 | 1364 | - (id)initWithURL:(NSURL *)url; 1365 | 1366 | @end 1367 | 1368 | ``` 1369 | 1370 | 1371 | 因为 `ZOCFeedParser` 实现了 `ZOCFeedParserProtocol`,它需要实现所有的`required`方法。 1372 | 从这点来看 viewController能接受任何遵循该协议的对象,只要确保所有的对象都会响应`start`和`stop`方法并通过`delegate`属性提供信息(译者注:因为protocol默认情况下所有的方法定义都是`required`的)。对指定的对象而言,这就是viewController所要知道的一切,且不需要知道其实现的细节。 1373 | 1374 | 1375 | ```objective-c 1376 | 1377 | @interface ZOCTableViewController : UITableViewController 1378 | 1379 | - (instancetype)initWithFeedParser:(id)feedParser; 1380 | 1381 | @end 1382 | 1383 | ``` 1384 | 1385 | 上面的代码片段的改变看起来不多,但是有了一个巨大的提升。view controller 将基于协议而不是具体的实现来工作。这带来了以下的优点: 1386 | 1387 | - view controller 现在可以接收通过`delegate`属性提供信息的任意对象:可以是 RSS 远程解析器,或者本地解析器,或是一个读取其他远程或者本地数据的服务 1388 | - `ZOCFeedParser` 和 `ZOCFeedParserDelegate` 可以被其他组成部分复用 1389 | - `ZOCViewController` (UI逻辑部分)可以被复用 1390 | - 测试更简单了,因为可以用 mock 对象来达到 protocol 预期的效果 1391 | 1392 | 当实现一个 protocol 你总应该坚持 [里氏替换原则](https://en.wikipedia.org/wiki/Liskov_substitution_principle)。这个原则是:你应该可以取代任意接口(也就是Objective-C里的"protocol")实现,而不用改变客户端或者相关实现。 1393 | 1394 | 此外,这也意味着`protocol`不该关心类的实现细节;设计protocol的抽象表述时应非常用心,并且要牢记它和它背后的实现是不相干的,真正重要的是协议(这个暴露给使用者的抽象表述)。 1395 | 1396 | 任何在未来可复用的设计,无形当中可以提高代码质量,这也应该一直是程序员的追求。是否这样设计代码,就是大师和菜鸟的区别。 1397 | 1398 | 最后的代码可以在[这里](https://github.com/KevinHM/ADBFeedReader) 找到。 1399 | 1400 | 1401 | 1402 | # NSNotification 1403 | 1404 | 当你定义你自己的 `NSNotification` 的时候你应该把你的通知的名字定义为一个字符串常量,就像你暴露给其他类的其他字符串常量一样。你应该在公开的接口文件中将其声明为 `extern` 的, 并且在对应的实现文件里面定义。 1405 | 1406 | 因为你在头文件中暴露了符号,所以你应该按照统一的命名空间前缀法则,用类名前缀作为这个通知名字的前缀。 1407 | 1408 | 同时,用一个 Did/Will 这样的动词以及用 "Notifications" 后缀来命名这个通知也是一个好的实践。 1409 | 1410 | ```objective-c 1411 | // Foo.h 1412 | extern NSString * const ZOCFooDidBecomeBarNotification 1413 | 1414 | // Foo.m 1415 | NSString * const ZOCFooDidBecomeBarNotification = @"ZOCFooDidBecomeBarNotification"; 1416 | ``` 1417 | 1418 | 1419 | # 美化代码 1420 | 1421 | ### 空格 1422 | 1423 | * 缩进使用 4 个空格。 永远不要使用 tab, 确保你在 Xcode 的设置里面是这样设置的。 1424 | * 方法的大括号和其他的大括号(`if`/`else`/`switch`/`while` 等) 总是在同一行开始,在新起一行结束。 1425 | 1426 | **推荐:** 1427 | 1428 | ```objective-c 1429 | if (user.isHappy) { 1430 | // Do something 1431 | } 1432 | else { 1433 | // Do something else 1434 | } 1435 | ``` 1436 | 1437 | **不推荐:** 1438 | 1439 | ```objective-c 1440 | if (user.isHappy) 1441 | { 1442 | // Do something 1443 | } else { 1444 | // Do something else 1445 | } 1446 | ``` 1447 | 1448 | 1449 | 1450 | * 方法之间应该要有一个空行来帮助代码看起来清晰且有组织。 方法内的空格应该用来分离功能,但是通常不同的功能应该用新的方法来定义。 1451 | * 优先使用 auto-synthesis。但是如果必要的话, `@synthesize` and `@dynamic` 1452 | * 在实现文件中的声明应该新起一行。 1453 | * 应该总是让冒号对齐。有一些方法签名可能超过三个冒号,用冒号对齐可以让代码更具有可读性。即使有代码块存在,也应该用冒号对齐方法。 1454 | 1455 | 1456 | **推荐:** 1457 | 1458 | ```objective-c 1459 | [UIView animateWithDuration:1.0 1460 | animations:^{ 1461 | // something 1462 | } 1463 | completion:^(BOOL finished) { 1464 | // something 1465 | }]; 1466 | 1467 | ``` 1468 | 1469 | 1470 | **不推荐:** 1471 | 1472 | ```objective-c 1473 | [UIView animateWithDuration:1.0 animations:^{ 1474 | // something 1475 | } completion:^(BOOL finished) { 1476 | // something 1477 | }]; 1478 | ``` 1479 | 1480 | 如果自动对齐让可读性变得糟糕,那么应该在之前把 block 定义为变量,或者重新考虑你的代码签名设计。 1481 | 1482 | ### 换行 1483 | 1484 | 本指南关注代码显示效果以及在线浏览的可读性,所以换行是一个重要的主题。 1485 | 1486 | 举个例子: 1487 | 1488 | ```objective-c 1489 | self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers]; 1490 | ``` 1491 | 1492 | 一个像上面的长行的代码在第二行以一个间隔(2个空格)延续 1493 | 1494 | ```objective-c 1495 | self.productsRequest = [[SKProductsRequest alloc] 1496 | initWithProductIdentifiers:productIdentifiers]; 1497 | ``` 1498 | 1499 | ### 括号 1500 | 1501 | 1502 | 在以下的地方使用 [Egyptian风格 括号](https://en.wikipedia.org/wiki/Indent_style#K.26R_style) (译者注:又称 K&R 风格,代码段括号的开始位于一行的末尾,而不是另外起一行的风格。关于为什么叫做 Egyptian Brackets,可以参考 ) 1503 | 1504 | * 控制语句 (if-else, for, switch) 1505 | 1506 | 非 Egyptian 括号可以用在: 1507 | 1508 | * 类的实现(如果存在) 1509 | * 方法的实现 1510 | 1511 | 1512 | # 代码组织 1513 | 1514 | [来自](http://nshipster.com/pragma/) Mattt Thompson 1515 | 1516 | > code organization is a matter of hygiene (代码组织是卫生问题) 1517 | 1518 | 1519 | 我们十分赞成这句话。清晰地组织代码和规范地进行定义, 是你对自己以及其他阅读代码的人的尊重。 1520 | 1521 | ## 利用代码块 1522 | 1523 | 一个 GCC 非常模糊的特性,以及 Clang 也有的特性是,代码块如果在闭合的圆括号内的话,会返回最后语句的值 1524 | 1525 | 1526 | ------------------- 1527 | ```objective-c 1528 | NSURL *url = ({ 1529 | NSString *urlString = [NSString stringWithFormat:@"%@/%@", baseURLString, endpoint]; 1530 | [NSURL URLWithString:urlString]; 1531 | }); 1532 | ``` 1533 | 1534 | 1535 | 1536 | 1537 | ## Pragma 1538 | 1539 | ### Pragma Mark 1540 | 1541 | 1542 | `#pragma mark -` 是一个在类内部组织代码并且帮助你分组方法实现的好办法。 我们建议使用 `#pragma mark -` 来分离: 1543 | 1544 | - 不同功能组的方法 1545 | - protocols 的实现 1546 | - 对父类方法的重写 1547 | 1548 | ```objective-c 1549 | 1550 | - (void)dealloc { /* ... */ } 1551 | - (instancetype)init { /* ... */ } 1552 | 1553 | #pragma mark - View Lifecycle (View 的生命周期) 1554 | 1555 | - (void)viewDidLoad { /* ... */ } 1556 | - (void)viewWillAppear:(BOOL)animated { /* ... */ } 1557 | - (void)didReceiveMemoryWarning { /* ... */ } 1558 | 1559 | #pragma mark - Custom Accessors (自定义访问器) 1560 | 1561 | - (void)setCustomProperty:(id)value { /* ... */ } 1562 | - (id)customProperty { /* ... */ } 1563 | 1564 | #pragma mark - IBActions 1565 | 1566 | - (IBAction)submitData:(id)sender { /* ... */ } 1567 | 1568 | #pragma mark - Public 1569 | 1570 | - (void)publicMethod { /* ... */ } 1571 | 1572 | #pragma mark - Private 1573 | 1574 | - (void)zoc_privateMethod { /* ... */ } 1575 | 1576 | #pragma mark - UITableViewDataSource 1577 | 1578 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ } 1579 | 1580 | #pragma mark - ZOCSuperclass 1581 | 1582 | // ... 重写来自 ZOCSuperclass 的方法 1583 | 1584 | #pragma mark - NSObject 1585 | 1586 | - (NSString *)description { /* ... */ } 1587 | 1588 | ``` 1589 | 1590 | 1591 | 上面的标记能明显分离和组织代码。你还可以用 cmd+Click 来快速跳转到符号定义地方。 1592 | 但是小心,即使 paragma mark 是一门手艺,但是它不是让你类里面方法数量增加的一个理由:类里面有太多方法说明类做了太多事情,需要考虑重构了。 1593 | 1594 | ### 关于 pragma 1595 | 1596 | 在 http://raptureinvenice.com/pragmas-arent-just-for-marks/ 有很好的关于 pragma 的讨论了,在这边我们再做部分说明。 1597 | 1598 | 大多数 iOS 开发者平时并没有和很多编译器选项打交道。一些选项是对控制严格检查(或者不检查)你的代码或者错误的。有时候,你想要用 pragma 直接产生一个异常,临时打断编译器的行为。 1599 | 1600 | 当你使用ARC的时候,编译器帮你插入了内存管理相关的调用。但是这样可能产生一些烦人的事情。比如你使用 `NSSelectorFromString` 来动态地产生一个 selector 调用的时候,ARC不知道这个方法是哪个并且不知道应该用那种内存管理方法,你会被提示 `performSelector may cause a leak because its selector is unknown(执行 selector 可能导致泄漏,因为这个 selector 是未知的)`. 1601 | 1602 | 如果你知道你的代码不会导致内存泄露,你可以通过加入这些代码忽略这些警告 1603 | 1604 | ```objective-c 1605 | #pragma clang diagnostic push 1606 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 1607 | 1608 | [myObj performSelector:mySelector withObject:name]; 1609 | 1610 | #pragma clang diagnostic pop 1611 | ``` 1612 | 1613 | 1614 | 注意我们是如何在相关代码上下文中用 pragma 停用 -Warc-performSelector-leaks 检查的。这确保我们没有全局禁用。如果全局禁用,可能会导致错误。 1615 | 1616 | 全部的选项可以在 [The Clang User's Manual](http://clang.llvm.org/docs/UsersManual.html) 找到并且学习。 1617 | 1618 | 1619 | ### 忽略没用使用变量的编译警告 1620 | 1621 | 告诉你申明的变量它将不会被使用,这种做法很有用。大多数情况下,你希望移除这些引用来(稍微地)提高性能,但是有时候你希望保留它们。为什么?或许它们以后有用,或者有些特性只是暂时移除。无论如何,一个消除这些警告的好方法是用相关语句进行注解,使用 `#pragma unused()`: 1622 | 1623 | ```objective-c 1624 | - (NSInteger)giveMeFive { 1625 | NSString *foo; 1626 | #pragma unused (foo) 1627 | 1628 | return 5; 1629 | } 1630 | ``` 1631 | 1632 | 现在你的代码不用任何编译警告了。注意你的 pragma 需要标记到问题代码之下。 1633 | 1634 | ## 明确编译器警告和错误 1635 | 1636 | 1637 | 编译器是一个机器人,它会标记你代码中被 Clang 规则定义为错误的地方。但是,你总是比 Clang 更聪明。通常,你会发现一些讨厌的代码会导致这个问题,但是暂时却解决不了。你可以这样明确一个错误: 1638 | 1639 | 1640 | ```objective-c 1641 | - (NSInteger)divide:(NSInteger)dividend by:(NSInteger)divisor { 1642 | #error Whoa, buddy, you need to check for zero here! 1643 | return (dividend / divisor); 1644 | } 1645 | ``` 1646 | 1647 | 1648 | 类似的,你可以这样标明一个警告 1649 | 1650 | 1651 | ```objective-c 1652 | - (float)divide:(float)dividend by:(float)divisor { 1653 | #warning Dude, don't compare floating point numbers like this! 1654 | if (divisor != 0.0) { 1655 | return (dividend / divisor); 1656 | } 1657 | else { 1658 | return NAN; 1659 | } 1660 | } 1661 | ``` 1662 | 1663 | ## 字符串文档 1664 | 1665 | 1666 | 所有重要的方法,接口,分类以及协议定义应该有伴随的注释来解释它们的用途以及如何使用。更多的例子可以看 Google 代码风格指南中的 [File and Declaration Comments](http://google-styleguide.googlecode.com/svn/trunk/objcguide.xml#File_Comments)。 1667 | 1668 | 简而言之:有长的和短的两种字符串文档。 1669 | 1670 | 短文档适用于单行的文件,包括注释斜杠。它适合简短的函数,特别是(但不仅仅是)非 public 的 API: 1671 | 1672 | ``` 1673 | // Return a user-readable form of a Frobnozz, html-escaped. 1674 | ``` 1675 | 1676 | 1677 | 文本应该用一个动词 ("return") 而不是 "returns" 这样的描述。 1678 | 1679 | 如果描述超过一行,应改用长字符串文档: 1680 | 1681 | * 以`/**`开始 1682 | * 换行写一句总结的话,以`?或者!或者.`结尾。 1683 | * 空一行 1684 | * 在与第一行对齐的位置开始写剩下的注释 1685 | * 最后用`*/`结束。 1686 | 1687 | ``` 1688 | /** 1689 | This comment serves to demonstrate the format of a docstring. 1690 | 1691 | Note that the summary line is always at most one line long, and 1692 | after the opening block comment, and each line of text is preceded 1693 | by a single space. 1694 | */ 1695 | ``` 1696 | 1697 | 1698 | 一个函数必须有一个字符串文档,除非它符合下面的所有条件: 1699 | 1700 | * 非公开 1701 | * 很短 1702 | * 显而易见 1703 | 1704 | 字符串文档应该描述函数的调用符号和语义,而不是它如何实现。 1705 | 1706 | ## 注释 1707 | 1708 | 当它需要的时候,注释应该用来解释特定的代码做了什么。所有的注释必须被持续维护或者干脆就删掉。 1709 | 1710 | 块注释应该被避免,代码本身应该尽可能就像文档一样表示意图,只需要很少的打断注释。 *例外: 这不能适用于用来产生文档的注释* 1711 | 1712 | ### 头文档 1713 | 1714 | 一个类的文档应该只在 .h 文件里用 Doxygen/AppleDoc 的语法书写。 方法和属性都应该提供文档。 1715 | 1716 | **例子: ** 1717 | 1718 | ```objective-c 1719 | /** 1720 | * Designated initializer. 1721 | * 1722 | * @param store The store for CRUD operations. 1723 | * @param searchService The search service used to query the store. 1724 | * 1725 | * @return A ZOCCRUDOperationsStore object. 1726 | */ 1727 | - (instancetype)initWithOperationsStore:(id)store 1728 | searchService:(id)searchService; 1729 | ``` 1730 | 1731 | 1732 | # 对象间的通讯 1733 | 1734 | 对象之间需要通信,这也是所有软件的基础。再非凡的软件也需要通过对象通信来完成复杂的目标。本章将深入讨论一些设计概念,以及如何依据这些概念来设计出良好的架构。 1735 | 1736 | ## Block 1737 | 1738 | Block 是 Objective-C 版本的 lambda 或者 closure(闭包)。 1739 | 1740 | 使用 block 定义异步接口: 1741 | 1742 | ```objective-c 1743 | - (void)downloadObjectsAtPath:(NSString *)path 1744 | completion:(void(^)(NSArray *objects, NSError *error))completion; 1745 | ``` 1746 | 1747 | 当你定义一个类似上面的接口的时候,尽量使用一个单独的 block 作为接口的最后一个参数。把需要提供的数据和错误信息整合到一个单独 block 中,比分别提供成功和失败的 block 要好。 1748 | 1749 | 以下是你应该这样做的原因: 1750 | 1751 | 1752 | * 通常这成功处理和失败处理会共享一些代码(比如让一个进度条或者提示消失); 1753 | * Apple 也是这样做的,与平台一致能够带来一些潜在的好处; 1754 | * block 通常会有多行代码,如果不作为最后一个参数放在后面的话,会打破调用点; 1755 | * 使用多个 block 作为参数可能会让调用看起来显得很笨拙,并且增加了复杂性。 1756 | 1757 | 看上面的方法,完成处理的 block 的参数很常见:第一个参数是调用者希望获取的数据,第二个是错误相关的信息。这里需要遵循以下两点: 1758 | 1759 | * 若 `objects` 不为 nil,则 `error` 必须为 nil 1760 | * 若 `objects` 为 nil,则 `error` 必须不为 nil 1761 | 1762 | 因为调用者更关心的是实际的数据,就像这样: 1763 | 1764 | 1765 | ```objective-c 1766 | - (void)downloadObjectsAtPath:(NSString *)path 1767 | completion:(void(^)(NSArray *objects, NSError *error))completion { 1768 | if (objects) { 1769 | // do something with the data 1770 | } 1771 | else { 1772 | // some error occurred, 'error' variable should not be nil by contract 1773 | } 1774 | } 1775 | ``` 1776 | 1777 | 1778 | 此外,Apple 提供的一些同步接口在成功状态下向 error 参数(如果非 NULL) 写入了垃圾值,所以检查 error 的值可能出现问题。 1779 | 1780 | ### 深入 Block 1781 | 1782 | 一些关键点: 1783 | 1784 | * block 是在栈上创建的 1785 | * block 可以复制到堆上 1786 | * Block会捕获栈上的变量(或指针),将其复制为自己私有的const(变量)。 1787 | * (如果在Block中修改Block块外的)栈上的变量和指针,那么这些变量和指针必须用`__block`关键字申明(译者注:否则就会跟上面的情况一样只是捕获他们的瞬时值)。 1788 | 1789 | 1790 | 如果 block 没有在其他地方被保持,那么它会随着栈生存并且当栈帧(stack frame)返回的时候消失。仅存在于栈上时,block对对象访问的内存管理和生命周期没有任何影响。 1791 | 1792 | 如果 block 需要在栈帧返回的时候存在,它们需要明确地被复制到堆上,这样,block 会像其他 Cocoa 对象一样增加引用计数。当它们被复制的时候,它会带着它们的捕获作用域一起,retain 他们所有引用的对象。 1793 | 1794 | 如果一个 block引用了一个栈变量或指针,那么这个block初始化的时候会拥有这个变量或指针的const副本,所以(被捕获之后再在栈中改变这个变量或指针的值)是不起作用的。(译者注:所以这时候我们在block中对这种变量进行赋值会编译报错:`Variable is not assignable(missing __block type specifier)`,因为他们是副本而且是const的.具体见下面的例程)。 1795 | 1796 | 当一个 block 被复制后,`__block` 声明的栈变量的引用被复制到了堆里,复制完成之后,无论是栈上的block还是刚刚产生在堆上的block(栈上block的副本)都会引用该变量在堆上的副本。 1797 | 1798 | (下面代码是译者加的) 1799 | 1800 | ```objective-c 1801 | 1802 | CGFloat blockInt = 10; 1803 | void (^playblock)(void) = ^{ 1804 | NSLog(@"blockInt = %zd", blockInt); 1805 | }; 1806 | blockInt++; 1807 | playblock(); 1808 | ... 1809 | 1810 | // 结果为:blockInt = 10 1811 | ``` 1812 | 1813 | 用 LLDB 来展示 block 是这样子的: 1814 | 1815 | ![](./images/blocks_debugger.png) 1816 | 1817 | 1818 | 最重要的事情是 `__block` 声明的变量和指针在 block 里面是作为显示操作真实值/对象的结构来对待的。 1819 | 1820 | 1821 | block 在 Objective-C 的 runtime(运行时) 里面被当作一等公民对待:他们有一个 `isa` 指针,一个类也是用 `isa` 指针在Objective-C 运行时来访问方法和存储数据的。在非 ARC 环境肯定会把它搞得很糟糕,并且悬挂指针会导致 crash。`__block` 仅仅对 block 内的变量起作用,它只是简单地告诉 block: 1822 | 1823 | > 嗨,这个指针或者原始的类型依赖它们在的栈。请用一个栈上的新变量来引用它。我是说,请对它进行双重解引用,不要 retain 它。 1824 | 谢谢,哥们。 1825 | 1826 | 1827 | 如果在定义之后但是 block 没有被调用前,对象被释放了,那么 block 的执行会导致 crash。 `__block` 变量不会在 block 中被持有,最后... 指针、引用、解引用以及引用计数变得一团糟。 1828 | 1829 | [blocks_uth1]: https://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/00_Introduction.html 1830 | [blocks_uth2]: http://www.ios-blog.co.uk/tutorials/programming-with-blocks-an-overview/ 1831 | 1832 | 1833 | ### self 的循环引用 1834 | 1835 | 1836 | 当使用代码块和异步分发的时候,要注意避免引用循环。 总是使用 `weak` 来引用对象,避免引用循环。(译者注:这里更为优雅的方式是采用影子变量@weakify/@strongify [这里有更为详细的说明](https://github.com/jspahrsummers/libextobjc/blob/master/extobjc/EXTScope.h)) 此外,把持有 block 的属性设置为 nil (比如 `self.completionBlock = nil`) 是一个好的实践。它会打破 block 捕获的作用域带来的引用循环。 1837 | 1838 | 1839 | **例子:** 1840 | 1841 | ```objective-c 1842 | __weak __typeof(self) weakSelf = self; 1843 | [self executeBlock:^(NSData *data, NSError *error) { 1844 | [weakSelf doSomethingWithData:data]; 1845 | }]; 1846 | ``` 1847 | 1848 | **不要这样:** 1849 | 1850 | ```objective-c 1851 | [self executeBlock:^(NSData *data, NSError *error) { 1852 | [self doSomethingWithData:data]; 1853 | }]; 1854 | ``` 1855 | 1856 | **多个语句的例子:** 1857 | 1858 | ```objective-c 1859 | __weak __typeof(self)weakSelf = self; 1860 | [self executeBlock:^(NSData *data, NSError *error) { 1861 | __strong __typeof(weakSelf) strongSelf = weakSelf; 1862 | if (strongSelf) { 1863 | [strongSelf doSomethingWithData:data]; 1864 | [strongSelf doSomethingWithData:data]; 1865 | } 1866 | }]; 1867 | ``` 1868 | 1869 | **不要这样:** 1870 | 1871 | ```objective-c 1872 | __weak __typeof(self)weakSelf = self; 1873 | [self executeBlock:^(NSData *data, NSError *error) { 1874 | [weakSelf doSomethingWithData:data]; 1875 | [weakSelf doSomethingWithData:data]; 1876 | }]; 1877 | ``` 1878 | 1879 | 1880 | 你应该把这两行代码作为 snippet 加到 Xcode 里面并且总是这样使用它们。 1881 | 1882 | ```objective-c 1883 | __weak __typeof(self)weakSelf = self; 1884 | __strong __typeof(weakSelf)strongSelf = weakSelf; 1885 | ``` 1886 | 1887 | 这里我们来讨论下 block 里面的 self 的 `__weak` 和 `__strong` 限定词的一些微妙的地方。简而言之,我们可以参考 self 在 block 里面的三种不同情况。 1888 | 1889 | 1. 直接在 block 里面使用关键词 self 1890 | 2. 在 block 外定义一个 `__weak` 的 引用到 self,并且在 block 里面使用这个弱引用 1891 | 3. 在 block 外定义一个 `__weak` 的 引用到 self,并在在 block 内部通过这个弱引用定义一个 `__strong` 的引用。 1892 | 1893 | 1894 | **方案 1. 直接在 block 里面使用关键词 `self`** 1895 | 1896 | 如果我们直接在 block 里面用 self 关键字,对象会在 block 的定义时候被 retain,(实际上 block 是 [copied][blocks_caveat13] 但是为了简单我们可以忽略这个)。一个 const 的对 self 的引用在 block 里面有自己的位置并且它会影响对象的引用计数。如果这个block被其他的类使用并且(或者)彼此间传来传去,我们可能想要在 block 中保留 self,就像其他在 block 中使用的对象一样. 因为他们是block执行所需要的. 1897 | 1898 | ```objective-c 1899 | dispatch_block_t completionBlock = ^{ 1900 | NSLog(@"%@", self); 1901 | } 1902 | 1903 | MyViewController *myController = [[MyViewController alloc] init...]; 1904 | [self presentViewController:myController 1905 | animated:YES 1906 | completion:completionHandler]; 1907 | ``` 1908 | 1909 | 1910 | 没啥大不了。但是如果通过一个属性中的 `self` 保留 了这个 block(就像下面的例程一样),对象( self )保留了 block 会怎么样呢? 1911 | 1912 | ```objective-c 1913 | self.completionHandler = ^{ 1914 | NSLog(@"%@", self); 1915 | } 1916 | 1917 | MyViewController *myController = [[MyViewController alloc] init...]; 1918 | [self presentViewController:myController 1919 | animated:YES 1920 | completion:self.completionHandler]; 1921 | ``` 1922 | 1923 | 1924 | 这就是有名的 retain cycle, 并且我们通常应该避免它。这种情况下我们收到 CLANG 的警告: 1925 | 1926 | ```objective-c 1927 | Capturing 'self' strongly in this block is likely to lead to a retain cycle (在 block 里面发现了 `self` 的强引用,可能会导致循环引用) 1928 | ``` 1929 | 所以 `__weak` 就有用武之地了。 1930 | 1931 | **方案 2. 在 block 外定义一个 `__weak` 的 引用到 self,并且在 block 里面使用这个弱引用** 1932 | 1933 | 1934 | 这样会避免循坏引用,也是通常情况下我们的block作为类的属性被self retain 的时候会做的。 1935 | 1936 | ```objective-c 1937 | __weak typeof(self) weakSelf = self; 1938 | self.completionHandler = ^{ 1939 | NSLog(@"%@", weakSelf); 1940 | }; 1941 | 1942 | MyViewController *myController = [[MyViewController alloc] init...]; 1943 | [self presentViewController:myController 1944 | animated:YES 1945 | completion:self.completionHandler]; 1946 | ``` 1947 | 1948 | 1949 | 这个情况下 block 没有 retain 对象并且对象在属性里面 retain 了 block 。所以这样我们能保证了安全的访问 self。 不过糟糕的是,它可能被设置成 nil 的。问题是:如何让 self 在 block 里面安全地被销毁。 1950 | 1951 | 考虑这么个情况:block 作为属性(property)赋值的结果,从一个对象被复制到另一个对象(如 myController),在这个复制的 block 执行之前,前者(即之前的那个对象)已经被解除分配。 1952 | 1953 | 下面的更有意思。 1954 | 1955 | **方案 3. 在 block 外定义一个 `__weak` 的 引用到 self,并在在 block 内部通过这个弱引用定义一个 `__strong` 的引用** 1956 | 1957 | 你可能会想,首先,这是避免 retain cycle 警告的一个技巧。 1958 | 1959 | 这不是重点,这个 self 的强引用是在block 执行时 被创建的,但是否使用 self 在 block 定义时就已经定下来了, 因此self (在block执行时) 会被 retain. 1960 | 1961 | [Apple 文档][blocks_caveat1] 中表示 "为了 non-trivial cycles ,你应该这样" : 1962 | 1963 | ```objective-c 1964 | MyViewController *myController = [[MyViewController alloc] init...]; 1965 | // ... 1966 | MyViewController * __weak weakMyController = myController; 1967 | myController.completionHandler = ^(NSInteger result) { 1968 | MyViewController *strongMyController = weakMyController; 1969 | if (strongMyController) { 1970 | // ... 1971 | [strongMyController dismissViewControllerAnimated:YES completion:nil]; 1972 | // ... 1973 | } 1974 | else { 1975 | // Probably nothing... 1976 | } 1977 | }; 1978 | ``` 1979 | 1980 | 1981 | 首先,我觉得这个例子看起来是错误的。如果 block 本身在 completionHandler 属性中被 retain 了,那么 self 如何被 delloc 和在 block 之外赋值为 nil 呢? completionHandler 属性可以被声明为 `assign` 或者 `unsafe_unretained` 的,来允许对象在 block 被传递之后被销毁。 1982 | 1983 | 我不能理解这样做的理由,如果其他对象需要这个对象(self),block 被传递的时候应该 retain 对象,所以 block 应该不被作为属性存储。这种情况下不应该用 `__weak`/`__strong` 1984 | 1985 | 总之,其他情况下,希望 weakSelf 变成 nil 的话,就像第二种情况解释那么写(在 block 之外定义一个弱应用并且在 block 里面使用)。 1986 | 1987 | 还有,Apple的 "trivial block" 是什么呢。我们的理解是 trivial block 是一个不被传送的 block ,它在一个良好定义和控制的作用域里面,weak 修饰只是为了避免循环引用。 1988 | 1989 | 1990 | 虽然有 Kazuki Sakamoto 和 Tomohiko Furumoto) 讨论的 [一][blocks_caveat2] [些][blocks_caveat3] [的][blocks_caveat4] [在线][blocks_caveat5] [参考][blocks_caveat6], [Matt Galloway][blocks_caveat16] 的 ([Effective Objective-C 2.0][blocks_caveat14] 和 [Pro Multithreading and Memory Management for iOS and OS X][blocks_caveat15] ,大多数开发者始终没有弄清楚概念。 1991 | 1992 | 在 block 内用强引用的优点是,抢占执行的时候的鲁棒性。在 block 执行的时候, 再次温故下上面的三个例子: 1993 | 1994 | **方案 1. 直接在 block 里面使用关键词 `self`** 1995 | 1996 | 如果 block 被属性 retain,self 和 block 之间会有一个循环引用并且它们不会再被释放。如果 block 被传送并且被其他的对象 copy 了,self 在每一个 copy 里面被 retain 1997 | 1998 | **方案 2. 在 block 外定义一个 `__weak` 的 引用到 self,并且在 block 里面使用这个弱引用** 1999 | 2000 | 不管 block 是否通过属性被 retain ,这里都不会发生循环引用。如果 block 被传递或者 copy 了,在执行的时候,weakSelf 可能已经变成 nil。 2001 | 2002 | block 的执行可以抢占,而且对 weakSelf 指针的调用时序不同可以导致不同的结果(如:在一个特定的时序下 weakSelf 可能会变成nil)。 2003 | 2004 | ```objective-c 2005 | __weak typeof(self) weakSelf = self; 2006 | dispatch_block_t block = ^{ 2007 | [weakSelf doSomething]; // weakSelf != nil 2008 | // preemption, weakSelf turned nil 2009 | [weakSelf doSomethingElse]; // weakSelf == nil 2010 | }; 2011 | ``` 2012 | 2013 | **方案 3. 在 block 外定义一个 `__weak` 的 引用到 self,并在在 block 内部通过这个弱引用定义一个 `__strong` 的引用。** 2014 | 2015 | 不管 block 是否通过属性被 retain ,这里也不会发生循环引用。如果 block 被传递到其他对象并且被复制了,执行的时候,weakSelf 可能被nil,因为强引用被赋值并且不会变成nil的时候,我们确保对象 在 block 调用的完整周期里面被 retain了,如果抢占发生了,随后的对 strongSelf 的执行会继续并且会产生一样的值。如果 strongSelf 的执行到 nil,那么在 block 不能正确执行前已经返回了。 2016 | 2017 | ```objective-c 2018 | __weak typeof(self) weakSelf = self; 2019 | myObj.myBlock = ^{ 2020 | __strong typeof(self) strongSelf = weakSelf; 2021 | if (strongSelf) { 2022 | [strongSelf doSomething]; // strongSelf != nil 2023 | // preemption, strongSelf still not nil(抢占的时候,strongSelf 还是非 nil 的) 2024 | [strongSelf doSomethingElse]; // strongSelf != nil 2025 | } 2026 | else { 2027 | // Probably nothing... 2028 | return; 2029 | } 2030 | }; 2031 | ``` 2032 | 2033 | 在ARC条件中,如果尝试用 `->` 符号访问一个实例变量,编译器会给出非常清晰的错误信息: 2034 | 2035 | ```objective-c 2036 | Dereferencing a __weak pointer is not allowed due to possible null value caused by race condition, assign it to a strong variable first. (对一个 __weak 指针的解引用不允许的,因为可能在竞态条件里面变成 null, 所以先把他定义成 strong 的属性) 2037 | ``` 2038 | 2039 | 可以用下面的代码展示 2040 | 2041 | ```objective-c 2042 | __weak typeof(self) weakSelf = self; 2043 | myObj.myBlock = ^{ 2044 | id localVal = weakSelf->someIVar; 2045 | }; 2046 | ``` 2047 | 2048 | 2049 | 在最后 2050 | 2051 | * **方案 1**: 只能在 block 不是作为一个 property 的时候使用,否则会导致 retain cycle。 2052 | 2053 | * **方案 2**: 当 block 被声明为一个 property 的时候使用。 2054 | 2055 | * **方案 3**: 和并发执行有关。当涉及异步的服务的时候,block 可以在之后被执行,并且不会发生关于 self 是否存在的问题。 2056 | 2057 | [blocks_caveat1]: https://developer.apple.com/library/mac/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html 2058 | [blocks_caveat2]: https://dhoerl.wordpress.com/2013/04/23/i-finally-figured-out-weakself-and-strongself/ 2059 | [blocks_caveat3]: http://blog.random-ideas.net/?p=160 2060 | [blocks_caveat4]: http://stackoverflow.com/questions/7904568/disappearing-reference-to-self-in-a-block-under-arc 2061 | 2062 | [blocks_caveat5]: http://stackoverflow.com/questions/12218767/objective-c-blocks-and-memory-management 2063 | [blocks_caveat6]: https://github.com/AFNetworking/AFNetworking/issues/807 2064 | [blocks_caveat10]: https://twitter.com/pedrogomes 2065 | [blocks_caveat11]: https://twitter.com/dmakarenko 2066 | [blocks_caveat12]: https://ef.com 2067 | [blocks_caveat13]: https://developer.apple.com/library/ios/documentation/cocoa/conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW4 2068 | [blocks_caveat14]: http://www.effectiveobjectivec.com/ 2069 | [blocks_caveat15]: http://www.amazon.it/Pro-Multithreading-Memory-Management-Ios/dp/1430241160 2070 | [blocks_caveat16]: https://twitter.com/mattjgalloway 2071 | 2072 | ## 委托和数据源 2073 | 2074 | [委托模式](https://www.wikiwand.com/zh/%E5%A7%94%E6%89%98%E6%A8%A1%E5%BC%8F) 是 Apple 的框架里面使用广泛的模式,同时它是四人帮的书“设计模式”中的重要模式之一。委托代理模式是单向的,消息的发送方(委托方)需要知道接收方(代理方)是谁,反过来就没有必要了。对象之间耦合较松,发送方仅需知道它的代理方是否遵守相关 protocol 即可。 2075 | 2076 | 本质上,委托代理模式仅需要代理方提供一些回调方法,即代理方需要实现一系列空返回值的方法。 2077 | 2078 | 不幸的是 Apple 的 API 并没有遵守这个原则,开发者也效仿 Apple 进入了一个误区。典型的例子就是 [UITableViewDelegate](https://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewDelegate_Protocol/Reference/Reference.html) 协议。 2079 | 2080 | 它的一些方法返回 void 类型,就像我们所说的回调: 2081 | 2082 | ```objective-c 2083 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath; 2084 | - (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath; 2085 | ``` 2086 | 2087 | 但是其他的就不是那么回事: 2088 | 2089 | ```objective-c 2090 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath; 2091 | - (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender; 2092 | ``` 2093 | 2094 | 当委托者询问代理者一些信息的时候,这就暗示着信息是从代理者流向委托者而非相反的过程。 这(译者注:委托者 ==Data==> 代理者)是概念性的不同,须用另一个新的名字来描述这种模式:数据源模式。 2095 | 2096 | 可能有人会说 Apple 有一个 [UITableViewDataSouce](https://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewDataSource_Protocol/Reference/Reference.html) protocol 来做这个(虽然使用委托模式的名字),但是实际上它的方法是用来提供真实的数据应该如何被展示的信息的。 2097 | 2098 | ```objective-c 2099 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; 2100 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; 2101 | ``` 2102 | 2103 | 此外,以上两个方法 Apple 混合了展示层和数据层,这显的非常糟糕,但是很少的开发者感到糟糕。而且我们在这里把空返回值和非空返回值的方法都天真地叫做委托方法。 2104 | 2105 | 为了分离概念,我们应该这样做: 2106 | 2107 | * 委托模式(delegate pattern):事件发生的时候,委托者需要通知代理者。 2108 | * 数据源模式(datasource pattern): 委托者需要从数据源对象拉取数据。 2109 | 2110 | 2111 | 这个是实际的例子: 2112 | 2113 | ```objective-c 2114 | @class ZOCSignUpViewController; 2115 | 2116 | @protocol ZOCSignUpViewControllerDelegate 2117 | - (void)signUpViewControllerDidPressSignUpButton:(ZOCSignUpViewController *)controller; 2118 | @end 2119 | 2120 | @protocol ZOCSignUpViewControllerDataSource 2121 | - (ZOCUserCredentials *)credentialsForSignUpViewController:(ZOCSignUpViewController *)controller; 2122 | @end 2123 | 2124 | 2125 | @interface ZOCSignUpViewController : UIViewController 2126 | 2127 | @property (nonatomic, weak) id delegate; 2128 | @property (nonatomic, weak) id dataSource; 2129 | 2130 | @end 2131 | 2132 | ``` 2133 | 2134 | 2135 | 代理方法必须以调用者(即委托者)作为第一个参数,就像上面的例子一样。否则代理者无法区分不同的委托者实例。换句话说,调用者(委托者)没有被传递给代理,那就没有方法让代理处理两个不同的委托者,所以下面这种写法人神共怒: 2136 | 2137 | 2138 | ```objective-c 2139 | - (void)calculatorDidCalculateValue:(CGFloat)value; 2140 | ``` 2141 | 2142 | 默认情况下,代理者需要实现 protocol 的方法。可以用`@required` 和 `@optional` 关键字来标记方法是否是必要的还是可选的(默认是 `@required`: 必需的)。 2143 | 2144 | 2145 | ```objective-c 2146 | @protocol ZOCSignUpViewControllerDelegate 2147 | @required 2148 | - (void)signUpViewController:(ZOCSignUpViewController *)controller didProvideSignUpInfo:(NSDictionary *)dict; 2149 | @optional 2150 | - (void)signUpViewControllerDidPressSignUpButton:(ZOCSignUpViewController *)controller; 2151 | @end 2152 | ``` 2153 | 2154 | 对于可选的方法,委托者必须在发送消息前检查代理是否确实实现了特定的方法(否则会 crash): 2155 | 2156 | ```objective-c 2157 | if ([self.delegate respondsToSelector:@selector(signUpViewControllerDidPressSignUpButton:)]) { 2158 | [self.delegate signUpViewControllerDidPressSignUpButton:self]; 2159 | } 2160 | ``` 2161 | 2162 | ### 继承 2163 | 2164 | 2165 | 有时候你可能需要重写代理方法。考虑有两个 UIViewController 子类的情况:UIViewControllerA 和 UIViewControllerB,有下面的类继承关系。 2166 | 2167 | `UIViewControllerB < UIViewControllerA < UIViewController` 2168 | 2169 | `UIViewControllerA` 遵从 `UITableViewDelegate` 并且实现了 `- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath`. 2170 | 2171 | 你可能会想要在 `UIViewControllerB` 中提供一个不同的实现,这个实现可能是这样子的: 2172 | 2173 | ```objective-c 2174 | 2175 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 2176 | CGFloat retVal = 0; 2177 | if ([super respondsToSelector:@selector(tableView:heightForRowAtIndexPath:)]) { 2178 | retVal = [super tableView:self.tableView heightForRowAtIndexPath:indexPath]; 2179 | } 2180 | return retVal + 10.0f; 2181 | } 2182 | 2183 | ``` 2184 | 2185 | 但是如果超类(`UIViewControllerA`)没有实现这个方法呢?此时调用`[super respondsToSelector:@selector(tableView:heightForRowAtIndexPath:)]`方法,将使用 NSObject 的实现,在 `self` 上下文深入查找并且明确 `self` 实现了这个方法(因为 `UITableViewControllerA` 遵从 `UITableViewDelegate`),但是应用将在下一行发生崩溃,并提示如下错误信息: 2186 | 2187 | ``` 2188 | *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIViewControllerB tableView:heightForRowAtIndexPath:]: unrecognized selector sent to instance 0x8d82820' 2189 | 2190 | *** 由于未捕获异常 `NSInvalidArgumentException(无效的参数异常)`导致应用终止,理由是:向实例 ox8d82820 发送了无法识别的 selector `- [UIViewControllerB tableView:heightForRowAtIndexPath:]` 2191 | 2192 | ``` 2193 | 2194 | 这种情况下我们需要来询问特定的类实例是否可以响应对应的 selector。下面的代码提供了一个小技巧: 2195 | 2196 | ```objective-c 2197 | 2198 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 2199 | CGFloat retVal = 0; 2200 | if ([[UIViewControllerA class] instancesRespondToSelector:@selector(tableView:heightForRowAtIndexPath:)]) { 2201 | retVal = [super tableView:self.tableView heightForRowAtIndexPath:indexPath]; 2202 | } 2203 | return retVal + 10.0f; 2204 | } 2205 | 2206 | ``` 2207 | 2208 | 就像上面丑陋的代码,通常它会是更好的设计架构的方式,因为这种方式代理方法不需要被重写。 2209 | 2210 | ### 多重委托 2211 | 2212 | 2213 | 多重委托是一个非常基础的概念,但是,大多数开发者对此非常不熟悉而使用 NSNotifications。就像你可能注意到的,委托和数据源是对象之间的通讯模式,但是只涉及两个对象:委托者和委托。 2214 | 2215 | 2216 | 数据源模式强制一对一的关系,当发送者请求信息时有且只能有一个对象来响应。对于代理模式而言这会有些不同,我们有足够的理由要去实现很多代理者等待(唯一委托者的)回调的场景。 2217 | 2218 | 2219 | 一些情况下至少有两个对象对特定委托者的回调感兴趣,而后者(即委托者)需要知道他的所有代理。这种方法在分布式系统下更为适用并且广泛使用于大型软件的复杂信息流程中。 2220 | 2221 | 多重委托可以用很多方式实现,但读者更在乎找到适合自己的个人实现。Luca Bernardi 在他的 [LBDelegateMatrioska](https://github.com/lukabernardi/LBDelegateMatrioska)中提供了上述范式的一个非常简洁的实现。 2222 | 2223 | 这里给出一个基本的实现,方便你更好地理解这个概念。即使在Cocoa中也有一些在数据结构中保存 weak 引用来避免 引用循环的方法, 这里我们使用一个类来保留代理对象的 weak 引用(就像单一代理那样): 2224 | 2225 | ```objective-c 2226 | @interface ZOCWeakObject : NSObject 2227 | 2228 | 2229 | @property (nonatomic, readonly, weak) id object; 2230 | // 译者注:这里原文并没有很好地实践自己在本书之前章节所讨论的关于property属性修饰符的 2231 | // 人体工程学法则: 从左到右: 原子性 ===》 读写权限 (别名) ===》 内存管理权限符 2232 | 2233 | + (instancetype)weakObjectWithObject:(id)object; 2234 | - (instancetype)initWithObject:(id)object; 2235 | 2236 | @end 2237 | ``` 2238 | 2239 | ```objective-c 2240 | @interface ZOCWeakObject () 2241 | @property (nonatomic, weak) id object; 2242 | @end 2243 | 2244 | @implementation ZOCWeakObject 2245 | 2246 | + (instancetype)weakObjectWithObject:(id)object { 2247 | return [[[self class] alloc] initWithObject:object]; 2248 | } 2249 | 2250 | - (instancetype)initWithObject:(id)object { 2251 | if ((self = [super init])) { 2252 | _object = object; 2253 | } 2254 | return self; 2255 | } 2256 | 2257 | - (BOOL)isEqual:(id)object { 2258 | if (self == object) { 2259 | return YES; 2260 | } 2261 | 2262 | if (![object isKindOfClass:[object class]]) { 2263 | return NO; 2264 | } 2265 | 2266 | return [self isEqualToWeakObject:(ZOCWeakObject *)object]; 2267 | } 2268 | 2269 | - (BOOL)isEqualToWeakObject:(ZOCWeakObject *)object { 2270 | if (!object) { 2271 | return NO; 2272 | } 2273 | 2274 | BOOL objectsMatch = [self.object isEqual:object.object]; 2275 | return objectsMatch; 2276 | } 2277 | 2278 | - (NSUInteger)hash { 2279 | return [self.object hash]; 2280 | } 2281 | 2282 | @end 2283 | ``` 2284 | 2285 | 2286 | 使用 weak 对象来实现多重代理的简单组件: 2287 | 2288 | 2289 | ```objective-c 2290 | @protocol ZOCServiceDelegate 2291 | @optional 2292 | - (void)generalService:(ZOCGeneralService *)service didRetrieveEntries:(NSArray *)entries; 2293 | @end 2294 | 2295 | @interface ZOCGeneralService : NSObject 2296 | - (void)registerDelegate:(id)delegate; 2297 | - (void)deregisterDelegate:(id)delegate; 2298 | @end 2299 | 2300 | @interface ZOCGeneralService () 2301 | @property (nonatomic, strong) NSMutableSet *delegates; 2302 | @end 2303 | ``` 2304 | 2305 | 2306 | ```objective-c 2307 | @implementation ZOCGeneralService 2308 | - (void)registerDelegate:(id)delegate { 2309 | if ([delegate conformsToProtocol:@protocol(ZOCServiceDelegate)]) { 2310 | [self.delegates addObject:[[ZOCWeakObject alloc] initWithObject:delegate]]; 2311 | } 2312 | } 2313 | 2314 | - (void)deregisterDelegate:(id)delegate { 2315 | if ([delegate conformsToProtocol:@protocol(ZOCServiceDelegate)]) { 2316 | [self.delegates removeObject:[[ZOCWeakObject alloc] initWithObject:delegate]]; 2317 | } 2318 | } 2319 | 2320 | - (void)_notifyDelegates { 2321 | ... 2322 | for (ZOCWeakObject *object in self.delegates) { 2323 | if (object.object) { 2324 | if ([object.object respondsToSelector:@selector(generalService:didRetrieveEntries:)]) { 2325 | [object.object generalService:self didRetrieveEntries:entries]; 2326 | } 2327 | } 2328 | } 2329 | } 2330 | 2331 | @end 2332 | ``` 2333 | 2334 | 在 `registerDelegate:` 和 `deregisterDelegate:` 方法的帮助下,连接/解除组件之间的联系很简单:在某些时间点上,如果代理不需要接收委托者的回调,仅仅需要'unsubscribe'. 2335 | 2336 | 当不同的 view 等待同一个回调来更新界面展示的时候,这很有用:如果 view 只是暂时隐藏(但是仍然存在),它仅仅需要取消对回调的订阅。 2337 | 2338 | 2339 | # 面向切面编程 2340 | 2341 | Aspect Oriented Programming (AOP,面向切面编程) 在 Objective-C 社区内没有那么有名,但是 AOP 在运行时可以有巨大威力。 但是因为没有事实上的标准,Apple 也没有开箱即用的提供,也显得不重要,开发者都不怎么考虑它。 2342 | 2343 | 2344 | 引用 [Aspect Oriented Programming](https://en.wikipedia.org/wiki/Aspect-oriented_programming) 维基页面: 2345 | 2346 | > An aspect can alter the behavior of the base code (the non-aspect part of a program) by applying advice (additional behavior) at various join points (points in a program) specified in a quantification or query called a pointcut (that detects whether a given join point matches). (一个切面可以通过在多个 join points 中附加的行为来改变基础代码的行为(程序的非切面的部分) ) 2347 | 2348 | 2349 | 在 Objective-C 的世界里,这意味着使用运行时的特性来为指定的方法追加 *切面* 。切面所附加的行为可以是这样的: 2350 | 2351 | 2352 | * 在类的特定方法调用前运行特定的代码 2353 | * 在类的特定方法调用后运行特定的代码 2354 | * 增加代码来替代原来的类的方法的实现 2355 | 2356 | 2357 | 有很多方法可以达成这些目的,但是我们没有深入挖掘,不过它们主要都是利用了运行时。 [Peter Steinberger](https://twitter.com/steipete) 写了一个库,[Aspects](https://github.com/steipete/Aspects) 完美地适配了 AOP 的思路。我们发现它值得信赖以及设计得非常优秀,所以我们就在这边作为一个简单的例子。 2358 | 2359 | 对于所有的 AOP库,这个库用运行时做了一些非常酷的魔法,可以替换或者增加一些方法(比 method swizzling 技术更有技巧性) 2360 | 2361 | 2362 | Aspect 的 API 有趣并且非常强大: 2363 | 2364 | ```objective-c 2365 | + (id)aspect_hookSelector:(SEL)selector 2366 | withOptions:(AspectOptions)options 2367 | usingBlock:(id)block 2368 | error:(NSError **)error; 2369 | - (id)aspect_hookSelector:(SEL)selector 2370 | withOptions:(AspectOptions)options 2371 | usingBlock:(id)block 2372 | error:(NSError **)error; 2373 | ``` 2374 | 2375 | 2376 | 比如,下面的代码会对于执行 `MyClass` 类的 `myMethod:` (实例或者类的方法) 执行块参数。 2377 | 2378 | ```objective-c 2379 | 2380 | [MyClass aspect_hookSelector:@selector(myMethod:) 2381 | withOptions:AspectPositionAfter 2382 | usingBlock:^(id aspectInfo) { 2383 | ... 2384 | } 2385 | error:nil]; 2386 | ``` 2387 | 2388 | 2389 | 换一句话说:任意的 `MyClass` 类型的对象(或者是类型本身当这个 @selector 方法为类方法时)的 `@selector` 方法执行完后,就会执行这个代码中块参数所提供的代码。 2390 | 2391 | 我们为 `MyClass` 类的 `myMethod:` 方法增加了切面。 2392 | 2393 | 通常 AOP 被用来实现横向切面。统计与日志就是一个完美的例子。 2394 | 2395 | 下面的例子里面,我们会用AOP用来进行统计。统计是iOS项目里面一个热门的特性,有很多选择比如 Google Analytics, Flurry, MixPanel, 等等. 2396 | 2397 | 大部分统计框架都有教程来指导如何追踪特定的界面和事件,包括在每一个类里写几行代码。 2398 | 2399 | 在 Ray Wenderlich 的博客里有 [文章](https://www.raywenderlich.com/53459/google-analytics-ios) 和一些示例代码,通过在你的 view controller 里面加入 [Google Analytics](https://developers.google.com/analytics/devguides/collection/ios/) 进行统计。 2400 | 2401 | ```objective-c 2402 | 2403 | - (void)logButtonPress:(UIButton *)button { 2404 | id tracker = [[GAI sharedInstance] defaultTracker]; 2405 | [tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"UX" 2406 | action:@"touch" 2407 | label:[button.titleLabel text] 2408 | value:nil] build]]; 2409 | } 2410 | ``` 2411 | 2412 | 2413 | 上面的代码在按钮点击的时候发送了特定的上下文事件。但是当你想追踪屏幕的时候会变得很糟。 2414 | 2415 | ```objective-c 2416 | 2417 | - (void)viewDidAppear:(BOOL)animated { 2418 | [super viewDidAppear:animated]; 2419 | 2420 | id tracker = [[GAI sharedInstance] defaultTracker]; 2421 | [tracker set:kGAIScreenName value:@"Stopwatch"]; 2422 | [tracker send:[[GAIDictionaryBuilder createAppView] build]]; 2423 | } 2424 | ``` 2425 | 2426 | 对于大部分有经验的iOS工程师,这看起来不是很好的代码。我们让 view controller 变得更糟糕了。因为我们加入了统计事件的代码,但是它不是 view controller 的职能。你可以反驳,因为你通常有特定的对象来负责统计追踪,并且你将代码注入了 view controller ,但是无论你隐藏逻辑,问题仍然存在 :你最后还是在`viewDidAppear:` 后插入了代码。 2427 | 2428 | 2429 | 2430 | 我们可以在类的 `viewDidAppear:` 方法上使用 AOP 来追踪屏幕,并且我们可以使用同样的方法在其他我们感兴趣的方法上添加事件追踪。比如当用户点击某个按钮时(比如:一般调用对应的 IBAction). 2431 | 2432 | 2433 | 方法很简洁且不具侵入性: 2434 | 2435 | * view controller 不会被不属于它的代码污染 2436 | * 为所有加入到我们代码的切面指定一个 SPOC 文件 (single point of customization)提供了可能 2437 | * SPOC 应该在 App 刚开始启动的时候用来添加切面 2438 | * 如果SPOC文件异常,至少有一个 selector 或者 类 识别不出来,应用将会在启动时崩溃(对我们来说这很酷). 2439 | * 公司负责统计的团队通常会提供统计文档,罗列出需要追踪的事件。这个文档可以很容易映射到一个 SPOC 文件。 2440 | * 追踪逻辑抽象化之后,扩展到很多其他统计框架会很方便 2441 | * 对于屏幕视图,对于需要定义 selector 的方法,只需要在 SPOC 文件修改相关的类(相关的切面会加入到 `viewDidAppear:` 方法)。如果要同时发送屏幕视图和事件,需要(依靠统计提供方)提供一个追踪的标示或者可能还需要提供其他的元信息。 2442 | 2443 | 2444 | 2445 | 我们可能希望一个 SPOC 文件类似下面的(同样的一个 .plist 文件会适配) 2446 | 2447 | ```objective-c 2448 | 2449 | NSDictionary *analyticsConfiguration() { 2450 | return @{ 2451 | @"trackedScreens" : @[ 2452 | @{ 2453 | @"class" : @"ZOCMainViewController", 2454 | @"label" : @"Main screen" 2455 | } 2456 | ], 2457 | @"trackedEvents" : @[ 2458 | @{ 2459 | @"class" : @"ZOCMainViewController", 2460 | @"selector" : @"loginViewFetchedUserInfo:user:", 2461 | @"label" : @"Login with Facebook" 2462 | }, 2463 | @{ 2464 | @"class" : @"ZOCMainViewController", 2465 | @"selector" : @"loginViewShowingLoggedOutUser:", 2466 | @"label" : @"Logout with Facebook" 2467 | }, 2468 | @{ 2469 | @"class" : @"ZOCMainViewController", 2470 | @"selector" : @"loginView:handleError:", 2471 | @"label" : @"Login error with Facebook" 2472 | }, 2473 | @{ 2474 | @"class" : @"ZOCMainViewController", 2475 | @"selector" : @"shareButtonPressed:", 2476 | @"label" : @"Share button" 2477 | } 2478 | ] 2479 | }; 2480 | } 2481 | ``` 2482 | 2483 | 2484 | 提及的架构托管 在 Github 的[EF Education First](https://github.com/ef-ctx/JohnnyEnglish/blob/master/CTXUserActivityTrackingManager.m) 中. 2485 | 2486 | ```objective-c 2487 | 2488 | - (void)setupWithConfiguration:(NSDictionary *)configuration { 2489 | // screen views tracking 2490 | for (NSDictionary *trackedScreen in configuration[@"trackedScreens"]) { 2491 | Class clazz = NSClassFromString(trackedScreen[@"class"]); 2492 | 2493 | [clazz aspect_hookSelector:@selector(viewDidAppear:) 2494 | withOptions:AspectPositionAfter 2495 | usingBlock:^(id aspectInfo) { 2496 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 2497 | ^{ 2498 | NSString *viewName = trackedScreen[@"label"]; 2499 | [tracker trackScreenHitWithName:viewName]; 2500 | }); 2501 | } 2502 | error:nil]; 2503 | 2504 | } 2505 | 2506 | // events tracking 2507 | for (NSDictionary *trackedEvents in configuration[@"trackedEvents"]) { 2508 | Class clazz = NSClassFromString(trackedEvents[@"class"]); 2509 | SEL selektor = NSSelectorFromString(trackedEvents[@"selector"]); 2510 | 2511 | [clazz aspect_hookSelector:selektor 2512 | withOptions:AspectPositionAfter 2513 | usingBlock:^(id aspectInfo) { 2514 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 2515 | ^{ 2516 | UserActivityButtonPressedEvent *buttonPressEvent = \ 2517 | [UserActivityButtonPressedEvent \ 2518 | eventWithLabel:trackedEvents[@"label"]]; 2519 | [tracker trackEvent:buttonPressEvent]; 2520 | }); 2521 | } 2522 | error:nil]; 2523 | 2524 | } 2525 | } 2526 | ``` 2527 | 2528 | 2529 | # 参考资料 2530 | 2531 | 这里有一些和风格指南有关的苹果的文档: 2532 | 2533 | * [The Objective-C Programming Language](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Introduction/introObjectiveC.html) 2534 | * [Cocoa Fundamentals Guide](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaFundamentals/Introduction/Introduction.html) 2535 | * [Coding Guidelines for Cocoa](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html) 2536 | * [iOS App Programming Guide](https://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/Introduction/Introduction.html) 2537 | * [Apple Objective-C conventions](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/Conventions/Conventions.html): 来自苹果的代码约定 2538 | 2539 | 其他: 2540 | 2541 | * [Objective-Clean](http://objclean.com): an attempt to write a standard for writing Objective-C code with Xcode integration; 2542 | * [Uncrustify](http://uncrustify.sourceforge.net/): source code beautifier. 2543 | 2544 | ### 其他的 Objective-C 风格指南 2545 | 2546 | 2547 | 这里有一些和风格指南有关的苹果的文档。如果有一些本书没有涉猎的地方,你或许能在这些之中找到详细说明。 2548 | 2549 | 2550 | 来自 Apple 的: 2551 | 2552 | * [The Objective-C Programming Language](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Introduction/introObjectiveC.html) 2553 | * [Cocoa Fundamentals Guide](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaFundamentals/Introduction/Introduction.html) 2554 | * [Coding Guidelines for Cocoa](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html) 2555 | * [iOS App Programming Guide](http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/Introduction/Introduction.html) 2556 | 2557 | 来自社区的: 2558 | 2559 | * [NYTimes Objective-C Style Guide](https://github.com/NYTimes/objective-c-style-guide) 2560 | * [Google](http://google-styleguide.googlecode.com/svn/trunk/objcguide.xml) 2561 | * [GitHub](https://github.com/github/objective-c-style-guide) 2562 | * [Adium](https://trac.adium.im/wiki/CodingStyle) 2563 | * [Sam Soffes](https://gist.github.com/soffes/812796) 2564 | * [CocoaDevCentral](http://cocoadevcentral.com/articles/000082.php) 2565 | * [Luke Redpath](http://lukeredpath.co.uk/blog/my-objective-c-style-guide.html) 2566 | * [Marcus Zarra](http://www.cimgf.com/zds-code-style-guide/) 2567 | * [Ray Wenderlich](https://github.com/raywenderlich/objective-c-style-guide) 2568 | 2569 | 2570 | [1]: https://github.com/NYTimes/objective-c-style-guide/issues/6) 2571 | -------------------------------------------------------------------------------- /README-En.md: -------------------------------------------------------------------------------- 1 | # Zen and the Art of the Objective-C Craftsmanship 2 | 3 | 4 | 5 | ![](./images/zen-logo-thumb.png) 6 | 7 | 8 | 9 | 10 | * [Preface](#preface) 11 | * [Swift](#swift) 12 | * [For the Community](#for-the-community) 13 | * [Authors](#authors) 14 | * [Conditionals](#conditionals) 15 | * [Yoda conditions](#yoda-conditions) 16 | * [nil and BOOL checks](#nil-and-bool-checks) 17 | * [Golden Path](#golden-path) 18 | * [Complex Conditions](#complex-conditions) 19 | * [Ternary Operator](#ternary-operator) 20 | * [Error handling](#error-handling) 21 | * [Case Statements](#case-statements) 22 | * [Enumerated Types](#enumerated-types) 23 | * [Naming](#naming) 24 | * [General conventions](#general-conventions) 25 | * [Constants](#constants) 26 | * [Methods](#methods) 27 | * [Literals](#literals) 28 | * [Class](#class) 29 | * [Class name](#class-name) 30 | * [Initializer and dealloc](#initializer-and-dealloc) 31 | * [Designated and Secondary Initializers](#designated-and-secondary-initializers) 32 | * [Designated Initializer](#designated-initializer) 33 | * [Secondary Initializer](#secondary-initializer) 34 | * [References](#references) 35 | * [instancetype](#instancetype) 36 | * [Reference](#reference) 37 | * [Initialization Patterns](#initialization-patterns) 38 | * [Class cluster](#class-cluster) 39 | * [Singleton](#singleton) 40 | * [Properties](#properties) 41 | * [Init and Dealloc](#init-and-dealloc) 42 | * [Dot-Notation](#dot-notation) 43 | * [Property Declaration](#property-declaration) 44 | * [Private Properties](#private-properties) 45 | * [Mutable Object](#mutable-object) 46 | * [Lazy Loading](#lazy-loading) 47 | * [Methods](#methods) 48 | * [Parameter Assert](#parameter-assert) 49 | * [Private methods](#private-methods) 50 | * [Equality](#equality) 51 | * [Categories](#categories) 52 | * [Protocols](#protocols) 53 | * [NSNotification](#nsnotification) 54 | * [Beautifying the code](#beautifying-the-code) 55 | * [Spacing](#spacing) 56 | * [Line Breaks](#line-breaks) 57 | * [Brackets](#brackets) 58 | * [Code Organization](#code-organization) 59 | * [Exploit Code Block](#exploit-code-block) 60 | * [Pragma](#pragma) 61 | * [Pragma Mark](#pragma-mark) 62 | * [Explicit warnings and errors](#explicit-warnings-and-errors) 63 | * [Docstrings](#docstrings) 64 | * [Comments](#comments) 65 | * [Header Documentation](#header-documentation) 66 | * [Inter-Object Communication](#inter-object-communication) 67 | * [Blocks](#blocks) 68 | * [Under the Hood](#under-the-hood) 69 | * [Retain cycles on self](#retain-cycles-on-self) 70 | * [Delegate and DataSource](#delegate-and-datasource) 71 | * [Inheritance](#inheritance) 72 | * [Multiple Delegation](#multiple-delegation) 73 | * [Aspect Oriented Programming](#aspect-oriented-programming) 74 | * [References](#references) 75 | * [Other Objective-C Style Guides](#other-objective-c-style-guides) 76 | 77 | 78 | 79 | 80 | # Preface 81 | 82 | We started writing this book on November 2013. The initial goal was to provide guidelines to write the most clean Objective-C code possible: there are too many guidelines out there and all of them are debatable. We didn't aim introducing hard rules but, instead, a way for writing code to be more uniform as possible across different developers. 83 | With time the scope moved to explain how to design and architecture good code. 84 | 85 | The idea underneath is that the code should not only compile, instead it should "validate". Good code has several characteristics: should be concise, self-explanatory, well organized, well documented, well named, well designed and stand the test of time. 86 | The main goals behind the curtain are that clarity always wins over performance and a rationale for a choice should always be provided. 87 | Some topics discussed here are general and independent from the language even if everything is tied up to Objective-C. 88 | 89 | ## Swift 90 | 91 | On June 6th, 2014 Apple announced the new programming language to be used for iOS and Mac development in future: Swift. 92 | This new language is a radical departure from Objective-C and, of course, has caused a change in our plan for writing this book. It boiled down to the decision of releasing the current status of this essay without continuing our journey in unfolding the topics we originally planned to include. 93 | Objective-C is not going anywhere but at the same time continuing to write a book on a language that will not receive the same attention as it used to, is not a wise move. 94 | 95 | ## For the Community 96 | 97 | We have released this book for free and for the community because we hope to provide value to the reader, if each one of you can learn at least one best practice we have reached our goal. 98 | 99 | We have done our best to polish this text and make it pleasant to the reader but we may have made typos, mistakes or left any part incomplete. We strongly encourage you to give us feedback and suggest improvements, so please get in touch with us if have any. We particularly appreciate pull requests. 100 | 101 | ## Authors 102 | 103 | **Luca Bernardi** 104 | 105 | - http://lucabernardi.com 106 | - @luka_bernardi 107 | - http://github.com/lukabernardi 108 | 109 | **Alberto De Bortoli** 110 | 111 | - http://albertodebortoli.com 112 | - @albertodebo 113 | - http://github.com/albertodebortoli 114 | 115 | 116 | # Conditionals 117 | 118 | Conditional bodies should always use braces even when a conditional body could be written without braces (e.g., it is one line only) to prevent errors. These errors include adding a second line and expecting it to be part of the if-statement. Another, even more dangerous defect, may happen where the line "inside" the if-statement is commented out, and the next line unwittingly becomes part of the if-statement. 119 | 120 | **Preferred:** 121 | 122 | ```objective-c 123 | if (!error) { 124 | return success; 125 | } 126 | ``` 127 | 128 | **Not preferred:** 129 | 130 | ```objective-c 131 | if (!error) 132 | return success; 133 | ``` 134 | 135 | or 136 | 137 | ```objective-c 138 | if (!error) return success; 139 | ``` 140 | 141 | In February 2014 the well-know [goto fail](https://gotofail.com/) was found in the Apple's SSL/TLS implementation. 142 | The bug was due to a repeated `goto` statement after an `if` condition, wrapping the `if` branch in parentheses would have prevented the issue. 143 | 144 | The code extract: 145 | 146 | ```objective-c 147 | static OSStatus 148 | SSLVerifySignedServerKeyExchange(SSLContext *ctx, bool isRsa, SSLBuffer signedParams, 149 | uint8_t *signature, UInt16 signatureLen) { 150 | OSStatus err; 151 | ... 152 | 153 | if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0) 154 | goto fail; 155 | if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0) 156 | goto fail; 157 | goto fail; 158 | if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0) 159 | goto fail; 160 | ... 161 | 162 | fail: 163 | SSLFreeBuffer(&signedHashes); 164 | SSLFreeBuffer(&hashCtx); 165 | return err; 166 | } 167 | ``` 168 | 169 | Easy to spot, there are 2 `goto fail;` lines one after the other without parentheses. We definitely don't want to risk creating bugs like the one above right? 170 | 171 | In addition, this style is more consistent with all other conditionals, and therefore more easily scannable. 172 | 173 | 174 | ## Yoda conditions 175 | 176 | Always avoid Yoda conditions. A Yoda condition is when comparing a constant to a variable instead of the other way around. It's like saying "if blue is the sky" or "if tall is the man" instead of "if the sky is blue" or "if the man is tall". 177 | 178 | ![Yoda](./images/yoda.png) 179 | 180 | **Preferred:** 181 | ```objective-c 182 | if ([myValue isEqual:@42]) { ... 183 | ``` 184 | 185 | **Not preferred:** 186 | ```objective-c 187 | if ([@42 isEqual:myValue]) { ... 188 | ``` 189 | 190 | ## nil and BOOL checks 191 | 192 | On a similar note of the Yoda conditions, also the nil check has been at the centre of debates. Some notous libraries out there use to check for an object to be or not to be nil as so: 193 | 194 | ```objective-c 195 | if (nil == myValue) { ... 196 | ``` 197 | 198 | One could argue that this is amiss or similar to a Yoda condition as nil is kind of a constant. The reason why sometimes programmers use this approach to prevent error that are difficult to debug. Consider the following code: 199 | 200 | ```objective-c 201 | if (myValue == nil) { ... 202 | ``` 203 | 204 | If a typo occurs and the programmer actually types: 205 | 206 | ```objective-c 207 | if (myValue = nil) { ... 208 | ``` 209 | 210 | it would be a valid assignment, indeed hard to debug if you are an experienced programmer (and therefore probably with some kind of visual impairment). That could never occur putting `nil` as argument on the left as it is nor assignable. There is also to be said that if the programmer uses this approach, he or she is perfectly aware of the underlying motivation and therefore the whole thing decades as it would be better to just double check what just typed. 211 | 212 | More on this, to avoid all this fuss the approach that leave no space to doubt is to use the exclamation mark. Since `nil` resolves to `NO` it is unnecessary to compare it in conditions. Also, never compare something directly to `YES`, because `YES` is defined to 1 and a `BOOL` can be up to 8 bits as it is a char underneath. 213 | 214 | **Preferred:** 215 | ```objective-c 216 | if (someObject) { ... 217 | if (![someObject boolValue]) { ... 218 | if (!someObject) { ... 219 | ``` 220 | 221 | **Not Preferred:** 222 | ```objective-c 223 | if (someObject == YES) { ... // Wrong 224 | if (myRawValue == YES) { ... // Never do this. 225 | if ([someObject boolValue] == NO) { ... 226 | ``` 227 | 228 | This allows also for more consistency across files and greater visual clarity. 229 | 230 | ## Golden Path 231 | 232 | When coding with conditionals, the left hand margin of the code should be the "golden" or "happy" path. That is, don't nest `if` statements. Multiple return statements are OK. This will avoid the growth of cyclomatic complexity and make the code easier to read because the important part of your method is not nested inside a branch but you have a visual clue of what is the most relevant code. 233 | 234 | **Preferred:** 235 | 236 | ```objective-c 237 | - (void)someMethod { 238 | if (![someOther boolValue]) { 239 | return; 240 | } 241 | 242 | // Do something important 243 | } 244 | ``` 245 | 246 | **Not preferred:** 247 | 248 | ```objective-c 249 | - (void)someMethod { 250 | if ([someOther boolValue]) { 251 | // Do something important 252 | } 253 | } 254 | ``` 255 | ## Complex Conditions 256 | 257 | When you have complex condition in the `if` clause you should always extract them and assign to a `BOOL` variable to make more clear the logic and the meaning of every single conditions. 258 | 259 | ```objective-c 260 | BOOL nameContainsSwift = [sessionName containsString:@"Swift"]; 261 | BOOL isCurrentYear = [sessionDateCompontents year] == 2014; 262 | BOOL isSwiftSession = nameContainsSwift && isCurrentYear; 263 | 264 | if (isSwiftSession) { 265 | // Do something very cool 266 | } 267 | ``` 268 | 269 | ## Ternary Operator 270 | 271 | The Ternary operator, `?` , should only be used when it increases clarity or code neatness. A single condition is usually all that should be evaluated. Evaluating multiple conditions is usually more understandable as an if statement, or refactored into instance variables. 272 | 273 | **Preferred:** 274 | ```objective-c 275 | result = a > b ? x : y; 276 | ``` 277 | 278 | **Not preferred:** 279 | ```objective-c 280 | result = a > b ? x = c > d ? c : d : y; 281 | ``` 282 | 283 | When the second argument of the ternary operator (the if branch) returns the same object that was checked for existence in the condition, the following syntax is neat: 284 | 285 | **Preferred:** 286 | ```objective-c 287 | result = object ? : [self createObject]; 288 | ``` 289 | 290 | **Not preferred:** 291 | ```objective-c 292 | result = object ? object : [self createObject]; 293 | ``` 294 | 295 | ## Error handling 296 | 297 | When methods return an error parameter by reference, check the returned value, not the error variable. 298 | 299 | **Preferred:** 300 | ```objective-c 301 | NSError *error = nil; 302 | if (![self trySomethingWithError:&error]) { 303 | // Handle Error 304 | } 305 | ``` 306 | 307 | Moreover, some of Apple's APIs write garbage values to the error parameter (if non-NULL) in successful cases, so checking the error can cause false negatives (and subsequently crash). 308 | 309 | 310 | # Case Statements 311 | 312 | Braces are not required for case statements, unless enforced by the complier. 313 | When a case contains more than one line, braces should be added. 314 | 315 | ```objective-c 316 | switch (condition) { 317 | case 1: 318 | // ... 319 | break; 320 | case 2: { 321 | // ... 322 | // Multi-line example using braces 323 | break; 324 | } 325 | case 3: 326 | // ... 327 | break; 328 | default: 329 | // ... 330 | break; 331 | } 332 | ``` 333 | 334 | There are times when the same code can be used for multiple cases, and a fall-through should be used. A fall-through is the removal of the 'break' statement for a case thus allowing the flow of execution to pass to the next case value. 335 | 336 | ```objective-c 337 | switch (condition) { 338 | case 1: 339 | case 2: 340 | // code executed for values 1 and 2 341 | break; 342 | default: 343 | // ... 344 | break; 345 | } 346 | ``` 347 | 348 | When using an enumerated type for a switch, `default` is not needed. For example: 349 | 350 | ```objective-c 351 | switch (menuType) { 352 | case ZOCEnumNone: 353 | // ... 354 | break; 355 | case ZOCEnumValue1: 356 | // ... 357 | break; 358 | case ZOCEnumValue2: 359 | // ... 360 | break; 361 | } 362 | ``` 363 | 364 | Moreover, avoiding the default case, if new values are added to the enum, the programmer is immediately notified with a warning: 365 | 366 | `Enumeration value 'ZOCEnumValue3' not handled in switch.` 367 | 368 | ### Enumerated Types 369 | 370 | When using `enum`s, it is recommended to use the new fixed underlying type specification because it has stronger type checking and code completion. The SDK now includes a macro to facilitate and encourage use of fixed underlying types — `NS_ENUM()` 371 | 372 | **Example:** 373 | 374 | ```objective-c 375 | typedef NS_ENUM(NSUInteger, ZOCMachineState) { 376 | ZOCMachineStateNone, 377 | ZOCMachineStateIdle, 378 | ZOCMachineStateRunning, 379 | ZOCMachineStatePaused 380 | }; 381 | ``` 382 | 383 | # Naming 384 | 385 | ## General conventions 386 | 387 | Apple naming conventions should be adhered to wherever possible, especially those related to [memory management rules](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html) ([NARC](http://stackoverflow.com/a/2865194/340508)). 388 | 389 | Long, descriptive method and variable names are good. 390 | 391 | **Preferred:** 392 | 393 | ```objective-c 394 | UIButton *settingsButton; 395 | ``` 396 | 397 | **Not Preferred:** 398 | ```objective-c 399 | UIButton *setBut; 400 | ``` 401 | 402 | ## Constants 403 | 404 | Constants should be camel-case with all words capitalized and prefixed by the related class name for clarity. 405 | 406 | **Preferred:** 407 | ```objective-c 408 | static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4; 409 | ``` 410 | 411 | **Not Preferred:** 412 | ```objective-c 413 | static const NSTimeInterval fadeOutTime = 0.4; 414 | ``` 415 | 416 | Constants are preferred over in-line string literals or numbers, as they allow for easy reproduction of commonly used variables and can be quickly changed without the need for find and replace. Constants should be declared as `static` constants and not `#define`s unless explicitly being used as a macro. 417 | 418 | **Preferred:** 419 | ```objective-c 420 | static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification"; 421 | static const CGFloat ZOCImageThumbnailHeight = 50.0f; 422 | ``` 423 | 424 | **Not Preferred:** 425 | 426 | ```objective-c 427 | #define CompanyName @"Apple Inc." 428 | #define magicNumber 42 429 | ``` 430 | 431 | Constants exposed externally should use this pattern in the interface file: 432 | 433 | ```objective-c 434 | extern NSString *const ZOCCacheControllerDidClearCacheNotification; 435 | ``` 436 | and the previously defined assignment should be in the implementation file. 437 | 438 | You only need to add the namespace to a constant that is made public. Even though constants used within an implementation could follow a different pattern, there is no need not to be consistent with the rule. 439 | 440 | 441 | ## Methods 442 | 443 | In method signatures, there should be a space after the method type (`-`/`+` symbol). There should be a space between the method segments (matching Apple's style). Always include a keyword and be descriptive with the word before the argument which describes the argument. 444 | 445 | The usage of the word "and" is reserved. It should not be used for multiple parameters as illustrated in the `initWithWidth:height:` example below. 446 | 447 | **Preferred:** 448 | ```objective-c 449 | - (void)setExampleText:(NSString *)text image:(UIImage *)image; 450 | - (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag; 451 | - (id)viewWithTag:(NSInteger)tag; 452 | - (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height; 453 | ``` 454 | 455 | **Not Preferred:** 456 | 457 | ```objective-c 458 | - (void)setT:(NSString *)text i:(UIImage *)image; 459 | - (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag; 460 | - (id)taggedView:(NSInteger)tag; 461 | - (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height; 462 | - (instancetype)initWith:(int)width and:(int)height; // Never do this. 463 | ``` 464 | 465 | ## Literals 466 | 467 | `NSString`, `NSDictionary`, `NSArray`, and `NSNumber` literals should be used whenever creating immutable instances of those objects. Pay special care that `nil` values not be passed into `NSArray` and `NSDictionary` literals, as this will cause a crash. 468 | 469 | **For example:** 470 | 471 | ```objective-c 472 | NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"]; 473 | NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"}; 474 | NSNumber *shouldUseLiterals = @YES; 475 | NSNumber *buildingZIPCode = @10018; 476 | ``` 477 | 478 | **Not:** 479 | 480 | ```objective-c 481 | NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil]; 482 | NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil]; 483 | NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES]; 484 | NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018]; 485 | ``` 486 | 487 | For the mutable counterparts of these classes we recommend, instead, the use of the explicit classes such as `NSMutableArray`, `NSMutableString`, and so on. 488 | 489 | The following **should be avoided**: 490 | 491 | ```objective-c 492 | NSMutableArray *aMutableArray = [@[] mutableCopy]; 493 | ``` 494 | 495 | The problems with the previous notation are both of efficiency and readability. 496 | On the efficiency side, an unnecessarily immutable object is created and immediately thrown away; this unlikely will slow down your app (unless the method here is called frequently) but there is really no reason to do this just to save some characters. 497 | Regarding the readability, we can see two problems here: the first is that when scanning through the code and seeing `@[]` your mind is immediately connected to and instance of `NSArray`, but in this case you need to stop and check more thoughtfully. Another aspect to take into account is that it would be very likely that someone with less experience will see your code and depending on his background he might not be very comfortable with the dichotomy between mutable and immutable objects. He or she could not be very familiar with the meaning of creating a mutable copy (obviously we are not suggesting that this knowledge is not necessary). Again, this is not something absolutely wrong but is more about code usability (that includes readability). 498 | 499 | # Class 500 | 501 | ## Class name 502 | 503 | You should always prefix your class with **three** capital-case letters (two letters are reserved for Apple classes), this looking-weird practice is done to mitigate the notable absence of namespaces in our favorite language. 504 | Some developers don't follow this practice for model objects (we observed this practice especially for Core Data objects), we advice to strictly follow this convention also in the case of Core Data objects because you might end up merging your Managed Object Model with other MOMs, maybe coming from third party library. 505 | As you may already have noticed, in this book the class (but not only) prefix is `ZOC`. 506 | 507 | There is another good practice that you might want to follow while choosing the name for your classes: when you're creating a subclass, you should put the specifying name part between the class prefix and the superclass name. This is better explained with an example: if you have a class named `ZOCNetworkClient`, example of subclass name will be `ZOCTwitterNetworkClient` (note "Twitter" between "ZOC" and "NetworkClient"); or, following the same rule, a `UIViewController` subclass would `ZOCTimelineViewController`. 508 | 509 | 510 | ## Initializer and dealloc 511 | 512 | The recommended way to organize the code is to have `dealloc` method placed at the top of the implementation (directly after the `@synthesize` and `@dynamic` statements) and the `init` should be placed directly below the `dealloc` methods of any class. In case of multiple initializer the designated initializer should be placed as first method, because is the one where most likely there is more logic, and the secondary initializers should follow. 513 | In these days with ARC, it is less likely that you will need to implement the dealloc method, but still the rationale is that by having the dealloc very close to your init methods, you are visually emphasizing the pairing between the methods. Usually in the dealloc method you will undo things that you have done in the init methods. 514 | 515 | `init` methods should be structured like this: 516 | 517 | ```objective-c 518 | - (instancetype)init { 519 | self = [super init]; // call the designated initializer 520 | if (self) { 521 | // Custom initialization 522 | } 523 | return self; 524 | } 525 | ``` 526 | 527 | It's interesting to understand why do we need to set the value of `self` with the return value of `[super init]` and what happens if we don't do that. 528 | 529 | Let's do a step back: we are so used to type expressions like `[[NSObject alloc] init]` that the difference between `alloc` and `init` fades away. A peculiarity of Objective-C is the so called *two stage creation*. This means that the allocation and the initialization are two separate steps, and therefore two different methods need to be called: `alloc` and `init`. 530 | - `alloc` is responsible for the object allocation. This process involve the allocation of enough memory to hold the object from the application virtual memory, writing the `isa` pointer, initializes the retain count, and zeroing all the instance variables. 531 | - `init` is responsible for initializing the object, that means brings the object in an usable state. This typically means set the instance variable of an object to reasonable and useful initial values. 532 | 533 | The `alloc` method will return a valid instance of an uninitialized object. Every message sent to this instance will be translated into an `objc_msgSend()` call where the parameter named `self` will be the pointer returned by `alloc`; in this way `self` is implicitly available in the scope of every methods. 534 | To conclude the two step creation the first method sent to a newly allocated instance should, by convention, be an `init` method. Notably the `init` implementation of `NSObject` is not doing more than simply return `self`. 535 | 536 | There is another important part of the contract with `init`: the method can (and should) signal to the caller that it wasn't able to successfully finish the initialization by returning `nil`; the initialization can fail for various reasons such as an input passed in the wrong format or the failure in successfully initialize a needed object. 537 | This is lead us to understand why you should always call `self = [super init]`, if your superclass is stating that it wasn't able to successfully initialize itself, you must assume that you are in an inconsistent state and therefore do not proceed with your own initialization and return `nil` as well in your implementation. If you fail to do so you might end up dealing with an object that is not usable, that will not behave as expected and that might eventually lead to crash your app. 538 | 539 | The ability to re-assign `self` can also be exploited by the `init` methods to return a different instance than the one they have been called on. Examples of this behavior are [Class cluster](#class-cluster) or some Cocoa classes that returns the same instance for identical (immutable) objects. 540 | 541 | ### Designated and Secondary Initializers 542 | 543 | Objective-C have the concept of designated and secondary initializers. 544 | The designated initializer is the initializer that takes the full complement of initialization parameters, the secondary initializers are one or more initializer methods that calls the designated initializer providing one or more default values for the initialization parameter. 545 | 546 | ```objective-c 547 | @implementation ZOCEvent 548 | 549 | - (instancetype)initWithTitle:(NSString *)title 550 | date:(NSDate *)date 551 | location:(CLLocation *)location { 552 | self = [super init]; 553 | if (self) { 554 | _title = title; 555 | _date = date; 556 | _location = location; 557 | } 558 | return self; 559 | } 560 | 561 | - (instancetype)initWithTitle:(NSString *)title 562 | date:(NSDate *)date { 563 | return [self initWithTitle:title date:date location:nil]; 564 | } 565 | 566 | - (instancetype)initWithTitle:(NSString *)title { 567 | return [self initWithTitle:title date:[NSDate date] location:nil]; 568 | } 569 | 570 | @end 571 | ``` 572 | 573 | Given the above example `initWithTitle:date:location:` is the designated initializer. The other two init methods are the secondary initializers because they are just calling the designated initializer of the class where they are implemented. 574 | 575 | #### Designated Initializer 576 | 577 | A class should always have one and only one designated initializer, all other init methods should call the designated one (even though there is an exception to this case). 578 | This dichotomy does not dictate any requirement about which initializer should be called. 579 | It should rather be valid to call any designated initializer in the class hierarchy, and it should be guaranteed that *all* the designated initializer in the class hierarchy are called starting from the furthest ancestor (typically `NSObject`) down to your class. 580 | Practically speaking this means that the first initialization code executed is the furthest ancestor, and then going down to the class hierarchy; giving to all the classes in the hierarchy the chance to do their specific part of initialization. This totally make sense: you want that everything you inherit from your superclass is in an usable state before doing your actual work. 581 | Even though it isn't explicitly stated, all the classes in Apple's frameworks are guaranteed to respect this contract and your classes should do the same to be sure to be a good citizen and behave correctly and as expected. 582 | 583 | There are three different situations that may present when defining a new class: 584 | 585 | 1. No need to override any initializers 586 | 2. Overriding designated initializer 587 | 3. Define a new designated initializer 588 | 589 | The first case is the most trivial: you don't need to add any specific logic at the initialization of your class you simply rely on you parent designated initializer. 590 | When you want to provide additional initialization logic you can decide to override the designated initializer. You should only override your immediate superclass's designated initializer and be sure that your implementation calls the super of the method you're overriding. 591 | A typical example is whether you create a `UIViewController` subclass overriding `initWithNibName:bundle:`: 592 | 593 | ```objective-c 594 | @implementation ZOCViewController 595 | 596 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { 597 | // call to the superclass designated initializer 598 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 599 | if (self) { 600 | // Custom initialization 601 | } 602 | return self; 603 | } 604 | 605 | @end 606 | ``` 607 | 608 | In case of `UIViewController` subclass it would be an error to override `init` as, in the case that the caller will try to initialize your class by calling `initWithNib:bundle`, your implementation will not be called. This also contradicts the rule that states that it should be valid to call any designated initializer. 609 | 610 | In case you want to provide your own designated initializer there are three steps that you need to follow in order to guarantee the correct behavior: 611 | 612 | 1. Declare your designated initializer, being sure to call your immediate superclass's designated initializer. 613 | 2. Override the immediate superclass's designated initializer calling your new designated initializer. 614 | 3. Document the new designated initializer. 615 | 616 | Lots of developers often miss the last two steps, this is not only a sign of little care, but in the case of the step two is clearly against the contract with the framework and can lead to very non-deterministic behaviors and bugs. 617 | Let's see an example of the correct way to implement this: 618 | 619 | ```objective-c 620 | @implementation ZOCNewsViewController 621 | 622 | - (id)initWithNews:(ZOCNews *)news { 623 | // call to the immediate superclass's designated initializer 624 | self = [super initWithNibName:nil bundle:nil]; 625 | if (self) { 626 | _news = news; 627 | } 628 | return self; 629 | } 630 | 631 | // Override the immediate superclass's designated initializer 632 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { 633 | // call the new designated initializer 634 | return [self initWithNews:nil]; 635 | } 636 | 637 | @end 638 | ``` 639 | 640 | In case you don't override `initWithNibName:bundle:` and the caller decides to initialize you class with this method (that would be a perfectly valid option) the method `initWithNews:` will never get called and this will bring to an incorrect initialization sequence where the specific initialization logic of your class is not executed. 641 | 642 | Even though it should be possible to infer what method is the designate initializer, it is always good to be clear and explicit (the future you or other developers that will work on your code will thank you). There are two strategies (non mutually exclusive) that you can decide to use: the first one you is to clearly state in the documentation which initializer is the designated one, but better yet you can be nice with your compiler and by using the compiler directive `__attribute__((objc_designated_initializer))` you can signal your intent. 643 | Using that directive will also help the compiler helping you and in fact the compiler will issue a warning if in your new designate initializer you don't call your superclass's designated initializer. 644 | There are, though, cases in which not calling the class designated initializer (and in turn providing the required parameters) and calling another designated initializer in the class hierarchy will bring the class in an useless state. Referring to the previous example, there is no point in instantiating a `ZOCNewsViewController` that should present a news, without the news itself. In this case you can enforce even more the need to call a very specific designated initializer by simply making all the other designated initializers not available. It is possible to do that by using another compiler directive `__attribute__((unavailable("Invoke the designated initializer"))) `, decorating a method with this attribute will make the compiler issuing an error if you try to call this method. 645 | 646 | Here the header relative to the implementation of the previous example (note the use of macros to don't repeat the code and being less verbose). 647 | 648 | ```objective-c 649 | 650 | @interface ZOCNewsViewController : UIViewController 651 | 652 | - (instancetype)initWithNews:(ZOCNews *)news ZOC_DESIGNATED_INITIALIZER; 653 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil ZOC_UNAVAILABLE_INSTEAD(initWithNews:); 654 | - (instancetype)init ZOC_UNAVAILABLE_INSTEAD(initWithNews:); 655 | 656 | @end 657 | 658 | ``` 659 | 660 | A corollary of what described above is that you should never call a secondary initializer from within the designated one (if the secondary initializer respects the contract, it will call the designated one). Doing so, the call is very likely to invoke one of the subclass's overridden init methods and it will result in infinite recursion. 661 | 662 | There is however an exception to all the rules laid out before that is whether an object conforms to the `NSCoding` protocol and it is initialized through the method `initWithCoder:`. 663 | We should distinguish between the case where the superclass is adopting `NSCoding` and when not. 664 | In the former case, if you just call `[super initWithCoder:]` you will probably have some shared initialization code with the designated initializer. A good way to handle this is to extract this code in a private method (i.e. `p_commonInit`). 665 | When your superclass does not adopt `NSCoding` the recommendation is to threat `initWithCoder:` as a secondary initializer and therefore call the `self` designated initializer. Note that this is against what suggested by Apple in the [Archives and Serializations Programming Guide](https://developer.apple.com/library/mac/documentation/cocoa/Conceptual/Archiving/Articles/codingobjects.html#//apple_ref/doc/uid/20000948-BCIHBJDE) where is stated: 666 | 667 | > the object should first invoke its superclass's designated initializer to initialize inherited state 668 | 669 | following this will actually lead to a non-deterministic behavior that will change if your class is or not a direct subclass of `NSObject`. 670 | 671 | #### Secondary Initializer 672 | 673 | As stated in the previous paragraph, a secondary initializer is a sort of convenience method to provide default values / behaviors to the designated initializer. 674 | That said, it seems clear that you should not do any mandatory initialization in such method and you should never assume that this method will gets called. Again, the only methods that we are guaranteed to get called are the designated initializer. 675 | This imply that in your secondary designated initializer you should always call another secondary initializer or your `self` designated initializer. Sometimes, by mistake, one can type `super`; doing this will cause not to respect the aforementioned sequence of initialization (in this specific case by skipping the initialization of the current class). 676 | 677 | ##### References 678 | 679 | - https://developer.apple.com/library/ios/Documentation/General/Conceptual/DevPedia-CocoaCore/ObjectCreation.html 680 | - https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaEncyclopedia/Initialization/Initialization.html 681 | - https://developer.apple.com/library/ios/Documentation/General/Conceptual/DevPedia-CocoaCore/MultipleInitializers.html 682 | - https://blog.twitter.com/2014/how-to-objective-c-initializer-patterns 683 | 684 | 685 | 686 | ### instancetype 687 | 688 | One often doesn't realize that Cocoa is full of conventions, and they can help the compiler being a little bit more smart. Whether the compiler encounters `alloc` or `init` methods, it will know that, even though the return type of both method is `id`, those methods will always return objects that are an instance of the receiving class's type. As a consequence, this allows the compiler to provide and enforce the type checking (i.e. check that the methods called on the returned instance are valid). This kind of Clang smartness is due to what is called [related result type](http://clang.llvm.org/docs/LanguageExtensions.html#related-result-types), meaning that 689 | 690 | > messages sent to one of alloc and init methods will have the same static type as the instance of the receiver class 691 | 692 | To have more information about the convention that allow to automatically identify the related result type please refer to the [appropriate section]((http://clang.llvm.org/docs/LanguageExtensions.html#related-result-types)) in the Clang Language Extensions guide. 693 | A related result type can be explicitly stated using the `instancetype` keyword as return type and this can be very helpful in situation where a factory method or convenience constructor is used. This will hint the compiler with the correct type checking and, probably more important, will behave correctly also when subclassing. 694 | 695 | ```objective-c 696 | @interface ZOCPerson 697 | + (instancetype)personWithName:(NSString *)name; 698 | @end 699 | ``` 700 | 701 | Even though, according to the clang specification, `id` can be promoted to `instancetype` by the compiler. In the case of `alloc` or `init` we strongly encourage to use the return type `instancetype` for all class and instance methods that returns an instance of the class. 702 | This is mostly to form the habit and to be consistent (and possibly having a more readable interface) in all your APIs. Once again, by making small adjustments to your code you can improve the readability: with a simple glance you will be able to distinguish which methods are returning an instance of your class. Sort of details you will appreciate in the long run. 703 | 704 | ##### Reference 705 | - http://tewha.net/2013/02/why-you-should-use-instancetype-instead-of-id/ 706 | - http://tewha.net/2013/01/when-is-id-promoted-to-instancetype/ 707 | - http://clang.llvm.org/docs/LanguageExtensions.html#related-result-types 708 | - http://nshipster.com/instancetype/ 709 | 710 | 711 | 712 | ### Initialization Patterns 713 | 714 | #### Class cluster 715 | Class cluster as described in the Apple's documentation is 716 | > an architecture that groups a number of private, concrete subclasses under a public, abstract superclass. 717 | 718 | If this description sounds familiar probably your instinct is correct. Class cluster is the Apple lingo for the [Abstract Factory](http://en.wikipedia.org/wiki/Abstract_factory_pattern) design pattern. 719 | The idea with class cluster is very simple: you typically have an abstract class that during the initialization process uses information, generally provided as parameters of the initializer method or available in the environment, to apply a logic and instantiate a concrete subclass. This "public facing" class should internally have a good knowledge of its subclass to be able to return the private subclass that best suited the task. 720 | This pattern is very useful because it removes the complexity of this initialization logic from the caller that only knows about the interface to communicate with the object, without actually caring about the underlying implementation. 721 | Class clusters are widely used in the Apple's frameworks; some of the most notably examples are `NSNumber` that can return an appropriate subclass depending of the type of number provided (Integer, Float, etc...) or `NSArray` that will return a concrete subclass with the best storage policy. 722 | The beauty of this pattern is that the caller can be completely unaware of the concrete subclass; in fact it can be used when designing a library to be able to swap the underlaying returned class without leaking any implementation detail as long as is respectful of the contract established in the abstract class. 723 | 724 | In our experience the use of a Class Cluster can be very helpful in removing a lot of conditional code. 725 | A typical example of this is when you have the same UIViewController subclass for both iPhone and iPad, but the behavior is slightly different depending on the the device. 726 | The naïve implementation is to put some conditional code checking the device in the methods where the different logic is needed, even though initially the place where this conditional logic is applied may be quite few they naturally tend to grow producing an explosion of code paths. 727 | A better design can be achieved by creating an abstract and generic view controller that will contain all the shared logic and then two specializing subclass for every device. 728 | The generic view controller will check the current device idiom and depending on that it will return the appropriate subclass. 729 | 730 | 731 | ```objective-c 732 | @implementation ZOCKintsugiPhotoViewController 733 | 734 | - (id)initWithPhotos:(NSArray *)photos { 735 | if ([self isMemberOfClass:ZOCKintsugiPhotoViewController.class]) { 736 | self = nil; 737 | 738 | if ([UIDevice isPad]) { 739 | self = [[ZOCKintsugiPhotoViewController_iPad alloc] initWithPhotos:photos]; 740 | } 741 | else { 742 | self = [[ZOCKintsugiPhotoViewController_iPhone alloc] initWithPhotos:photos]; 743 | } 744 | return self; 745 | } 746 | return [super initWithNibName:nil bundle:nil]; 747 | } 748 | 749 | @end 750 | ``` 751 | 752 | The previous code example show how to create a class cluster. First of all the `[self isMemberOfClass:ZOCKintsugiPhotoViewController.class]` is done to prevent the necessity to override the init method in the subclass in order to prevent an infinite recursion. 753 | When `[[ZOCKintsugiPhotoViewController alloc] initWithPhotos:photos]` will get called the previous check will be true, the `self = nil` is to remove every reference to the instance of `ZOCKintsugiPhotoViewController` that it will be deallocated , following there is the logic to choose which subclass should be initialized. 754 | Let's assume that we are running this code on an iPhone and that `ZOCKintsugiPhotoViewController_iPhone` is not overriding `initWithPhotos:`; in this case, when executing `self = [[ZOCKintsugiPhotoViewController_iPhone alloc] initWithPhotos:photos];` the `ZOCKintsugiPhotoViewController` will be called and here is when the first check comes handy, given that now is not exactly `ZOCKintsugiPhotoViewController` the check will be false calling the `return [super initWithNibName:nil bundle:nil];` this will make continue the initialization following the correct initialization path highlighted in the previous session. 755 | 756 | #### Singleton 757 | 758 | Generally avoid using them if possible, use dependency injection instead. 759 | Nevertheless, unavoidable singleton objects should use a thread-safe pattern for creating their shared instance. As of GCD, it is possible to use the `dispatch_once()` function to 760 | 761 | ```objective-c 762 | + (instancetype)sharedInstance { 763 | static id sharedInstance = nil; 764 | static dispatch_once_t onceToken = 0; 765 | dispatch_once(&onceToken, ^{ 766 | sharedInstance = [[self alloc] init]; 767 | }); 768 | return sharedInstance; 769 | } 770 | ``` 771 | 772 | The use of dispatch_once(), which is synchronous, replaces the following, yet obsolete, idiom: 773 | 774 | ```objective-c 775 | + (instancetype)sharedInstance { 776 | static id sharedInstance; 777 | @synchronized(self) { 778 | if (sharedInstance == nil) { 779 | sharedInstance = [[MyClass alloc] init]; 780 | } 781 | } 782 | return sharedInstance; 783 | } 784 | ``` 785 | 786 | The benefits of `dispatch_once()` over this are that it's faster, it's also semantically cleaner, because the entire idea of dispatch_once() is "perform something once and only once", which is precisely what we're doing. This approach will also prevent [possible and sometimes prolific crashes][singleton_cocoasamurai]. 787 | 788 | Classic examples of acceptable singleton objects are the GPS and the accelerometer of a device. Even though Singleton objects can be subclassed, the cases where this comes useful are rare. The interface should put in evidence that the given class is intended to be used as a Singleton. Therefore, often a single public `sharedInstance` class method would suffice and no writable properties should be exposed. 789 | 790 | Trying to use a Singleton as a container for objects to share across the code or layer of your application is ugly and nasty, and should be interpreted as a design smell. 791 | 792 | [singleton_cocoasamurai]: http://cocoasamurai.blogspot.com/2011/04/singletons-your-doing-them-wrong.html 793 | 794 | ## Properties 795 | 796 | Properties should be named as descriptively as possible, avoiding abbreviation, and camel-case with the leading word being lowercase. Luckily our tool of choice is able to autocomplete everything we type (well... almost everything. Yes, I'm looking at you Xcode's Derived Data) so there is no reason to save a couple of chars, and it's better to convey as much information as possible in your source code. 797 | 798 | **For example:** 799 | ```objective-c 800 | NSString *text; 801 | ``` 802 | 803 | **Not:** 804 | ```objective-c 805 | NSString* text; 806 | NSString * text; 807 | ``` 808 | (Note: this preference is different for constants. This is actually a matter of common sense and readability: C++ developers often prefer to separate the type from the name of the variable, and as the type in its pure form would be `NSString*` (for objects allocated on the heap, as in C++ it'd be possible to allocate objects on the stack) the format `NSString* text;` is used.) 809 | 810 | Use auto-synthesize for properties rather than manual `@synthesize` statements unless your properties are part of a protocol rather than a concrete class. If Xcode can automatically synthesize the variable, then let it do so; moreover it'd be a part of the code that is just redundant and that you have to maintain without a real benefit. 811 | 812 | You should always use the setter and getter to access the property, except for the `init` and `dealloc` methods. Generally speaking, using the property gives you an increased visual clue that what you're accessing is outside you current scope and therefor is subject to side-effect. 813 | 814 | You should always prefer the setter because: 815 | 816 | * Using the setter will respect the defined memory-management semantics (`strong`, `weak`, `copy` etc...). This was definitely more relevant before ARC but it's still relevant; think, for example, the `copy` semantic, every time you use the setter the passed value is copied without any additional operation; 817 | * KVO notifications (`willChangeValueForKey`, `didChangeValueForKey`) are fired automatically; 818 | * It is easier to debug: you can set a breakpoint on the property declaration and the breakpoint will fire every time the getter/setter is called, or you can set a breakpoint inside a custom setter/getter; 819 | * It allows to add extra logic when setting a value in a single place. 820 | 821 | You should alway prefer the getter: 822 | 823 | * It is resilient to future change (e.g. the property is dynamically generated); 824 | * It allows subclassing; 825 | * It is easier to debug (i.e. Is possible to put a breakpoint in the getter and see who's accessing that specific getter); 826 | * It makes the intent clear and explicit: by accessing an ivar `_anIvar` you are actually accessing `self->_anIvar`. This can lead to problems, for instance, accessing the iVar inside a block (you're capturing and retaining self even if you don't explicitly see the keyword `self`); 827 | * It automatically fires KVO notifications; 828 | * The overhead introduced by the message sending is very low and in the most of the case is negligible. For more information on the performance penalty introduced by the property you may find an interesting performance rundown [Should I Use a Property or an Instance Variable?](http://blog.bignerdranch.com/4005-should-i-use-a-property-or-an-instance-variable/) 829 | 830 | #### Init and Dealloc 831 | 832 | There is however an exception to what stated before: you must never use the setter (or the getter) in the `init` (and other initializer method), and instead you should always access directly the variable using the instance variable. This is to be defensive against subclassing: eventually a subclass can override the setter (or getter) and trying to call other methods, access properties or iVars that aren't in a consistent state or fully-initialized. Remember that an object is considered fully initialized and in a consistent state only after the init returns. The same applies for the `dealloc` method (during the `dealloc` method an object can be in a inconsistent state). This is also clearly stated many times over time: 833 | 834 | * [Advanced Memory Management Programming Guide](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW6) under the self-explanatory section "Don't Use Accessor Methods in Initializer Methods and dealloc"; 835 | * [Migrating to Modern Objective-C](http://adcdownload.apple.com//wwdc_2012/wwdc_2012_session_pdfs/session_413__migrating_to_modern_objectivec.pdf) at WWDC 2012 at slide 27; 836 | * in a [pull request](https://github.com/NYTimes/objective-c-style-guide/issues/6) form Dave DeLong's. 837 | 838 | Moreover, using the setter in the init will not play nicely with `UIAppearence` proxy (please refer to [UIAppearance for Custom Views](http://petersteinberger.com/blog/2013/uiappearance-for-custom-views/) for additional informations). 839 | 840 | #### Dot-Notation 841 | 842 | When using the setter/getter always prefer the dot notation. 843 | Dot-notation should *always* be used for accessing and mutating properties. 844 | 845 | **For example:** 846 | ```objective-c 847 | view.backgroundColor = [UIColor orangeColor]; 848 | [UIApplication sharedApplication].delegate; 849 | ``` 850 | 851 | **Not:** 852 | ```objective-c 853 | [view setBackgroundColor:[UIColor orangeColor]]; 854 | UIApplication.sharedApplication.delegate; 855 | ``` 856 | Using the dot notation will ensure a visual clue to help distinguish between a property or a typical method call. 857 | 858 | ### Property Declaration 859 | 860 | The preferred way to declare a property is the following format 861 | 862 | ```objective-c 863 | @property (nonatomic, readwrite, copy) NSString *name; 864 | ``` 865 | 866 | The property attributes should be ordered as following: atomicity, read/write and memory-managements storage. By doing this your attributes are more likely to change in the right most position and it's easier to scan with your eyes. 867 | 868 | You must use `nonatomic` attribute, unless strictly necessary. On iOS, the locking introduced by `atomic` can significantly affect the performances. 869 | 870 | Properties that stores a block, in order to keep it alive after the end of the declaration scope, must be `copy` (the block is initially created on the stack, calling `copy` cause the block to be copied on the heap). 871 | 872 | In order to achieve a public getter and a private setter, you can declare the public property as `readonly` and re-declare the same property in the the class extension as `readwrite`: 873 | 874 | ```objective-c 875 | @interface MyClass : NSObject 876 | @property (nonatomic, readonly) NSObject *object 877 | @end 878 | 879 | @implementation MyClass () 880 | @property (nonatomic, readwrite, strong) NSObject *object 881 | @end 882 | ``` 883 | 884 | If the name of a `BOOL` property is expressed as an adjective, the property can omit the "is" prefix but specifies the conventional name for the get accessor, for example: 885 | 886 | ```objective-c 887 | @property (assign, getter=isEditable) BOOL editable; 888 | ``` 889 | Text and example taken from the [Cocoa Naming Guidelines](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingIvarsAndTypes.html#//apple_ref/doc/uid/20001284-BAJGIIJE). 890 | 891 | In the implementation file avoid the use of `@synthesize`, Xcode is already adding this for you. 892 | 893 | #### Private Properties 894 | 895 | Private properties should be declared in class extensions (anonymous categories) in the implementation file of a class. Named categories (e.g. `ZOCPrivate`) should never be used unless extending another class. 896 | 897 | **For example:** 898 | 899 | ```objective-c 900 | @interface ZOCViewController () 901 | @property (nonatomic, strong) UIView *bannerView; 902 | @end 903 | ``` 904 | 905 | ### Mutable Object 906 | 907 | Any property that potentially can be set with a mutable object (e.g. `NSString`,`NSArray`,`NSURLRequest`) must have the memory-management type to `copy`. This is done in order to ensure the encapsulation and prevent that the value is changed after the property is set without that the object know it. 908 | 909 | You should also avoid to expose mutable object in the public interface, because this allows clients of your class to change your own internal representation and break the encapsulation. You can provide a read-only property that returns an immutable copy of your object: 910 | 911 | ```objective-c 912 | /* .h */ 913 | @property (nonatomic, readonly) NSArray *elements 914 | 915 | /* .m */ 916 | - (NSArray *)elements { 917 | return [self.mutableElements copy]; 918 | } 919 | ``` 920 | 921 | ### Lazy Loading 922 | 923 | There are cases when instantiating an object can be expensive and/or needs to be configured once and has some configuration involved that you don't want to clutter the caller method. 924 | 925 | In this case, instead of allocating the object in the init method one could opt for overriding the property getter for lazy instantiation. Usually the template for this kind of operation is the following: 926 | 927 | ```objective-c 928 | - (NSDateFormatter *)dateFormatter { 929 | if (!_dateFormatter) { 930 | _dateFormatter = [[NSDateFormatter alloc] init]; 931 | NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; 932 | [dateFormatter setLocale:enUSPOSIXLocale]; 933 | [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSSS"]; 934 | } 935 | return _dateFormatter; 936 | } 937 | ``` 938 | 939 | Even though this can be beneficial in some circumstance we advice to be thoughtful when you decide to do, and actually this should be avoided. The following are the arguments against the use of property with lazy initialization: 940 | 941 | * The getter should not have side effect. By looking a getter on the right-hand side you're not thinking that this is causing an object to be allocated or will lead to side effect. In fact, trying to call a getter without using the returned value the compiler will emit the warning "getter should not used for side effect"; 942 | * You're moving the cost of the initialization as side effect of the first access, this will lead to difficulties to optimize performance issues (that are also hard to instrument); 943 | * The moment of instantiation can be non deterministic: for example you're expecting that this property is first accessed by a method but as soon as you change the class implementation, the accessor gets called before your original expectation. This can cause problems especially if the instantiation logic has dependencies on other state of the class that may be different. In general is better to explicitly express the dependency; 944 | * This behavior might not be KVO friendly. If the getter changes the reference it should fire a KVO notification for notify the change, it can be weird to receive a change notification when accessing a getter. 945 | 946 | 947 | ## Methods 948 | 949 | ### Parameter Assert 950 | Your method may require some parameter to satisfy certain condition (i.e. not to be nil): in such cases it's a good practice to use `NSParameterAssert()` to assert the condition and eventually throwing an exception. 951 | 952 | ### Private methods 953 | 954 | Never prefix your private method with a single underscore `_`, this prefix is reserved by Apple, doing otherwise expose you to the risk of overriding an existing Apple's private method. A recommended approach is to use the `p_` prefix (e.g. `p_privateMethod`). 955 | 956 | ## Equality 957 | 958 | In case you need to implement equality remember the contract: you need to implement both the `isEqual` and the `hash` methods. If two objects are considered equal through `isEqual`, the `hash` method must return the same value, but if `hash` returns the same value the object are not guaranteed to be equals. 959 | 960 | This contracts boils down to how the lookup of those objects is done when are stored in collections (i.e. `NSDictionary` and `NSSet` use hash table data structure underneath). 961 | 962 | ```objective-c 963 | @implementation ZOCPerson 964 | 965 | - (BOOL)isEqual:(id)object { 966 | if (self == object) { 967 | return YES; 968 | } 969 | 970 | if (![object isKindOfClass:[ZOCPerson class]]) { 971 | return NO; 972 | } 973 | 974 | // check objects properties (name and birthday) for equality 975 | ... 976 | return propertiesMatch; 977 | } 978 | 979 | - (NSUInteger)hash { 980 | return [self.name hash] ^ [self.birthday hash]; 981 | } 982 | 983 | @end 984 | ``` 985 | 986 | It is important to notice that the hash method must not return a constant. This is a typical error and causes serious problems as it will cause 100% of collisions in the hash table as the value returned by the hash method is actually used as key in the hash table. 987 | 988 | You should also always implement a typed equality check method with the following format `isEqualTo<#class-name-without-prefix#>:` 989 | If you can, it's always preferable to call the typed equal method in order to avoid the type checking overhead. 990 | 991 | A complete pattern for the isEqual* method should be as so: 992 | 993 | ```objective-c 994 | - (BOOL)isEqual:(id)object { 995 | if (self == object) { 996 | return YES; 997 | } 998 | 999 | if (![object isKindOfClass:[ZOCPerson class]]) { 1000 | return NO; 1001 | } 1002 | 1003 | return [self isEqualToPerson:(ZOCPerson *)object]; 1004 | } 1005 | 1006 | - (BOOL)isEqualToPerson:(Person *)person { 1007 | if (!person) { 1008 | return NO; 1009 | } 1010 | 1011 | BOOL namesMatch = (!self.name && !person.name) || 1012 | [self.name isEqualToString:person.name]; 1013 | BOOL birthdaysMatch = (!self.birthday && !person.birthday) || 1014 | [self.birthday isEqualToDate:person.birthday]; 1015 | 1016 | return haveEqualNames && haveEqualBirthdays; 1017 | } 1018 | ``` 1019 | 1020 | Given an object instance the computation of the `hash` should be deterministic, this is extremely important if the object is added to a container object (i.e. `NSArray`, `NSSet`, or `NSDictionary`) otherwise the behavior will be undefined (all container objects use the object's hash to do the lookup and enforce specific property like the uniqueness of the objects contained). That said, the calculation of the hash should always be made only by using immutable properties or, better yet, by guarantee the immutability of the objects. 1021 | 1022 | 1023 | # Categories 1024 | 1025 | It is ugly, we know, but categories should always be prefixed with your lower case prefix and an underscore i.e. `- (id)zoc_myCategoryMethod`. This practice is also [recommended by Apple](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html#//apple_ref/doc/uid/TP40011210-CH6-SW4). 1026 | 1027 | This is absolutely needed because implementing a category method with a name already existing in the extended object or in another category may result in an undefined behavior. Practically, what is going to happen is that the implementation of the last category loaded will be the one that gets called. 1028 | 1029 | In case you want to be sure that you're not replacing any implementation with your own category you can set the environment variable `OBJC_PRINT_REPLACED_METHODS` to `YES`, this will print in the console the names of the methods that have been replaced. 1030 | At the time of writing LLVM 5.1 does not emit any warning or error for this, so be careful and don't override methods in categories. 1031 | 1032 | A good practice is to use prefix also for category names. 1033 | 1034 | **Example:** 1035 | 1036 | ```objective-c 1037 | @interface NSDate (ZOCTimeExtensions) 1038 | - (NSString *)zoc_timeAgoShort; 1039 | @end 1040 | ``` 1041 | 1042 | **Not:** 1043 | 1044 | ```objective-c 1045 | @interface NSDate (ZOCTimeExtensions) 1046 | - (NSString *)timeAgoShort; 1047 | @end 1048 | ``` 1049 | 1050 | Category can be used to group related method in a header file. This is a very common practice in Apple's framework (nearby is proposed an extract from `NSDate` header) and we strongly encourage to do the same in your code. 1051 | In our experience creating this groups can be helpful in further refactoring: when the interface of a class starts growing can be a signal that your class is doing to much and therefore violating the Single Responsibility Principle, the previously created groups be used to better understand the different responsibilities and help in breaking down the class in more self-contained components. 1052 | 1053 | ```objective-c 1054 | 1055 | @interface NSDate : NSObject 1056 | 1057 | @property (readonly) NSTimeInterval timeIntervalSinceReferenceDate; 1058 | 1059 | @end 1060 | 1061 | @interface NSDate (NSDateCreation) 1062 | 1063 | + (instancetype)date; 1064 | + (instancetype)dateWithTimeIntervalSinceNow:(NSTimeInterval)secs; 1065 | + (instancetype)dateWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti; 1066 | + (instancetype)dateWithTimeIntervalSince1970:(NSTimeInterval)secs; 1067 | + (instancetype)dateWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date; 1068 | // ... 1069 | @end 1070 | ``` 1071 | 1072 | # Protocols 1073 | 1074 | A great miss in the Objective-C world is the outcome of the latest decades about abstract interfaces. The term interface is typically used to refer to the `.h` file of a class but it has also another meaning well known to Java programmers which is basically used to describe to a set of methods not backed by a concrete implementation. 1075 | In Objective-C the latter case is achieved by using protocols. For historical reasons, protocols (used as Java interfaces) are not so much used in Objective-C code and in general by the community. It's mainly because the majority of the code developed at Apple don't embrace this approach and almost all developers tend to follow Apple's patterns and guidelines. Apple uses protocols almost only for the delegation pattern. 1076 | The concept of abstract interface is very powerful, has roots in the computer science history and there are no reasons to pretend it can't be used in Objective-C. 1077 | 1078 | Here will be explained a powerful usage of protocols (intended as abstract interfaces) going through a concrete example: starting from a very bad designed architecture up to reaching a very well and reusable piece of code. 1079 | 1080 | The example presented is the implementation of a RSS feed reader (think of it as a common test task asked during technical interviews). 1081 | 1082 | The requirement is straightforward: presenting a remote RSS feed in a table view. 1083 | 1084 | A very naïve approach would be to create a `UITableViewController` subclass and code the entire logic for the retrieving of the feed data, the parsing and the displaying in one place, which is, in other words, a MVC (Massive View Controller). This would work but it's very poorly designed and unfortunately it'd suffice to pass the interview step in some not-so-demanding tech startups. 1085 | 1086 | A minimal step forward would be to follow the Single Responsibility Principle and create at least 2 components to do the different tasks: 1087 | 1088 | - a feed parser to parse the results gathered from an endpoint 1089 | - a feed reader to display the results 1090 | 1091 | The interfaces for these classes could be as so: 1092 | 1093 | ```objective-c 1094 | 1095 | @interface ZOCFeedParser : NSObject 1096 | 1097 | @property (nonatomic, weak) id delegate; 1098 | @property (nonatomic, strong) NSURL *url; 1099 | 1100 | - (id)initWithURL:(NSURL *)url; 1101 | 1102 | - (BOOL)start; 1103 | - (void)stop; 1104 | 1105 | @end 1106 | 1107 | ``` 1108 | 1109 | ```objective-c 1110 | 1111 | @interface ZOCTableViewController : UITableViewController 1112 | 1113 | - (instancetype)initWithFeedParser:(ZOCFeedParser *)feedParser; 1114 | 1115 | @end 1116 | 1117 | ``` 1118 | 1119 | The `ZOCFeedParser` is initialized with a `NSURL` to the endpoint to fetch the RSS feed (under the hood it will probably use NSXMLParser and NSXMLParserDelegate to create meaningful data) and the `ZOCTableViewController` is initialized with the parser. We want it to display the values retrieved by the parser and we do it using delegation with the following protocol: 1120 | 1121 | ```objective-c 1122 | 1123 | @protocol ZOCFeedParserDelegate 1124 | @optional 1125 | - (void)feedParserDidStart:(ZOCFeedParser *)parser; 1126 | - (void)feedParser:(ZOCFeedParser *)parser didParseFeedInfo:(ZOCFeedInfoDTO *)info; 1127 | - (void)feedParser:(ZOCFeedParser *)parser didParseFeedItem:(ZOCFeedItemDTO *)item; 1128 | - (void)feedParserDidFinish:(ZOCFeedParser *)parser; 1129 | - (void)feedParser:(ZOCFeedParser *)parser didFailWithError:(NSError *)error; 1130 | @end 1131 | 1132 | ``` 1133 | 1134 | It's a perfectly reasonable and suitable protocol to deal with RSS, I'd say. The view controller will conform to it in the public interface: 1135 | 1136 | ```objective-c 1137 | @interface ZOCTableViewController : UITableViewController 1138 | ``` 1139 | 1140 | and the final creation code is like so: 1141 | 1142 | ```objective-c 1143 | NSURL *feedURL = [NSURL URLWithString:@"http://bbc.co.uk/feed.rss"]; 1144 | 1145 | ZOCFeedParser *feedParser = [[ZOCFeedParser alloc] initWithURL:feedURL]; 1146 | 1147 | ZOCTableViewController *tableViewController = [[ZOCTableViewController alloc] initWithFeedParser:feedParser]; 1148 | feedParser.delegate = tableViewController; 1149 | ``` 1150 | 1151 | So far so good and you're probably happy with this new code, but how much of this code can be effectively reused? The view controller can only deal with objects of type `ZOCFeedParser`: at this point we just split the code in 2 components without any additional and tangible value than separation of responsibilities. 1152 | 1153 | The responsibility of the view controller should be to "display items provided by " but if we are allowed to pass to it only `ZOCFeedParser` objects this can't hold. Here surfaces the need for a more general type of object to be used by the view controller. 1154 | 1155 | We modify our feed parser introducing the `ZOCFeedParserProtocol` protocol (in the ZOCFeedParserProtocol.h file where also `ZOCFeedParserDelegate` will be). 1156 | 1157 | ```objective-c 1158 | 1159 | @protocol ZOCFeedParserProtocol 1160 | 1161 | @property (nonatomic, weak) id delegate; 1162 | @property (nonatomic, strong) NSURL *url; 1163 | 1164 | - (BOOL)start; 1165 | - (void)stop; 1166 | 1167 | @end 1168 | 1169 | @protocol ZOCFeedParserDelegate 1170 | @optional 1171 | - (void)feedParserDidStart:(id)parser; 1172 | - (void)feedParser:(id)parser didParseFeedInfo:(ZOCFeedInfoDTO *)info; 1173 | - (void)feedParser:(id)parser didParseFeedItem:(ZOCFeedItemDTO *)item; 1174 | - (void)feedParserDidFinish:(id)parser; 1175 | - (void)feedParser:(id)parser didFailWithError:(NSError *)error; 1176 | @end 1177 | 1178 | ``` 1179 | 1180 | Notice that the delegate protocol now deals with objects conforming to our new protocol and the interface file of the ZOCFeedParser would be more skinny: 1181 | 1182 | ```objective-c 1183 | 1184 | @interface ZOCFeedParser : NSObject 1185 | 1186 | - (id)initWithURL:(NSURL *)url; 1187 | 1188 | @end 1189 | 1190 | ``` 1191 | 1192 | As `ZOCFeedParser`now conforms to `ZOCFeedParserProtocol`, it must implement all the required methods. 1193 | At this point the view controller can accept any object conforming to the new protocol, having the certaincy that the object will respond to `start` and `stop` methods and that it will provide info through the delegate property. This is all the view controller should know about the given objects and no implementation details should concern it. 1194 | 1195 | 1196 | ```objective-c 1197 | 1198 | @interface ZOCTableViewController : UITableViewController 1199 | 1200 | - (instancetype)initWithFeedParser:(id)feedParser; 1201 | 1202 | @end 1203 | 1204 | ``` 1205 | 1206 | The change in the above snippet of code might seem a minor change, but actually it is a huge improvement as the view controller would work against a contract rather than a concrete implementation. This leads to many advantages: 1207 | 1208 | - the view controller can now accept any object that provide some information via the delegate property: this can be a RSS remote feed parser, a local one, a service that read other types of data remotely or even a service that fetch data from the local database; 1209 | - the feed parser object can be totally reused (as it was before after the first refactoring step); 1210 | - `ZOCFeedParser` and `ZOCFeedParserDelegate` can be reused by other components; 1211 | - `ZOCViewController` (UI logic apart) can be reused; 1212 | - it is easier to test as it'd be possible to use a mock object conforming to the expected protocol. 1213 | 1214 | When implementing a protocol you should always strive to adhere to the [Liskov Substitution Principle](http://en.wikipedia.org/wiki/Liskov_substitution_principle). This principle states that you should be able to replace one implementation of an interface ("protocol" in the Objective-C lingo) with another without breaking either the client or the implementation. 1215 | 1216 | In other words this means that your protocol should not leak the detail of the implementing classes; be even more careful when designing the abstraction expressed by you protocol and always keep in mind that the underlay implementation is irrelevant, what really matters is the contract that the abstraction expose to the consumer. 1217 | 1218 | Everything that is designed to be reused in future is implicitly better code and should be always the programmer's goal. Code designed this way is what makes the difference between an experienced programmer and a newbie. 1219 | 1220 | The final code proposed here can be found [here](http://github.com/albertodebortoli/ADBFeedReader). 1221 | 1222 | # NSNotification 1223 | 1224 | When you define your own `NSNotification` you should define your notification's name as a string constant. Like any string constant that you want to make available to other classes, it should be declared as `extern` in the public interface, and defined privately in the corresponding implementation. 1225 | Because you're exposing this symbol in the header you should follow the usual namespace rule prefixing the notification name with the class name that belongs to. 1226 | It's also good practice to name the notification using the verb Did/Will and terminate the name with the word "Notifications". 1227 | 1228 | ```objective-c 1229 | // Foo.h 1230 | extern NSString * const ZOCFooDidBecomeBarNotification 1231 | 1232 | // Foo.m 1233 | NSString * const ZOCFooDidBecomeBarNotification = @"ZOCFooDidBecomeBarNotification"; 1234 | ``` 1235 | 1236 | # Beautifying the code 1237 | 1238 | ### Spacing 1239 | 1240 | * Indent using 4 spaces. Never indent with tabs. Be sure to set this preference in Xcode. 1241 | * Method braces and other braces (`if`/`else`/`switch`/`while` etc.) always open on the same line as the statement but close on a new line. 1242 | 1243 | **Preferred:** 1244 | ```objective-c 1245 | if (user.isHappy) { 1246 | // Do something 1247 | } 1248 | else { 1249 | // Do something else 1250 | } 1251 | ``` 1252 | 1253 | **Not Preferred:** 1254 | ```objective-c 1255 | if (user.isHappy) 1256 | { 1257 | // Do something 1258 | } else { 1259 | // Do something else 1260 | } 1261 | ``` 1262 | 1263 | * There should be exactly one blank line between methods to aid in visual clarity and organization. Whitespace within methods should separate functionality, but often there should probably be new methods. 1264 | * Prefer using auto-synthesis. But if necessary, `@synthesize` and `@dynamic` should each be declared on new lines in the implementation. 1265 | * Colon-aligning method invocation should often be avoided. There are cases where a method signature may have more than 3 colons and colon-aligning makes the code more readable. Always colon align methods, even if they contain blocks. 1266 | 1267 | **Preferred:** 1268 | 1269 | ```objective-c 1270 | [UIView animateWithDuration:1.0 1271 | animations:^{ 1272 | // something 1273 | } 1274 | completion:^(BOOL finished) { 1275 | // something 1276 | }]; 1277 | ``` 1278 | 1279 | 1280 | **Not Preferred:** 1281 | 1282 | ```objective-c 1283 | [UIView animateWithDuration:1.0 animations:^{ 1284 | // something 1285 | } completion:^(BOOL finished) { 1286 | // something 1287 | }]; 1288 | ``` 1289 | 1290 | If auto indentation falls into bad readability, declare blocks in variables before or reconsider your method signature. 1291 | 1292 | ### Line Breaks 1293 | Line breaks are an important topic since this style guide is focused for print and online readability. 1294 | 1295 | For example: 1296 | ```objective-c 1297 | self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers]; 1298 | ``` 1299 | 1300 | A long line of code like the one above should be carried on to the second line adhering to this style guide's Spacing section (two spaces). 1301 | 1302 | ```objective-c 1303 | self.productsRequest = [[SKProductsRequest alloc] 1304 | initWithProductIdentifiers:productIdentifiers]; 1305 | ``` 1306 | 1307 | ### Brackets 1308 | 1309 | Use [Egyptian brackets](https://en.wikipedia.org/wiki/Indent_style#K.26R_style) for: 1310 | 1311 | * control structures (if-else, for, switch) 1312 | 1313 | Non-Egyptian brackets are accepted for: 1314 | 1315 | * class implementations (if any) 1316 | * method implementations 1317 | 1318 | 1319 | 1320 | 1321 | # Code Organization 1322 | 1323 | [Quoting](http://nshipster.com/pragma/) Mattt Thompson 1324 | 1325 | > code organization is a matter of hygiene 1326 | 1327 | We could not agree more. Having you code clearly organized in a clean and defined manner is a way to show respect for yourself and other people that will read and change you code (consider the future you included). 1328 | 1329 | ## Exploit Code Block 1330 | 1331 | A very obscure GCC behavior that it is also supported by Clang is the ability of a code block to return the value of the latest statement if enclosing in round bracket. 1332 | 1333 | ```objective-c 1334 | NSURL *url = ({ 1335 | NSString *urlString = [NSString stringWithFormat:@"%@/%@", baseURLString, endpoint]; 1336 | [NSURL URLWithString:urlString]; 1337 | }); 1338 | ``` 1339 | This feature can be nicely organized to group small chunk of code that usually are necessary only for the sole purpose of setting up a class. 1340 | This gives a reader an important visual clue and help reduce the visual noise focusing on the most important variable in the function. 1341 | Additionally, this technique has the advantage that all the variables declared inside the code block, as one might expect, are valid only inside that scope, this mean that the your not polluting the method's stack trace and you can reuse the variable name without having duplicated symbols. 1342 | 1343 | ## Pragma 1344 | 1345 | ### Pragma Mark 1346 | 1347 | `#pragma mark -` is a great way to organize the code inside a class and helps you grouping methods implementation. 1348 | We suggest to use `#pragma mark -`to separate: 1349 | 1350 | - methods in functional groupings 1351 | - protocols implementations. 1352 | - methods overridden from a superclass 1353 | 1354 | 1355 | ```objective-c 1356 | 1357 | - (void)dealloc { /* ... */ } 1358 | - (instancetype)init { /* ... */ } 1359 | 1360 | #pragma mark - View Lifecycle 1361 | 1362 | - (void)viewDidLoad { /* ... */ } 1363 | - (void)viewWillAppear:(BOOL)animated { /* ... */ } 1364 | - (void)didReceiveMemoryWarning { /* ... */ } 1365 | 1366 | #pragma mark - Custom Accessors 1367 | 1368 | - (void)setCustomProperty:(id)value { /* ... */ } 1369 | - (id)customProperty { /* ... */ } 1370 | 1371 | #pragma mark - IBActions 1372 | 1373 | - (IBAction)submitData:(id)sender { /* ... */ } 1374 | 1375 | #pragma mark - Public 1376 | 1377 | - (void)publicMethod { /* ... */ } 1378 | 1379 | #pragma mark - Private 1380 | 1381 | - (void)zoc_privateMethod { /* ... */ } 1382 | 1383 | #pragma mark - UITableViewDataSource 1384 | 1385 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ } 1386 | 1387 | #pragma mark - ZOCSuperclass 1388 | 1389 | // ... overridden methods from ZOCSuperclass 1390 | 1391 | #pragma mark - NSObject 1392 | 1393 | - (NSString *)description { /* ... */ } 1394 | 1395 | ``` 1396 | 1397 | The above marks will help to visually separate and organize the code. One of the pros is that you can cmd+click on the mark to jump to the symbol definition. 1398 | Be aware that even though the use of pragma mark is a sign of craftsmanship, it's not a good reason to make grow the number of methods in your class in a unbounded fashion: having too many of them should be a warning sign that your class has too many responsibilities and a good opportunity for refactoring. 1399 | 1400 | 1401 | ### Notes about pragma 1402 | 1403 | At http://raptureinvenice.com/pragmas-arent-just-for-marks there's a great discussion about pragmas, reported in part here. 1404 | 1405 | While most iOS developers don't play around much with compiler options, some options are useful to control how strictly to check (or not check) your code for errors. Sometimes, though, you want to make an exception directly in your code using a pragma, which purpose is to temporarily disable a compiler behavior. 1406 | 1407 | When you use ARC, the compiler inserts memory-management calls for you. There are cases, though, where it can get confused. One such case is when you use `NSSelectorFromString` to have a dynamically-named selector called. Since ARC can't know what the method will be and what kind of memory management to use, you'll be warned with `performSelector may cause a leak because its selector is unknown`. 1408 | 1409 | If you know your code won't leak, you can suppress the warning for just this instance by wrapping it like this: 1410 | 1411 | ```objective-c 1412 | #pragma clang diagnostic push 1413 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 1414 | 1415 | [myObj performSelector:mySelector withObject:name]; 1416 | 1417 | #pragma clang diagnostic pop 1418 | ``` 1419 | 1420 | Note how we disable the -Warc-performSelector-leaks check by pushing and popping the change around our code. This assures us we don't disable it globally, which would be a huge mistake. 1421 | 1422 | The entire list of options that can be enable or disabled can be found at [The Clang User's Manual](http://clang.llvm.org/docs/UsersManual.html) to learn about all of them. 1423 | 1424 | Suppressing warnings for unused variables 1425 | 1426 | It's useful to be told that a variable you've defined is going unused. In most cases, you want to remove these references to improve performance (however slightly), but sometimes you want to keep them. Why? Perhaps they have a future usage or the functionality is only temporarily removed. Either way, a smarter way to suppress the warning without brutally commenting out the relevant lines, is to use the `#pragma unused()`: 1427 | 1428 | ```objective-c 1429 | - (NSUInteger)giveMeFive { 1430 | NSString *foo; 1431 | #pragma unused (foo) 1432 | 1433 | return 5; 1434 | } 1435 | ``` 1436 | 1437 | Now you can keep your code in place without the compiler complaining about it. And yes, that pragma needs to go below the offending code. 1438 | 1439 | ## Explicit warnings and errors 1440 | 1441 | The compiler is a robot: it will mark what's wrong with your code using a set of rules that've been defined by Clang. But, every so often you're smarter than it. Often, you might find some offending code that you know will lead to problems but, for whatever reason, can't fix yourself at the moment. You can explicitly signal errors like this: 1442 | 1443 | ```objective-c 1444 | - (NSInteger)divide:(NSInteger)dividend by:(NSInteger)divisor { 1445 | #error Whoa, buddy, you need to check for zero here! 1446 | return (dividend / divisor); 1447 | } 1448 | ``` 1449 | 1450 | You can signal warnings similarly: 1451 | 1452 | ```objective-c 1453 | - (float)divide:(float)dividend by:(float)divisor { 1454 | #warning Dude, don't compare floating point numbers like this! 1455 | if (divisor != 0.0) { 1456 | return (dividend / divisor); 1457 | } 1458 | else { 1459 | return NAN; 1460 | } 1461 | } 1462 | ``` 1463 | 1464 | ## Docstrings 1465 | 1466 | All non-trivial methods, interfaces, categories, and protocol declarations should have accompanying comments describing their purpose and how they fit into the larger picture. For more examples, see the Google Style Guide around [File and Declaration Comments](http://google-styleguide.googlecode.com/svn/trunk/objcguide.xml#File_Comments). 1467 | 1468 | To summarize: There are two types of docstrings, long-form and short-form. 1469 | 1470 | A short-form docstring fits entirely on one line, including the comment slashes. It is used for simple functions, especially (though by no means exclusively) ones that are not part of a public API: 1471 | 1472 | ``` 1473 | // Return a user-readable form of a Frobnozz, html-escaped. 1474 | ``` 1475 | 1476 | Note that the text is specified as an action ("return") rather than a description ("returns"). 1477 | 1478 | If the description spills past one line, you should move to the long-form docstring: a summary line (one physical line) preceded by an opening block comment with two asterisks on a line of its own (/\*\*, terminated by a period, question mark, or exclamation point, followed by a blank line, followed by the rest of the doc string starting at the same cursor position as the first quote of the first line, ending with an end-block comment (\*/) on a line by itself. 1479 | 1480 | ``` 1481 | /** 1482 | This comment serves to demonstrate the format of a docstring. 1483 | 1484 | Note that the summary line is always at most one line long, and 1485 | after the opening block comment, and each line of text is preceded 1486 | by a single space. 1487 | */ 1488 | ``` 1489 | 1490 | A function must have a docstring unless it meets all of the following criteria: 1491 | 1492 | * not externally visible 1493 | * very short 1494 | * obvious 1495 | 1496 | The docstring should describe the function's calling syntax and its semantics, not its implementation. 1497 | 1498 | ## Comments 1499 | 1500 | When they are needed, comments should be used to explain why a particular piece of code does something. Any comments that are used must be kept up-to-date or deleted. 1501 | 1502 | Block comments should generally be avoided, as code should be as self-documenting as possible, with only the need for intermittent, few-line explanations. *Exception: This does not apply to those comments used to generate documentation.* 1503 | 1504 | ### Header Documentation 1505 | 1506 | The documentation of class should be done using the Doxygen/AppleDoc syntax only in the .h files when possible. Documentation should be provided for methods and properties. 1507 | 1508 | **For example:** 1509 | 1510 | ```objective-c 1511 | /** 1512 | * Designated initializer. 1513 | * 1514 | * @param store The store for CRUD operations. 1515 | * @param searchService The search service used to query the store. 1516 | * 1517 | * @return A ZOCCRUDOperationsStore object. 1518 | */ 1519 | - (instancetype)initWithOperationsStore:(id)store 1520 | searchService:(id)searchService; 1521 | ``` 1522 | 1523 | 1524 | 1525 | 1526 | # Inter-Object Communication 1527 | 1528 | Every non-trivial software is based on objects that need to communicate with each other to meet complex goals. This chapter is about design concepts going first into deep explanations on how the armory will achieve great architectures works and how it should be used. 1529 | 1530 | ## Blocks 1531 | 1532 | Blocks are the Objective-C version of well-knows constructs called lambdas or closures available in other languages for many years now. 1533 | 1534 | They are a great way to design asynchronous APIs as so: 1535 | 1536 | ```objective-c 1537 | - (void)downloadObjectsAtPath:(NSString *)path 1538 | completion:(void(^)(NSArray *objects, NSError *error))completion; 1539 | ``` 1540 | 1541 | When designing something like the above, try to declare functions and methods to take no more than one block and always make the blocks be the last arguments. It is a good approach to try to combine data and error in one block instead of two separate blocks (usually one for the success and one for the failure). 1542 | 1543 | You should do this because: 1544 | 1545 | * Usually there are part of code that are shared between them (i.e. dismiss a progress or an activity indicator); 1546 | * Apple is doing the same, and it is always good to be consistent with the platform; 1547 | * Since Blocks are typically multiple lines of code, having the block be other than the last argument would break up the call site[1]; 1548 | * Taking more than one block as arguments would make the call site potentially unwieldy in length. It also introduces complexity[1]. 1549 | 1550 | Consider the above method, the signature of the completion block is very common: the first parameter regards the data the caller is interested in, the second parameter is the error encountered. The contract should be therefore as follow: 1551 | 1552 | * if `objects` is not nil, then `error` must be nil 1553 | * if `objects` is nil, then `error` must not be nil 1554 | 1555 | as the caller of this method is first interested in the actual data, an implementation like so is preferred: 1556 | 1557 | ```objective-c 1558 | - (void)downloadObjectsAtPath:(NSString *)path 1559 | completion:(void(^)(NSArray *objects, NSError *error))completion { 1560 | if (objects) { 1561 | // do something with the data 1562 | } 1563 | else { 1564 | // some error occurred, 'error' variable should not be nil by contract 1565 | } 1566 | } 1567 | ``` 1568 | 1569 | Moreover, as for synchronous methods, some of Apple's APIs write garbage values to the error parameter (if non-NULL) in successful cases, so checking the error can cause false positives. 1570 | 1571 | 1572 | ### Under the Hood 1573 | 1574 | Some key points: 1575 | 1576 | * Blocks are created on the stack 1577 | * Blocks can be copied to the heap 1578 | * Blocks have their own private const copies of stack variables (and pointers) 1579 | * Mutable stack variables and pointers must be declared with the __block keyword 1580 | 1581 | If blocks aren't kept around anywhere, will remain on the stack and will go away when their stack frame returns. While on the stack, a block has no effect on the storage or lifetime of anything it accesses. If blocks need to exist after the stack frame returns, they can be copied to the heap and this action is an explicit operation. This way, a block will gain reference-counting as all objects in Cocoa. When they are copied, they take their captured scope with them, retaining any objects they refer. If a block references a stack variable or pointer, then when the block is initialized it is given its own copy of that variable declared const, so assignments won't work. When a block is copied, the `__block` stack variables it reference are copied to the heap and after the copy operation both block on the stack and brand new block on the heap refer to the variables on the heap. 1582 | 1583 | LLDB shows that a block is a nice piece of things. 1584 | 1585 | ![](./images/blocks_debugger.png) 1586 | 1587 | The most important thing to note is that `__block` variables and pointers are treated inside the block as structs that obviously handle the reference to the real value/object. 1588 | 1589 | Blocks are first-class citizens in the Objective-C runtime: they have an `isa` pointer which defines a class through which the Objective-C runtime can access methods and storage. In a non-ARC environment you can mess things up for sure, and cause crashes due to dangling pointers. `__block` applies only when using the variables in the block, it simply says to the block: 1590 | 1591 | > Hey, this pointer or primitive type relies on the stack with its own address. Please refer to this little friend with a new variable on the stack. I mean… refer to the object with double dereference, and don't retain it. 1592 | Thank you, sir. 1593 | 1594 | If at some time after the declaration but before the invocation of the block, the object has been released and deallocated, the execution of the block will cause a crash. `__block` variables are not retained within the block. In the deep end… it's all about pointers, references, dereferences and retain count stuff. 1595 | 1596 | [blocks_uth1]: http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/00_Introduction.html 1597 | [blocks_uth2]: http://ios-blog.co.uk/tutorials/programming-with-blocks-an-overview/ 1598 | 1599 | 1600 | ### Retain cycles on self 1601 | 1602 | It's important not to get into retain cycles when using blocks and asynchronous dispatches. Always set a `weak` reference to any variable that could cause a retain cycle. Moreover, it is a good practice to nil the properties holding the blocks (i.e. `self.completionBlock = nil`) this will break potential retain cycle introduced by the block capturing the scope. 1603 | 1604 | 1605 | **Example:** 1606 | 1607 | ```objective-c 1608 | __weak __typeof(self) weakSelf = self; 1609 | [self executeBlock:^(NSData *data, NSError *error) { 1610 | [weakSelf doSomethingWithData:data]; 1611 | }]; 1612 | ``` 1613 | 1614 | **Not:** 1615 | 1616 | ```objective-c 1617 | [self executeBlock:^(NSData *data, NSError *error) { 1618 | [self doSomethingWithData:data]; 1619 | }]; 1620 | ``` 1621 | 1622 | **Example with multiple statements:** 1623 | 1624 | ```objective-c 1625 | __weak __typeof(self)weakSelf = self; 1626 | [self executeBlock:^(NSData *data, NSError *error) { 1627 | __strong __typeof(weakSelf) strongSelf = weakSelf; 1628 | if (strongSelf) { 1629 | [strongSelf doSomethingWithData:data]; 1630 | [strongSelf doSomethingWithData:data]; 1631 | } 1632 | }]; 1633 | ``` 1634 | 1635 | **Not:** 1636 | 1637 | ```objective-c 1638 | __weak __typeof(self)weakSelf = self; 1639 | [self executeBlock:^(NSData *data, NSError *error) { 1640 | [weakSelf doSomethingWithData:data]; 1641 | [weakSelf doSomethingWithData:data]; 1642 | }]; 1643 | ``` 1644 | 1645 | You should add these two lines as snippets to Xcode and always use them exactly like this: 1646 | 1647 | ```objective-c 1648 | __weak __typeof(self)weakSelf = self; 1649 | __strong __typeof(weakSelf)strongSelf = weakSelf; 1650 | ``` 1651 | 1652 | Here we dig further about the subtle things to consider about the `__weak` and the `__strong` qualifiers for self inside blocks. To summarize, we can refer to self in three different ways inside a block: 1653 | 1654 | 1. using the keyword `self` directly inside the block 1655 | 2. declaring a `__weak` reference to self outside the block and referring to the object via this weak reference inside the block 1656 | 3. declaring a `__weak` reference to self outside the block and creating a `__strong` reference to self using the weak reference inside the block 1657 | 1658 | **Case 1: using the keyword `self` inside a block** 1659 | 1660 | If we use directly the keyword `self` inside a block, the object is retained at block declaration time within the block (actually when the block is [copied][blocks_caveat13] but for sake of simplicity we can forget about it) . A const reference to self has its place inside the block and this affects the reference counting of the object. If the block is used by other classes and/or passed around we may want to retain self as well as all the other objects used by the block since they are *needed* for the execution of the block. 1661 | 1662 | ```objective-c 1663 | dispatch_block_t completionBlock = ^{ 1664 | NSLog(@"%@", self); 1665 | } 1666 | 1667 | MyViewController *myController = [[MyViewController alloc] init...]; 1668 | [self presentViewController:myController 1669 | animated:YES 1670 | completion:completionHandler]; 1671 | ``` 1672 | 1673 | No big deal. But... what if the block is retained by self in a property (as the following example) and therefore the object (self) retains the block? 1674 | 1675 | ```objective-c 1676 | self.completionHandler = ^{ 1677 | NSLog(@"%@", self); 1678 | } 1679 | 1680 | MyViewController *myController = [[MyViewController alloc] init...]; 1681 | [self presentViewController:myController 1682 | animated:YES 1683 | completion:self.completionHandler]; 1684 | ``` 1685 | 1686 | This is what is well known as a retain cycle and retain cycles usually should be avoided. The warning we receive from CLANG is: 1687 | 1688 | ```objective-c 1689 | Capturing 'self' strongly in this block is likely to lead to a retain cycle 1690 | ``` 1691 | 1692 | Here comes in the `__weak` qualifier. 1693 | 1694 | **Case 2: declaring a `__weak` reference to self outside the block and use it inside the block** 1695 | 1696 | Declaring a `__weak` reference to self outside the block and referring to it via this weak reference inside the block avoids retain cycles. This is what we usually want to do if the block is already retained by self in a property. 1697 | 1698 | ```objective-c 1699 | __weak typeof(self) weakSelf = self; 1700 | self.completionHandler = ^{ 1701 | NSLog(@"%@", weakSelf); 1702 | }; 1703 | 1704 | MyViewController *myController = [[MyViewController alloc] init...]; 1705 | [self presentViewController:myController 1706 | animated:YES 1707 | completion:self.completionHandler]; 1708 | ``` 1709 | 1710 | In this example the block does not retain the object and the object retains the block in a property. Cool. We are sure that we can refer to self safely, at worst, it is nilled out by someone. The question is: how is it possible for self to be "destroyed" (deallocated) within the scope of a block? 1711 | 1712 | Consider the case of a block being copied from an object to another (let's say myController) as a result of the assignment of a property. The former object is then released before the copied block has had a chance to execute. 1713 | 1714 | The next step is interesting. 1715 | 1716 | **Case 3: declaring a `__weak` reference to self outside the block and use a `__strong` reference inside the block** 1717 | 1718 | You may think, at first, this is a trick to use self inside the block avoiding the retain cycle warning. This is not the case. The strong reference to self is created at *block execution time* while using self in the block is evaluated at *block declaration time*, thus retaining the object. 1719 | 1720 | [Apple documentation][blocks_caveat1] reports that "For non-trivial cycles, however, you should use" this approach: 1721 | 1722 | ```objective-c 1723 | MyViewController *myController = [[MyViewController alloc] init...]; 1724 | // ... 1725 | MyViewController * __weak weakMyController = myController; 1726 | myController.completionHandler = ^(NSInteger result) { 1727 | MyViewController *strongMyController = weakMyController; 1728 | if (strongMyController) { 1729 | // ... 1730 | [strongMyController dismissViewControllerAnimated:YES completion:nil]; 1731 | // ... 1732 | } 1733 | else { 1734 | // Probably nothing... 1735 | } 1736 | }; 1737 | ``` 1738 | 1739 | First of all, this example looks wrong to me. How can self be deallocated and be nilled out if the block itself is retained in the `completionHandler` property? The `completionHandler` property can be declared as `assign` or `unsafe_unretained` to allow the object to be deallocated after the block is passed around. 1740 | I can't see the reason for doing that. If other objects need the object (self), the block that is passed around should retain the object and therefore the block should not be assigned to a property. No `__weak`/`__strong` usage should be involved in this case. 1741 | 1742 | Anyway, in other cases it is possible for weakSelf to become nil just like the second case explained (declaring a weak reference outside the block and use it inside). 1743 | 1744 | Moreover, what is the meaning of "trivial block" for Apple? It is my understanding that a trivial block is a block that is not passed around, it's used within a well defined and controlled scope and therefore the usage of the weak qualifier is just to avoid a retain cycle. 1745 | 1746 | As [a][blocks_caveat2] [lot][blocks_caveat3] [of][blocks_caveat4] [online][blocks_caveat5] [references][blocks_caveat6], books ([Effective Objective-C 2.0][blocks_caveat14] by [Matt Galloway][blocks_caveat16] and [Pro Multithreading and Memory Management for iOS and OS X][blocks_caveat15] by Kazuki Sakamoto & Tomohiko Furumoto) discuss this edge case, the topic is not well understood yet by the majority of the developers. 1747 | 1748 | The real benefit of using the strong reference inside of a block is to be robust to preemption. Going again through the above 3 cases, during the execution of a block: 1749 | 1750 | **Case 1: using the keyword `self` inside a block** 1751 | 1752 | If the block is retained by a property, a retain cycle is created between self and the block and both objects can't be destroyed anymore. If the block is passed around and copied by others, self is retained for each copy. 1753 | 1754 | **Case 2: declaring a `__weak` reference to self outside the block and use it inside the block** 1755 | 1756 | There is no retain cycle and no matter if the block is retained or not by a property. If the block is passed around and copied by others, when executed, weakSelf can have been turned nil. 1757 | The execution of the block can be preempted and different subsequent evaluations of the weakSelf pointer can lead to different values (i.e. weakSelf can become nil at a certain evaluation). 1758 | 1759 | ```objective-c 1760 | __weak typeof(self) weakSelf = self; 1761 | dispatch_block_t block = ^{ 1762 | [weakSelf doSomething]; // weakSelf != nil 1763 | // preemption, weakSelf turned nil 1764 | [weakSelf doSomethingElse]; // weakSelf == nil 1765 | }; 1766 | ``` 1767 | 1768 | **Case 3: declaring a `__weak` reference to self outside the block and use a `__strong` reference inside the block** 1769 | 1770 | There is no retain cycle and, again, no matter if the block is retained or not by a property. If the block is passed around and copied by others, when executed, `weakSelf` can have been turned nil. When the strong reference is assigned and it is not nil, we are sure that the object is retained for the entire execution of the block if preemption occurs and therefore subsequent evaluations of strongSelf will be consistent and will lead to the same value since the object is now retained. If strongSelf evaluates to nil usually the execution is returned since the block cannot execute properly. 1771 | 1772 | ```objective-c 1773 | __weak typeof(self) weakSelf = self; 1774 | myObj.myBlock = ^{ 1775 | __strong typeof(self) strongSelf = weakSelf; 1776 | if (strongSelf) { 1777 | [strongSelf doSomething]; // strongSelf != nil 1778 | // preemption, strongSelf still not nil 1779 | [strongSelf doSomethingElse]; // strongSelf != nil 1780 | } 1781 | else { 1782 | // Probably nothing... 1783 | return; 1784 | } 1785 | }; 1786 | ``` 1787 | 1788 | In an ARC-based environment, the compiler itself alerts us with an error if trying to access an instance variable using the `->` notation. The error is very clear: 1789 | 1790 | ```objective-c 1791 | Dereferencing a __weak pointer is not allowed due to possible null value caused by race condition, assign it to a strong variable first. 1792 | ``` 1793 | 1794 | It can be shown with the following code: 1795 | 1796 | ```objective-c 1797 | __weak typeof(self) weakSelf = self; 1798 | myObj.myBlock = ^{ 1799 | id localVal = weakSelf->someIVar; 1800 | }; 1801 | ``` 1802 | 1803 | In the very end: 1804 | 1805 | * **Case 1**: should be used only when the block is not assigned to a property, otherwise it will lead to a retain cycle. 1806 | 1807 | * **Case 2**: should be used when the block is assigned to a property. 1808 | 1809 | * **Case 3**: it is related to concurrent executions. When asynchronous services are involved, the blocks that are passed to them can be executed at a later period and there is no certainty about the existence of the self object. 1810 | 1811 | [blocks_caveat1]: http://developer.apple.com/library/mac/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html 1812 | [blocks_caveat2]: http://dhoerl.wordpress.com/2013/04/23/i-finally-figured-out-weakself-and-strongself/ 1813 | [blocks_caveat3]: http://blog.random-ideas.net/?p=160 1814 | [blocks_caveat4]: http://stackoverflow.com/questions/7904568/disappearing-reference-to-self-in-a-block-under-arc 1815 | 1816 | [blocks_caveat5]: http://stackoverflow.com/questions/12218767/objective-c-blocks-and-memory-management 1817 | [blocks_caveat6]: https://github.com/AFNetworking/AFNetworking/issues/807 1818 | [blocks_caveat10]: https://twitter.com/pedrogomes 1819 | [blocks_caveat11]: https://twitter.com/dmakarenko 1820 | [blocks_caveat12]: https://ef.com 1821 | [blocks_caveat13]: https://developer.apple.com/library/ios/documentation/cocoa/conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW4 1822 | [blocks_caveat14]: http://www.effectiveobjectivec.com/ 1823 | [blocks_caveat15]: http://www.amazon.it/Pro-Multithreading-Memory-Management-Ios/dp/1430241160 1824 | [blocks_caveat16]: https://twitter.com/mattjgalloway 1825 | 1826 | ## Delegate and DataSource 1827 | 1828 | Delegation is a widespread pattern throughout Apple's frameworks and it is one of the most important patterns in the Gang of Four's book "Design Patterns". The delegation pattern is unidirectional, the message sender (the delegant) needs to know about the recipient (the delegate), but not the other way around. The coupling between the objects is loosen the sender only knows that its delegate conforms to a specific protocol. 1829 | 1830 | In its pure form, delegation is about providing callbacks to the delegate, which means that the delegate implements a set of methods with void return type. 1831 | 1832 | Unfortunately this has not been respected over years by the APIs from Apple and therefore developers acted imitating this misleading approach. A classic example is the [UITableViewDelegate](https://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewDelegate_Protocol/Reference/Reference.html) protocol. 1833 | 1834 | While some methods have void return type and look like callbacks: 1835 | 1836 | ```objective-c 1837 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath; 1838 | - (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath; 1839 | ``` 1840 | 1841 | others are definitely not: 1842 | 1843 | ```objective-c 1844 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath; 1845 | - (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender; 1846 | ``` 1847 | 1848 | When the delegant asks for some kind of information to the delegate object, the direction implied is from the delegate to the delegant and to the other way around anymore. This is conceptually different and a new name should be use to describe the pattern: DataSource. 1849 | 1850 | One could argue that Apple has a [UITableViewDataSouce](https://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewDataSource_Protocol/Reference/Reference.html) protocol for it (forced under the name of the delegate pattern) but in reality it is used for methods providing information about how the real data should be presented. 1851 | 1852 | ```objective-c 1853 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; 1854 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; 1855 | ``` 1856 | 1857 | Moreover, in the above 2 methods Apple mixed the presentation layer with the data layer which is clearly ugly and in the end very few developers felt bad about it over times and even here we'll call delegate methods both methods with void return type and not void for simplicity. 1858 | 1859 | To split the concepts, the following approach should be used: 1860 | 1861 | * delegate pattern: when the delegant needs to notify the delegate about event occurred 1862 | * datasource pattern: when the delegant needs to fetch information from the datasource object 1863 | 1864 | Here is a concrete example: 1865 | 1866 | ```objective-c 1867 | @class ZOCSignUpViewController; 1868 | 1869 | @protocol ZOCSignUpViewControllerDelegate 1870 | - (void)signUpViewControllerDidPressSignUpButton:(ZOCSignUpViewController *)controller; 1871 | @end 1872 | 1873 | @protocol ZOCSignUpViewControllerDataSource 1874 | - (ZOCUserCredentials *)credentialsForSignUpViewController:(ZOCSignUpViewController *)controller; 1875 | @end 1876 | 1877 | @protocol ZOCSignUpViewControllerDataSource 1878 | 1879 | @interface ZOCSignUpViewController : UIViewController 1880 | 1881 | @property (nonatomic, weak) id delegate; 1882 | @property (nonatomic, weak) id dataSource; 1883 | 1884 | @end 1885 | 1886 | ``` 1887 | 1888 | Delegate methods should be always have the caller as first parameter as in the above example otherwise delegate objects could not be able to distinguish between different instances of delegants. In other words, if the caller is not passed to the delegate object, there would be no way for any delegate to deal with 2 delegant object. So, following is close to blasphemy: 1889 | 1890 | ```objective-c 1891 | - (void)calculatorDidCalculateValue:(CGFloat)value; 1892 | ``` 1893 | 1894 | By default, methods in protocols are required to be implemented by delegate objects. It is possible to mark some of them as optional and to be explicit about the required method using the `@required` and `@optional` keywords as so: 1895 | 1896 | ```objective-c 1897 | @protocol ZOCSignUpViewControllerDelegate 1898 | @required 1899 | - (void)signUpViewController:(ZOCSignUpViewController *)controller didProvideSignUpInfo:(NSDictionary *); 1900 | @optional 1901 | - (void)signUpViewControllerDidPressSignUpButton:(ZOCSignUpViewController *)controller; 1902 | @end 1903 | ``` 1904 | 1905 | For optional methods, the delegant must check if the delegate actually implements a specific method before sending the message to it (otherwise a crash would occur) as so: 1906 | 1907 | ```objective-c 1908 | if ([self.delegate respondsToSelector:@selector(signUpViewControllerDidPressSignUpButton:)]) { 1909 | [self.delegate signUpViewControllerDidPressSignUpButton:self]; 1910 | } 1911 | ``` 1912 | 1913 | ### Inheritance 1914 | 1915 | Sometimes you may need to override delegate methods. Consider the case of having two UIViewController subclasses: UIViewControllerA and UIViewControllerB, with the following class hierarchy. 1916 | 1917 | `UIViewControllerB < UIViewControllerA < UIViewController` 1918 | 1919 | `UIViewControllerA` conforms to `UITableViewDelegate` and implements `- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath`. 1920 | 1921 | You want to provide a different implementation for the method above in `UIViewControllerB`. An implementation like the following will work: 1922 | 1923 | ```objective-c 1924 | 1925 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 1926 | CGFloat retVal = 0; 1927 | if ([super respondsToSelector:@selector(tableView:heightForRowAtIndexPath:)]) { 1928 | retVal = [super tableView:self.tableView heightForRowAtIndexPath:indexPath]; 1929 | } 1930 | return retVal + 10.0f; 1931 | } 1932 | 1933 | ``` 1934 | 1935 | But what if the given method was not implemented in the superclass (`UIViewControllerA`)? 1936 | 1937 | The invocation 1938 | 1939 | ```objective-c 1940 | [super respondsToSelector:@selector(tableView:heightForRowAtIndexPath:)] 1941 | ``` 1942 | 1943 | will use the NSObject's implementation that will lookup, under the hood, in the context of `self` and clearly self implements the method but the app will crash at the next line with the following error: 1944 | 1945 | ``` 1946 | *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIViewControllerB tableView:heightForRowAtIndexPath:]: unrecognized selector sent to instance 0x8d82820' 1947 | ``` 1948 | 1949 | In this case we need to ask if instances of a specific class can respond to a given selector. The following code would do the trick: 1950 | 1951 | ```objective-c 1952 | 1953 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 1954 | CGFloat retVal = 0; 1955 | if ([[UIViewControllerA class] instancesRespondToSelector:@selector(tableView:heightForRowAtIndexPath:)]) { 1956 | retVal = [super tableView:self.tableView heightForRowAtIndexPath:indexPath]; 1957 | } 1958 | return retVal + 10.0f; 1959 | } 1960 | 1961 | ``` 1962 | 1963 | As code as the one above is ugly, often it'd be better to design the architecture in a way that delegate methods don't need to be overridden. 1964 | 1965 | ### Multiple Delegation 1966 | 1967 | Multiple delegation is a very fundamental concept that, unfortunately, the majority of developers are hardly familiar with and too often NSNotifications are used instead. As you may have noticed, delegation and datasource are inter-object communication pattern involving only 2 objects: 1 delegant and 1 delegate. 1968 | 1969 | DataSource pattern is forced to be 1 to 1 as the information the sender asks for can be provided by one and only one object. Things are different for the delegate pattern and it would be perfectly reasonable to have many delegate objects waiting for the callbacks. 1970 | 1971 | There are cases where at least 2 objects are interested in receiving the callbacks from a particular delegant and the latter wants to know all of its delegates. This approach maps better a distributed system and more generically how complex flows of information usually go in wide software. 1972 | 1973 | Multiple delegation can be achieved in many ways and the reader is dared to find a proper personal implementation. A very neat implementation of multiple delegation using the forward mechanism is given by Luca Bernardi in his [LBDelegateMatrioska](https://github.com/lukabernardi/LBDelegateMatrioska). 1974 | 1975 | A basic implementation is given here to unfold the concept. Even if in Cocoa there are ways to store weak references in a data structure to avoid retain cycles, here we use a class to hold a weak reference to the delegate object as single delegation does. 1976 | 1977 | ```objective-c 1978 | @interface ZOCWeakObject : NSObject 1979 | 1980 | @property (nonatomic, weak, readonly) id object; 1981 | 1982 | + (instancetype)weakObjectWithObject:(id)object; 1983 | - (instancetype)initWithObject:(id)object; 1984 | 1985 | @end 1986 | ``` 1987 | 1988 | ```objective-c 1989 | @interface ZOCWeakObject () 1990 | @property (nonatomic, weak) id object; 1991 | @end 1992 | 1993 | @implementation ZOCWeakObject 1994 | 1995 | + (instancetype)weakObjectWithObject:(id)object { 1996 | return [[[self class] alloc] initWithObject:object]; 1997 | } 1998 | 1999 | - (instancetype)initWithObject:(id)object { 2000 | if ((self = [super init])) { 2001 | _object = object; 2002 | } 2003 | return self; 2004 | } 2005 | 2006 | - (BOOL)isEqual:(id)object { 2007 | if (self == object) { 2008 | return YES; 2009 | } 2010 | 2011 | if (![object isKindOfClass:[object class]]) { 2012 | return NO; 2013 | } 2014 | 2015 | return [self isEqualToWeakObject:(ZOCWeakObject *)object]; 2016 | } 2017 | 2018 | - (BOOL)isEqualToWeakObject:(ZOCWeakObject *)object { 2019 | if (!object) { 2020 | return NO; 2021 | } 2022 | 2023 | BOOL objectsMatch = [self.object isEqual:object.object]; 2024 | return objectsMatch; 2025 | } 2026 | 2027 | - (NSUInteger)hash { 2028 | return [self.object hash]; 2029 | } 2030 | 2031 | @end 2032 | ``` 2033 | 2034 | A simple component using weak objects to achieve multiple delegation: 2035 | 2036 | ```objective-c 2037 | @protocol ZOCServiceDelegate 2038 | @optional 2039 | - (void)generalService:(ZOCGeneralService *)service didRetrieveEntries:(NSArray *)entries; 2040 | @end 2041 | 2042 | @interface ZOCGeneralService : NSObject 2043 | - (void)registerDelegate:(id)delegate; 2044 | - (void)deregisterDelegate:(id)delegate; 2045 | @end 2046 | 2047 | @interface ZOCGeneralService () 2048 | @property (nonatomic, strong) NSMutableSet *delegates; 2049 | @end 2050 | ``` 2051 | 2052 | ```objective-c 2053 | @implementation ZOCGeneralService 2054 | - (void)registerDelegate:(id)delegate { 2055 | if ([delegate conformsToProtocol:@protocol(ZOCServiceDelegate)]) { 2056 | [self.delegates addObject:[[ZOCWeakObject alloc] initWithObject:delegate]]; 2057 | } 2058 | } 2059 | 2060 | - (void)deregisterDelegate:(id)delegate { 2061 | if ([delegate conformsToProtocol:@protocol(ZOCServiceDelegate)]) { 2062 | [self.delegates removeObject:[[ZOCWeakObject alloc] initWithObject:delegate]]; 2063 | } 2064 | } 2065 | 2066 | - (void)p_notifyDelegates { 2067 | ... 2068 | for (ZOCWeakObject *object in self.delegates) { 2069 | if (object.object) { 2070 | if ([object.object respondsToSelector:@selector(generalService:didRetrieveEntries:)]) { 2071 | [object.object generalService:self didRetrieveEntries:entries]; 2072 | } 2073 | } 2074 | } 2075 | } 2076 | 2077 | @end 2078 | ``` 2079 | 2080 | With the `registerDelegate:` and `deregisterDelegate:` methods, it is easy to connect/disconnect cables between components: if at some point in time a delegate object is not interested in receiving the callbacks from a delegant, it has the chance to just 'unsubscribe'. 2081 | This can be useful when there are different views waiting for some callback to update the shown info: if a view is temporarily hidden (but still alive) it could make sense for it to just unsubscribe to those callbacks. 2082 | 2083 | 2084 | # Aspect Oriented Programming 2085 | 2086 | Aspect Oriented Programming (AOP) is something not well-known in the Objective-C community but it should be as the runtime is so powerful that AOP should be one of the first things that comes to the mind. Unfortunately, as there is no standard de facto library, nothing comes ready to use out-of-the-box from Apple and the topic is far from being trivial, developers still don't think of it in nowadays. 2087 | 2088 | Quoting the [Aspect Oriented Programming](http://en.wikipedia.org/wiki/Aspect-oriented_programming) Wikipedia page: 2089 | 2090 | > An aspect can alter the behavior of the base code (the non-aspect part of a program) by applying advice (additional behavior) at various join points (points in a program) specified in a quantification or query called a pointcut (that detects whether a given join point matches). 2091 | 2092 | 2093 | In the world of Objective-C this means using the runtime features to add *aspects* to specific methods. The additional behaviors given by the aspect can be either: 2094 | 2095 | * add code to be performed before a specific method call on a specific class 2096 | * add code to be performed after a specific method call on a specific class 2097 | * add code to be performed instead of the original implementation of a specific method call on a specific class 2098 | 2099 | There are many ways to achieve this we are not digging into deep here, basically all of them leverage the power of the runtime. 2100 | [Peter Steinberger](https://twitter.com/steipete) wrote a library, [Aspects](https://github.com/steipete/Aspects) that fits the AOP approach perfectly. We found it reliable and well-designed and we are going to use it here for sake of simplicity. 2101 | As said for all the AOP-ish libraries, the library does some cool magic with the runtime, replacing and adding methods (further tricks over the method swizzling technique). 2102 | The API of Aspect are interesting and powerful: 2103 | 2104 | ```objective-c 2105 | + (id)aspect_hookSelector:(SEL)selector 2106 | withOptions:(AspectOptions)options 2107 | usingBlock:(id)block 2108 | error:(NSError **)error; 2109 | - (id)aspect_hookSelector:(SEL)selector 2110 | withOptions:(AspectOptions)options 2111 | usingBlock:(id)block 2112 | error:(NSError **)error; 2113 | ``` 2114 | 2115 | For instance, the following code will perform the block parameter after the execution of the method `myMethod:` (instance or class method that be) on the class `MyClass`. 2116 | 2117 | ```objective-c 2118 | [MyClass aspect_hookSelector:@selector(myMethod:) 2119 | withOptions:AspectPositionAfter 2120 | usingBlock:^(id aspectInfo) { 2121 | ... 2122 | } 2123 | error:nil]; 2124 | ``` 2125 | 2126 | In other words: the code provided in the block parameter will always be executed after each call of the `@selector` parameter on any object of type `MyClass` (or on the class itself if the method is a class method). 2127 | 2128 | We added an aspect on `MyClass` for the method `myMethod:`. 2129 | 2130 | Usually AOP is used to implement cross cutting concern. Perfect example to leverage are analytics or logging. 2131 | 2132 | In the following we will present the use of AOP for analytics. Analytics are a popular "feature" to include in iOS projects, with a huge variety of choices ranging from Google Analytics, Flurry, MixPanel, etc. 2133 | Most of them have tutorials describing how to track specific views and events including a few lines of code inside each class. 2134 | 2135 | On Ray Wenderlich's blog there is a long [article](http://www.raywenderlich.com/53459/google-analytics-ios) with some sample code to include in your view controller in order to track an event with [Google Analytics](https://developers.google.com/analytics/devguides/collection/ios/): 2136 | 2137 | ```objective-c 2138 | - (void)logButtonPress:(UIButton *)button { 2139 | id tracker = [[GAI sharedInstance] defaultTracker]; 2140 | [tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"UX" 2141 | action:@"touch" 2142 | label:[button.titleLabel text] 2143 | value:nil] build]]; 2144 | } 2145 | ``` 2146 | 2147 | The code above sends an event with context information whenever a button is tapped. Things get worse when you want to track a screen view: 2148 | 2149 | ```objective-c 2150 | - (void)viewDidAppear:(BOOL)animated { 2151 | [super viewDidAppear:animated]; 2152 | 2153 | id tracker = [[GAI sharedInstance] defaultTracker]; 2154 | [tracker set:kGAIScreenName value:@"Stopwatch"]; 2155 | [tracker send:[[GAIDictionaryBuilder createAppView] build]]; 2156 | } 2157 | ``` 2158 | 2159 | This should look like a code smell to the most of the experienced iOS developers. We are actually making the view controller dirty adding lines of code that should not belong there as it's not responsibility of the view controller to track events. You could argue that you usually have a specific object responsible for analytics tracking and you inject this object inside the view controller but the problem is still there and no matter where you hide the tracking logic: you eventually end up inserting some lines of code in the `viewDidAppear:`. 2160 | 2161 | We can use AOP to track screen views on specific `viewDidAppear:` methods, and moreover, we could use the same approach to add event tracking in other methods we are interested in, for instance when the user taps on a button (i.e. trivially calling the corresponding IBAction). 2162 | 2163 | This approach is clean and unobtrusive: 2164 | 2165 | * the view controllers will not get dirty with code that does not naturally belongs to them 2166 | * it becomes possible to specify a SPOC file (single point of customization) for all the aspects to add to our code 2167 | * the SPOC should be used to add the aspects at the very startup of the app 2168 | * if the SPOC file is malformed and at least one selector or class is not recognized, the app will crash at startup (which is cool for our purposes) 2169 | * the team in the company responsible for managing the analytics usually provides a document with the list of *things* to track; this document could then be easily mapped to a SPOC file 2170 | * as the logic for the tracking is now abstracted, it becomes possible to scale with a grater number of analytics providers 2171 | * for screen views it is enough to specify in the SPOC file the classes involved (the corresponding aspect will be added to the `viewDidAppear:` method), for events it is necessary to specify the selectors. To send both screen views and events, a tracking label and maybe extra meta data are needed to provide extra information (depending on the analytics provider). 2172 | 2173 | We may want a SPOC file similar to the following (also a .plist file would perfectly fit as well): 2174 | 2175 | ```objective-c 2176 | NSDictionary *analyticsConfiguration() { 2177 | return @{ 2178 | @"trackedScreens" : @[ 2179 | @{ 2180 | @"class" : @"ZOCMainViewController", 2181 | @"label" : @"Main screen" 2182 | } 2183 | ], 2184 | @"trackedEvents" : @[ 2185 | @{ 2186 | @"class" : @"ZOCMainViewController", 2187 | @"selector" : @"loginViewFetchedUserInfo:user:", 2188 | @"label" : @"Login with Facebook" 2189 | }, 2190 | @{ 2191 | @"class" : @"ZOCMainViewController", 2192 | @"selector" : @"loginViewShowingLoggedOutUser:", 2193 | @"label" : @"Logout with Facebook" 2194 | }, 2195 | @{ 2196 | @"class" : @"ZOCMainViewController", 2197 | @"selector" : @"loginView:handleError:", 2198 | @"label" : @"Login error with Facebook" 2199 | }, 2200 | @{ 2201 | @"class" : @"ZOCMainViewController", 2202 | @"selector" : @"shareButtonPressed:", 2203 | @"label" : @"Share button" 2204 | } 2205 | ] 2206 | }; 2207 | } 2208 | ``` 2209 | 2210 | The architecture proposed is hosted on GitHub on the [EF Education First](https://github.com/ef-ctx/JohnnyEnglish/blob/master/CTXUserActivityTrackingManager.m) profile. 2211 | 2212 | ```objective-c 2213 | - (void)setupWithConfiguration:(NSDictionary *)configuration { 2214 | // screen views tracking 2215 | for (NSDictionary *trackedScreen in configuration[@"trackedScreens"]) { 2216 | Class clazz = NSClassFromString(trackedScreen[@"class"]); 2217 | 2218 | [clazz aspect_hookSelector:@selector(viewDidAppear:) 2219 | withOptions:AspectPositionAfter 2220 | usingBlock:^(id aspectInfo) { 2221 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 2222 | NSString *viewName = trackedScreen[@"label"]; 2223 | [tracker trackScreenHitWithName:viewName]; 2224 | }); 2225 | }]; 2226 | 2227 | } 2228 | 2229 | // events tracking 2230 | for (NSDictionary *trackedEvents in configuration[@"trackedEvents"]) { 2231 | Class clazz = NSClassFromString(trackedEvents[@"class"]); 2232 | SEL selektor = NSSelectorFromString(trackedEvents[@"selector"]); 2233 | 2234 | [clazz aspect_hookSelector:selektor 2235 | withOptions:AspectPositionAfter 2236 | usingBlock:^(id aspectInfo) { 2237 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 2238 | UserActivityButtonPressedEvent *buttonPressEvent = [UserActivityButtonPressedEvent eventWithLabel:trackedEvents[@"label"]]; 2239 | [tracker trackEvent:buttonPressEvent]; 2240 | }); 2241 | }]; 2242 | 2243 | } 2244 | } 2245 | ``` 2246 | 2247 | 2248 | 2249 | # References 2250 | 2251 | Here are some of the documents from Apple that informed the style guide: 2252 | 2253 | * [The Objective-C Programming Language](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Introduction/introObjectiveC.html) 2254 | * [Cocoa Fundamentals Guide](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaFundamentals/Introduction/Introduction.html) 2255 | * [Coding Guidelines for Cocoa](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html) 2256 | * [iOS App Programming Guide](http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/Introduction/Introduction.html) 2257 | * [Apple Objective-C conventions](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/Conventions/Conventions.html): code conventions from Apple; 2258 | 2259 | Others: 2260 | 2261 | * [Objective-Clean](http://objclean.com): an attempt to write a standard for writing Objective-C code with Xcode integration; 2262 | * [Uncrustify](http://uncrustify.sourceforge.net/): source code beautifier. 2263 | 2264 | ### Other Objective-C Style Guides 2265 | 2266 | Here are some of the documents from Apple that informed the style guide. If something has not been mentioned in this book, it's probably covered in great detail in one of these: 2267 | 2268 | From Apple: 2269 | 2270 | * [The Objective-C Programming Language](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Introduction/introObjectiveC.html) 2271 | * [Cocoa Fundamentals Guide](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaFundamentals/Introduction/Introduction.html) 2272 | * [Coding Guidelines for Cocoa](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html) 2273 | * [iOS App Programming Guide](http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/Introduction/Introduction.html) 2274 | 2275 | From the community: 2276 | 2277 | * [NYTimes Objective-C Style Guide](https://github.com/NYTimes/objetive-c-style-guide) 2278 | * [Google](http://google-styleguide.googlecode.com/svn/trunk/objcguide.xml) 2279 | * [GitHub](https://github.com/github/objective-c-conventions) 2280 | * [Adium](https://trac.adium.im/wiki/CodingStyle) 2281 | * [Sam Soffes](https://gist.github.com/soffes/812796) 2282 | * [CocoaDevCentral](http://cocoadevcentral.com/articles/000082.php) 2283 | * [Luke Redpath](http://lukeredpath.co.uk/blog/my-objective-c-style-guide.html) 2284 | * [Marcus Zarra](http://www.cimgf.com/zds-code-style-guide/) 2285 | * [Ray Wenderlich](https://github.com/raywenderlich/objective-c-style-guide) 2286 | 2287 | 2288 | [1]: https://github.com/NYTimes/objective-c-style-guide/issues/6) 2289 | --------------------------------------------------------------------------------