本条要点:(作者总结)

  • 在加载阶段,如果类实现了 load 方法,那么系统就会调用它。分类里也可以定义此方法,类的 load 方法要比分类中的先调用。与其他方法不同,load 方法不参与覆写机制。
  • 首次使用某个类之前,系统会向其发送 initialize 消息。由于此方法遵从普通的覆写规则,所以通常应该在里面判断当前要初始化的是哪个类。
  • load 与 initialize 方法都应该实现的精简一些,这有助于保持应用程序的响应能力,也能减少引入 “保留环”(interdependency cycle)的几率。
  • 无法在编译期设定的全局常量,可以放在 initialize 方法里初始化。

  有时候,类必须先执行某些初始化操作,然后才能正常使用。在 Objective-C 中,绝大多数都继承自 NSObject 这个类,而该类有两个方法,可用来实现这种初始化操作。

  首先要讲的是 load 方法,其原型如下:

  + (void)load;

  对于加入运行期系统中的每个类(class)及分类(category)来说,必定会调用此方法,而且仅调用一次。当包含类或分类的程序库载入系统时,就会执行此方法,而这通常就是指应用程序启动的时候,若程序是为 iOS 平台设计的,则肯定会在此时执行。 Mac OS X 应用程序更自由一些,它们可以使用 “动态加载”(dynamic loading)之类的特性,等应用程序启动好之后再去加载程序库。如果分类和其所属的类都定义了 load 方法,则先调用类里的,再调用分类里的。

  load 方法的问题在于,执行该方法时,运行期系统处于 “脆弱状态”(fragile state)。在执行子类的 load 方法之前,必定会先执行所有超类的 load 方法,而如果代码还依赖了其他程序库,那么程序库里相关类的load 方法也必定会先执行。然而,根据某个给定的程序库,却无法判断出其中各个类的载入顺序。因此,在 load 方法中使用其他类是不安全的。比方说,有下面这段代码:

  #import <Foundation/Foundation.h>

  #import "EOCClassA.h" // < From the same library

  @interface EOCClass : NSObject

  @end

  @implementation EOCClassB

  + (void)load {

    NSLog(@"Loading EOCClassB");

    EOCClassA *object = [EOCClassA new];

    // Use 'Object'

  }

  @end

  此处使用 NSLog 没问题,而且相关字符串也会照常纪录,因为 Foundation 框架肯定在运行 load 方法之前就已经载入系统了。但是,在 EOCClassB 的load 方法里使用 EOCClassA 却不太安全,因为无法确定在执行 EOCClassB 的 load 方法之前,EOCClassA 是不是已经加载好了。可以想见:EOCClassA 这个类,也许会在其 load 方法中执行某些重要操作,只有执行完这些操作之后,该类实例才能正常使用。

  有个重要的事情需要注意,那就是load 方法并不像普通的方法那样,它并不遵从那套继承规则。如果某个类本身没实现 load 方法,那么不管其各级超类是否实现此方法,系统都不会调用。此外,分类和其所属的类里,都可能出现 load 方法。此时两种实现代码都会调用,类的实现要比分类的实现先执行。

  而且 load 方法务必实现得精简一些,也就是要尽量减少其所执行的操作,因为整个应用程序在执行load 方法时都会阻塞。如果 load 方法中包含繁杂的代码,那么应用程序在执行期间就会变得无响应。不要在里面等待锁,也不要调用可能会加锁的方法。总之,能不做的事情就别做。实际上,凡是想通过 load 在类加载之前执行某些任务的,基本都做的不太对。其真正用途仅在于调试程序,比如可以在分类里编写此方法,用来判断该分类是否已经正确载入系统中。也许此方法一度很有用处,但现在完全可以说:时下编写 Objective-C 代码时,不需要用它。

  想执行与类相关的初始化操作,还有个办法,就是覆写下列方法:

  + (void)initialize;

  对于每个类来说,该方法会在程序首次用该类之前调用,且只调用一次。 它是由运行期系统来调用的,绝不应该通过代码直接调用。其虽与 load 相似,但却有几个非常重要的微妙区别。首先,它是 “惰性调用的”,也就是说,只有当程序用到了相关的类时,才会调用。因此,如果某个类一直都没有使用,那么其 initialize 方法就一直不会运行。这也就等于说,应用程序无须先把每个类的 initialize 都执行一遍,这与 load 方法不同,对于 load 来说,应用程序必须阻塞并等着所有类的 load 都执行完,才能继续。

  此方法与 load 还有个区别,就是运行期系统在执行该方法时,是处于正常状态的,因此,从运行期系统完整度上来讲,此时可以安全使用并调用任意类中的任意方法。而且,运行期系统也能确保 initialize 方法一定会在 “线程安全的环境”(thread-safe environment)中执行,也就是说,只有执行 initialize 的那个线程可以操作类或类实例。其他线程都要先阻塞,等着 initialize 执行完。

  最后一个区别是: initialize 方法与其他消息一样,如果某个类未实现它,而其超类实现了,那么就会运行超类的实现代码。这听起来并不稀奇,但却经常为开发者所忽视。比方说有下面这两个类:

  #import <Foundation/Foundation.h>

  @interface EOCBaseClass : NSObject

  @end

  @implementation EOCBaseClass

  + (void)initialize {

    NSLog(@"%@ initialize", self);

  }

  @end

  @interface EOCSubClass : EOCBaseClass

  @end

  @implementation EOCClass

  @end

  即便 EOCSubClass 类没有实现 initialize 方法,它也会收到这条消息。由各级超类所实现 initialize 也会先行调用。所以,首次使用 EOCSubClass 时,控制台会输出如下消息:

  EOCBaseClass initialize

  EOCSubClass initialize

  你可能认为输出的内容有些奇怪,不过这完全符合规则。与其他方法 (除去 load)一样, initialize 也遵循通常的继承规则,所以,当初始化基类 EOCBaseClass 时,EOCBaseClass 中定义的 initialize 方法要运行一遍,而当初始化 EOCSubClass 时,由于该类并未覆写此方法,因而还要把父类的实现代码再运行一遍。鉴于此,通常都会这么来实现 initialize 方法:

  + (void)initialize {

    if (self == [EOCBaseClass class]) {

      NSLog(@"%@ initialized", self);

    }

  }

  加上这条检测语句之后,只有当开发者所期望的那个类载入系统时,才会执行相关的初始化操作。如果把刚才的例子照此改写,那就不会打印出两条消息了,这次只输出一条:

  EOCBaseClass initialize

  看过 load 与 initialize 方法的这些特性之后,又回到了早前提过的那个主要问题上,也就是这两个方法的实现代码要尽量精简。在里面设置一些状态,使本类能够正常运作就可以了,不要执行那种耗时太久或需要加锁的任务。对于 load 方法来说,其原因已在前面解释过了,而 initialize 方法要保持精简的原因,也与之相似。首先,大家都不想看到应用程序“挂起”(hang)。对于某个类来说,任何线程都可能成为初次用到它的那个线程,并导致其初始化。如果这个线程碰巧是 UI 线程,那么初始化期间就会一直阻塞,导致应用程序无响应。有时候很难预测到底哪个线程会先用到这个类,强令某线程去初始化该类,显然不是好办法。

  其二,开发者无法控制类的初始化时机。类在首次使用之前,肯定要初始化,但编写程序时不能令代码依赖特定的时间点,否则会很危险。运行期系统将来更新了之后,可能会略微改变类的初始化方式,这样的话,开发者原来如果假设某个类必定会在某个具体时间点初始化,那么现在这条假设可能就不成立了。

  最后一个原因,如果某个类的实现代码很复杂,那么其中可能会直接或间接用到其他类。若那些类尚未初始化,则系统会迫使其初始化。然而,本类的初始化方法此时尚未运行完毕。其他类在运行其 initialize 方法时,有可能会依赖本类中

的某些数据,而这些数据此时也许还未初始化好。例如:

  #import <Foundation/Foundation.h>

  static id EOCClassAInternalData;

  @interface EOCClassB : NSObject

  @end

  @implementation EOCClassA

  + (void)initialize {

    if (self == [EOCClassA class]) {

      [EOCClassB doSomethingThatUsesItsInternalData];

      EOCClassAInternalData = [self setupInternalData];

    }

  }

  @end

  @implementation EOCClassB

  + (void)initialize {

    if (self == [EOCClassB class]) {

      [EOCClassA doSomethingThatUsesItsInternalData];

      EOCClassBInternalData = [self setupInternalData];

    }

  }

  @end

  若是 EOCClassA 先初始化,那么 EOCClassB 随后也会初始化,它会在自己的初始化方法中调用 EOCClassA 的 doSomethingThatUsesItsInternalData,而此时 EOCClassA 内部的数据还没准备好。在实际编码工作中,问题不可能像此处说的那样明显,而是牵涉的类可能也不止两个。因此,当代码无法正常运行时,想要找出错误就更难了。

  所以说,initialize 方法只应该用来设置内部数据。不应该在其中调用其他方法,即便是本类自己的方法,也最好别调用。因为稍后可能还要给那些方法里添加更多功能,如果在初始化过程中调用它们,那么还是有可能导致刚才说的那个问题。若某个全局状态无法在编译期初始化,则可以放在 initialize 里来做。下列代码演示了这种用法:

  // EOCClass.h

  #import <Foundation/Foundation.h>

  @interface EOCClass : NSObject

  @end

  // EOCClass.m

  #import "EOCClass.h"

  static const int kInterval = 10;

  static NSMutableArray *kSomeObjects;

  @implementation EOCClass

  + (void)initialize {

    if (self == [EOCClass class]) {

      kSomeObjects = [NSMutableArray new];

    }

   }

  @end

  整数可以在编译期定义,然而可变数组不行,因为它是个 Objective-C 对象,所以创建实例之前必须先激活运行期系统。注意,某些 Objective-C 对象也可以在编译期创建,例如 NSString 实例。然而,创建下面这种对象会令编译器报错:

  static NSMutableArray *kSomeObjects = [NSMutableArray new];

  编写 load 或 initialize 方法时,一定要留心这些注意事项。把代码实现得简单一些,能节省很多调试时间。除了初始化全局状态之外,如果还有其他事情要做,那么可以专门创建一个方法来执行这些操作,并要求该类的使用者必须在使用本类之前调用此方法。比方说,如果“单例类”(singleton class)在首次使用之前必须执行一些操作,那就可以采用这个办法。

  END

转载于:https://www.cnblogs.com/chmhml/p/7467316.html

第51条:精简initialize与load的实现代码相关推荐

  1. mysql快速导入5000万条数据过程记录(LOAD DATA INFILE方式)

    mysql快速导入5000万条数据过程记录(LOAD DATA INFILE方式) 首先将要导入的数据文件top5000W.txt放入到数据库数据目录/var/local/mysql/data/${d ...

  2. 计算机单词修改是否正确,计算机组装必懂的53个单词及装机步骤51条.doc

    计算机组装必懂的53个单词及装机步骤51条 2007-05-26 11:43 计算机组装必懂的53个单词及装机步骤51条 计算机组装DIY(Do It Yourself)至少要看懂的五十三个英文单词和 ...

  3. 感谢老师的51条短信息

    克里斯·德鲁(Chris Drew)搜集了感谢老师的七种场合下的合适致谢信息,发表在Helpfulprofessor网上.这些致谢信息合起来有51条,本文给出了这51条致谢信息的译文. Chris D ...

  4. nRF24L01+基于51单片机的驱动(库)实战代码分享

    nRF24L01+基于51单片机的驱动(库)实战代码分享 关于代码来源 定义数据类型的头文件Type.h nRF24L01+驱动源代码文件nRF24L01P.c nRF24L01+驱动的头文件nRF2 ...

  5. 51单片机开发系列一-51单片机开发环境搭建以及入门汇编代码

    51单片机开发系列一 51单片机开发环境搭建以及入门汇编代码 象棋小子    1048272975 1. 51单片机概述 51单片机是对所有兼容Intel 8031指令系统的单片机的统称.目前教科书基 ...

  6. C语言 | 基于51单片机实现MPU6050的卡尔曼滤波算法(代码类2)

    github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 之前写过一个博客(代码分享:单片机开发 | ...

  7. 计组课设:单周期31条MIPS指令CPU设计(含代码)

    多周期54条CPU:计组课设:多周期54条MIPS指令CPU设计(含代码)_孔艺菲的博客-CSDN博客 单周期CPU源码:while-TuRe/Single-cycle-CPU31 (github.c ...

  8. C语言极速学习开发——51单片机入门编程之使用KeilC51进行代码编译(点亮你心中学习的精神之灯-下)

    系列文章目录 提示:本文章为系列文章,系列文章的所有文章的目录后期会时刻更新,喜欢的朋友请收藏好 <第一章 C语言极速学习开发--51单片机入门编程之使用KeilC51进行代码编译(点亮你心中学 ...

  9. 《伤寒论》——辨太阳病脉证并治(下)51条

    辨太阳病脉证并治(下) 1.问曰:病有结胸①,有脏结②,其状何如?答曰:按之痛,寸脉浮,关脉沉,名曰结胸也. 2.何谓脏结?答曰:如结胸状,饮食如故,时时下利,寸脉浮,关脉小细沉紧,名曰脏结.舌上白胎 ...

  10. 刘彦斌51条理财名言

    1.一个人一生离不开三件事,[健康].[法律]和[财务]. 我们学校教育中缺乏财商教育,现在许多孩子都习惯于看到一件满意的东西就朝爸爸妈妈开口"我要买",但是钱是哪里来的.如何去衡 ...

最新文章

  1. 天池算法赛:数据挖掘经典赛事!DCIC 2020 数字中国创新大赛启动!
  2. 在Win10 Anaconda中安装Tensorflow
  3. padding valid same区别——就是是否补齐0的问题
  4. JavaScript设计模式-10.工厂模式实例xhr
  5. labelimg如何调整框的颜色_如何制作摄影集(下)
  6. 除了“团队牛”还有“饭菜香”,百度 IDL 招聘算法实习生
  7. 火星人谚语系列之八:少读书,多思考
  8. SylixOS 操作系统Makefile 简介
  9. 等概率随机产生0和1
  10. mschart走势图 vc_VC++6.0中MsChart控件的用法
  11. c++模板参数自动推导
  12. 微信模板消息字体设置变大
  13. 加州大学洛杉机分校计算机科学,加州大学洛杉矶分校计算机科学与工程世界排名2017年最新排名第6(ARWU世界排名)...
  14. 读书笔记:Dynamic GCN: Context-enriched Topology Learning for Skeleton-based Action Recognition
  15. 长沙民政职业技术学院计算机网络技术专业,长沙民政职业技术学院计算机网络技术专业...
  16. 数仓建模—数据治理的本质与实践
  17. 树莓派通过snowboy唤醒引擎(Python2、Python3的都可以),自定义唤醒词、关键字,达到小爱同学、天猫精灵一样的唤醒方式的全套教程
  18. 为什么lol计算机内存不足怎么办,win7玩LOL英雄联盟提示“内存不足”怎么处理?(图文)...
  19. 浅谈示波器X-Y模式 示波器触发模式及使用
  20. 政府怎么应用视频直播系统?

热门文章

  1. 项目交换通知——PM(李忠)
  2. 21最难调剂年:150万人参加调剂,预扩招18万人!
  3. 【Linux】详解Linux中3个文件查找相关命令
  4. 【情感分析】基于Aspect的情感分析模型总结(一)
  5. BERT/Transformer/迁移学习NLP资源大列表
  6. 如果你被这个视频深深地震撼!那你一定是幸运的!
  7. 具体数学-第2课(成套方法求解递归式)
  8. TF2.0—tf.keras.layers.Lambda
  9. tensorflow和keras的关系
  10. pyspark--dataframe使用