Effective objective-C 读书笔记 (第一片段)

第1章 熟悉Objective-C

第1长达 了解Objective-C语言的自

  • Objective-C是一样栽“消息结构”的言语,而无“函数调用”语言。
  • 要分在:使用信息结构的语言,其运作时所行之代码由运行环境来支配;而利用函数调用语言,则由编译器决定。若是函数调用语言,若调用的函数是多态的,则需按“虚方法表”来规定究竟该尽哪个函数实现。(即用“运行时派发”(runtime
    method
    binding)),而“消息结构语言”无论是否多态,总是在使运行时才会错过查所实施的法子,实际上编译器甚至不关乎消息是何种类型,接收信息的对象问题吗使以运行时处理,这个进程叫“dynamic
    binding”。
  • Objective-C的基本点工作还是由“运行期组件(runtime
    component)”完成的,而非编译器完成的。使用Objective-C的面向对象特性的所待整个数据结构及函数都于运行期组件里面。举例:运行期组件含有全部内存管理方。通俗来讲:只要还运行Objective-C工程即可提升应用程序性能,而工作还于“编译期”完成的言语,如果想取属性的提升,必须使重编译。
  • Objective-C语言中之指针用来针对对象,这点全照搬C语言。NSString *string = @"string";她声明了一个对准NSString类型的指针string,这象征了该string指向的目标分配在积上,在Objective-C中,所有目标还分配在积上,而string本身分配在栈上。
  • 分配在积中的内存必须一直保管,而分红在栈上的内存则会当其栈帧弹出时,自动清理。
  • CGRect rect意味着的凡C语言中的布局体类型,他们会使栈空间。因为若通Objective-C语言都动对象,则性能会给影响。

第2长 在类的腔文件中尽量少引入其他头文件

  • 将引入头文件的时机尽量延后,只于确定来得时才引入,这样就是足以减类似的使用者所引入的腔文件数量。而而把头文件一股脑的满引入,会加多不必要的编译时间。若要以峰文件中宣示一个别类似的@property,则可率先用上声明@class XXX.h如此这般就足以告知编译器,我先行引入这类似,实现的底细后再报告您。
  • 用上声明同时也得以化解了少数只八九不离十相互引用的问题。
  • 要点:
    • 惟有有必不可少,否则不要引入头文件,一般的话,应于有类的条文件中尽量采用上声明来提及别的类,并于贯彻公文中引入那些看似的峰文件。这样做得尽可能降低类之间的耦合。
    • 偶尔无法利用上声明,比如使声明某个类遵守某个协议,这样的话尽量将“该类所遵循的情商”
      这条声明在“class-continuation分类”中,如果坏,还可把分类放在一个独的腔文件被又引入。

第3漫漫 多为此配面量语法,少用和的对等价格的语法

  1. 字面数值NSNumber

  2. 平常方法:NSNumber *someNumber = [NSNumber numberWithInt:1];
    等价格的字面量方法:NSNumber *someNumber = @1;会以NSNumber类型表示的持有品种且可以动用该语法。字面量语法也可用来下面的表达式:

int x = 5;
int y = 6;
NSNumber *num = @(x * y);
  1. 字面数组NSArray

  2. 平凡方法:NSArray *array = [NSArray arrayWithObjects:@"cat", @"dog", @"pig", nil];
    字面量方法:NSArray *array = @["dog", @"cat", @"pig"];该措施以语义上也是同的,但是越来越便利。若要取出第1只要素虽然array[0]

  3. 亟需留意的是,当使用字面量方式开创数组时,若数组元素对象被出nil,则会丢来特别,因为字面量语法实际上是同种语法糖,其当效于先创造一个累组,再管具有因素添加到是数组中,而以普通方法创建数组时,若数组某个元素也nil,则会一直在该岗位就数组的创导,nil之后的素都将让抛弃,并且为不见面报错。所以用字面量语法更为安全,抛来特别终止程序总比直接获取错误的结果一旦好。

  4. 字面字典NSDictionary

  5. 采取字面量语法创建字典会令字典更加清晰明了。并且与数组一样,字面量创建字典时,若遇nil也会丢来怪。

  6. 字典也可以像数组那样用配面量语法访问。普通方法:[data objectForKey:@"hehe"];等于于字面量方法:data[@"hehe"];

  7. 不过变数组与字典

  8. 也堪下字面量的主意修改中的元素值:mutableArray[1] = @"gege";

  9. 局限性

  10. 动字面量语法创建出来的次第Foundation框架中的靶子还是不足变类型的,若要将该转会为可变类型,则需要复制一卖NSMutableArray *mutable = [@[@"cat", @"dog", @"pig"] mutableCopy];如此做会多调用一个道,还要再次多创造一个靶,但是补还是超越这些毛病的。

  11. 界定:除了字符串外,所创办出来的对象要属于Foundation框架才行,即NSArray的子类就非可以利用字面量语法,不过貌似为未待从定义子类。

第4条 多用种常量,少用#define预处理指令

  • 当使用#define预先处理指令定义变量时,假设#define ANIMATION_DURATION 0.3不时,你觉得早已定义好了,实际上当编译时,会用不折不扣程序有所叫做ANIMATION_DURATION的价都替换为0.3,也就是说要你在其他文件为定义了一个ANIMATION_DURATION,它的价值吗会为反。要惦记解决之题目,则需充分利用编译器的风味,比如:static const NSTimeInterval kAnimationDuration = 0.3;诸如此类便定义了一个号称也kAnimationDuration的常量。
  • 一旦未打算公诸于世某个常量,则当出口她定义在.m文件被,变量一定要以用staticconst来定义,使用const宣示的变量如果见到试图修改其的值,编译器就会报错。而使用static宣称的变量,表示该变量仅仅以概念之变量的编译单元中可见(即只有以是.m文本被可见)。假设不也变量添加static修饰符,则编译器会活动吗其缔造一个external symbol外部符号这时候只要其他一个.m文件被呢定义了同名变量,则会报错。
  • 实在只要一个变量既声明也static同时声称也const,name编译器会一直像#define一律,把持有遇到的变量都替换为常量。不过还是来一个区别:用这种艺术定义的常量带有类型信息。
  • 当得对外公开某个常量时,可以使用extern修饰符来修饰常值变量。例如当通告被,注册者无需清楚实际字符串的具体值,只待坐常值变量来注册自己想只要接纳的关照即可。此类变量常在“全局符号表”中,以便可以更定义该常量的编译单元之外使用。例如

// .h
extern NSString *const LYStringConstant;

// .m
NSString *const LYStringConstant = @"VALUE";
  • 动上述方法,即可在头文件被声明,在贯彻公文中定义。一旦编译器看到extern重在字,就懂得如何以引入此头文件之代码中处理常量了。此类常量必须要定义,并且不得不定义一差,通常还是在宣称该常量的
    .m 文件中定义该常量。编译器在这时,会在“data
    segment”中吗字符串分配存储空间。链接器会将此目标文件及另目标文件相链接,生成最终之二进制文件。
  • 瞩目常量的讳,为了避免名称冲突,一般前缀都为跟之有关的接近。
  • 以实现公文中采取static const概念“只当编译单元内可见的常量”,并且普通名称前加前缀k

第5修 用枚举表示状态,选项,状态码

  • 应用枚举来代表状态机的状态,传递让方的选项和状态码等值,给这些价值通俗易懂的名字。
  • 如拿传递给有方法的挑选项表示也枚举类型,而多只挑选又只是同时以,应该运用
    NS_OPTIONS 通过按位与操作以那个构成起来。
  • NS_ENUMNS_OPTIONS
    宏来定义枚举类型,并指明其底层的数据类型,这样做而管枚举是因此开发者所挑选的底数据列实现之。
  • 在拍卖枚举类型的 switch 语句被,不要采取 default
    分支,这样进入新枚举之后编译器便会提醒开发者:switch语句还不处理所有的枚举。

第2章 对象,消息,运行期

  • 使用 Objective-C 编程时,对象就是“基本的构造单元”
    (buliding block) ,在目标中 传递数据 并且 执行任务 的进程尽管于做
    “消息传递Messaging” 一定要是熟悉这有限独特色的做事原理。
  • 当程序运行后,为那个提供支撑之代码叫做:Objective-C运行期环境(Objective-C runtime),它提供了有的令对象期间会传递信息之重中之重函数,并且包含创建类实例所用的总体逻辑。都是亟需明白的。

第6长条 理解“属性”这无异于定义

  • 当直接以 类接口 中定义 实例变量 时,对象布局在 “编译期”
    就曾经稳定了。只要碰到访问该实例变量的办法,编译器就自行将该替换为
    “偏移量(offset)”,并且这偏移量是 硬编码
    ,表示该变量距离存放对象的内存区域的苗子地址发生差不多远,这样做一样开并未问题,但是倘若要是重新新加加一个实例变量,就需要再次编译了,否则将偏移量硬编码于其中的那么部分代码都见面读取到不当的值。Objective-C避这荒唐的做法是将
    实例变量 当做一种植存储 偏移量 所用之 “特殊变量” ,交给 “类对象”
    保管。偏移量会于 运行期 runtime
    查找,如果类似的定义变了,那么存储的偏移量也即变换了。这是内的均等种植于硬编码的解决方案。还有同种缓解方案就是硬着头皮
    不要直接 访问实例变量,而是通过 存取方法 来访问。也不怕是宣称属性
    @property
  • 每当目标接口的概念着,可以采取 属性
    来访问封装在对象吃的数额。编译器会活动写有同样效存取方法,用以访问于定类型中有所给定名称的变量。此过程叫做
    “自动合成” ,这个进程由于编译器在编译期间推行,所以编译器里看不到这些
    systhesized method合成方法
    的源代码。编译器还会自行往类中上加适量类型的实例变量,并当性能名称前加下划线。
  • 好采取 @synthesize 语法来指定实例变量的名字
    @synthesize firstName = _myFirstName;
  • 如若非思量让编译器自动合成存取方法,则好采用 @dynamic
    关键字来阻止编译器自动合成存取方法。并且以编译访问属性之代码时,编译器也无见面报错。因为他相信这些代码可以以
    runtime 时找到。
  • 特性特质 属性可以拥有的特质分为四类
    • 原子性
      • 以默认情况下,由编译器所合成的方法会通过锁机制保证其原子性,如果属性具备
        nonatomic
        特质,则无采用并锁,一般情况下于iOS开发被,都以性能声明也
        nonatomic
        修饰的,因为原子性将会晤损耗大量资源以为不可知保证“线程安全”,若要促成“线程安全”则要再深层的锁机制才行。
      • atomicnonatomic 的界别在于:具备 atomicget
        方法会通过锁机制来确保操作的原子性,也便是要是个别单线程同时读取同一属性,无论何时总是能够观看中的价值。而设无加锁,当内一个线程在改写某属性的值经常,另一个线程也得看该属性,会招数据错乱。
    • 读/写权限
      • readwrite 特质的性,若该属性由 @synthesize
        实现,则编译器会自动生成当下片独道。
      • readonly 特质的属性只具备读方法。只有当拖欠属性由
        @synthesize 实现时,编译器才见面吗那丰富获取方式。
    • 内存管理语义
      • assign:只针对“纯量类型”(CGFloatNSInteger 等)
      • strong :表明该属性定义了扳平栽 “拥有关系”
        ,即为这种性质设置新值时,设置方法会先保留新值,再自由旧值,然后再度以新值设置上。
      • weak:表明该属性定义了一致栽 “非拥有关系”
        ,即为这种属性设置新值时,设置方法会既然不保留新值,也不纵旧值,此特质同
        assign
        类似,然而于性能所因的靶子中摧毁时,该属性值也会见清空(即针对nil)
      • copy:此特质所抒发的隶属关系同 strong
        类似,只是,设置法并无保留新值,而是将那“拷贝”(copy)。当属于性类型为
        NSString* 时,经常用是特性来保管其封装性。因为传递让
        set
        方法的新值有或靠为一个只是更换字符串,由于可变换字符串是字符串的子类,所以字符串属性指为他并无会见报错,而此时,一旦而易字符串的价改变了,字符串的值也会暗暗的跟着变动,会招在咱们不知情的情景下,NSString*属性之价值就改成了,所以应拷贝一卖而转移字符串的不足变值immutable的字符串,确保目标被之字符串不会见无意变动。
      • unsafe_unretained :此特质所发挥的语义同 assgin
        相同,但她适用于对象类型,该特征表达了同等种植 “非拥有关系”
        当对象对象中摧毁时,不会见自动对nil(不安全)
    • 方法名
      • @property (nonatomic, getter=isOn) BOOL on;
        通过如下方式来转 get 方法的道名。

第7长长的 在目标中尽量直接访问实例变量

  • 鉴于未通过 Objective-C 的 “方法派发”
    ,所以一直看实例变量的进度较快。
  • 直访问实例变量时,不会见调用其 setter
    方法,这便绕了了呢有关属性所定义之 “内存管理语义”
    。比方说:在ARC环境下直看一个扬言也copy的特性,将无见面拷贝该属性。而是直接抛旧值保留新值。
  • 假使一直访问实例变量,则勿会见触发KVO通知。这样做是否生问题还要看现实的问题。
  • 通过属性来访问实例变量有助于排查和的休戚相关的荒谬,因为好给getter/setter新增断点,来监控其值。
  • 每当靶中读取数据时,应该直接通过实例变量来读取,写多少经常,应该通过性能来写。
  • 以初始化或dealloc方法吃,总是应该一直通过实例变量来读写多少。
  • 当使用懒加载方法加载数据经常,需要经过性能来读数据。

第8久 理解“对象等同性”这同定义

  • 比如 “ == ”
    操作符比较出来的结果未必使我们想只要的,因为它实质上是于比较有数独实例变量所针对的对象是否也同一值,换句话说,它实际上比的凡实例变量所对堆内存中的靶子地址是否也跟一个。而当我们如果自然如简单只目标是否同样时,往往想使较的凡点滴个目标所表示的逻辑意义上的是否等
  • 故是时段需要以 NSObject 协议中声明的 isEqual
    方法来判断两单对象的等同性。NSObject
    协议被产生少独用于判断等同性的显要措施:- (BOOL)isEqual:(id)object;
    - (NSInteger)hash;NSObject
    类对立即点儿单章程的默认实现只是简短的比较单薄独对象的地方是否等于。
  • 当起定义等时,必须掌握这点儿只方式的采取标准和意义。
    • isEqual 判定两个目标等时,那么 hash
      方法吧须回到同样的价值;
    • hash 方法也回同样的价时,isEqual 未必判定两单对象等;

// 一种实现hash的比较高效的方法。
- (NSInteger)hash {
    NSInteger firstNameHash = [_firstName hash];
    NSInteger lastNameHash = [_lastName hash];
    Nsinteger age = _age;
    return firstNameHash^ lastNameHash^ age;
}
  • 当自己实现判断等同性方法时,当覆写 isEqual
    方法时,有一个逻辑的论断:如果手上受测的参数和收该消息的目标都属同一个像样,则调用自己编辑的方式,否则交给超类来判定。

第9长长的 以“类族模式”隐藏实现细节

  • 类簇可以隐藏抽象基类,是一律种异常有因此之设计模式,OC框架中普遍采取是模式。比如
    UIButton 类中生一个
    + (UIButton)buttonWithType:(UIButtonType)type
    类方法。这个法可以为你传递一个参数为它们,然后她会活动根据你传递的参数类型自动生成对应之
    Button

  • 这个设计模式的在 iOS
    中之落实方式就是优先定义抽象的基类。在基类的头文件中定义各种 Button
    类型。然后采用工厂方法返回用户所选的品种的实例。然后分别实现各个实例。示例如下:

    // 首先定义UIButton类型种类
    typedef NS_ENUM(NSInteger, UIButtonType) {
        UIButtonTypeCustom = 0,                         // no button type
        UIButtonTypeSystem NS_ENUM_AVAILABLE_IOS(7_0),  // standard system button
    
        UIButtonTypeDetailDisclosure,
        UIButtonTypeInfoLight,
        UIButtonTypeInfoDark,
        UIButtonTypeContactAdd,
    
        UIButtonTypePlain API_AVAILABLE(tvos(11.0)) __IOS_PROHIBITED __WATCHOS_PROHIBITED, // standard system button without the blurred background view
    
        UIButtonTypeRoundedRect = UIButtonTypeSystem   // Deprecated, use UIButtonTypeSystem instead
    };
    
    // 再实现具体的类型方法,伪代码如下
    @interface UIButton : UIControl <NSCoding>  
    @property(nullable, nonatomic,readonly,strong) UILabel     *titleLabel NS_AVAILABLE_IOS(3_0);
    @property(nullable, nonatomic,readonly,strong) UIImageView *imageView  NS_AVAILABLE_IOS(3_0);
    
    + (UIButton)buttonWithType:(UIButtonType)type; 
    - (void)setTitle:(nullable NSString *)title forState:(UIControlState)state; 
    @end
    @implementation UIButton
    
    + (UIButton)buttonWithType:(UIButtonType)type {
      switch(type) {
        case 0:
          return [UIButtonCustom new];
          break;
        case 1:
          return [UIButtonSystem new];
          break;
        case 2:
          return [UIButtonDetailDisclosure new];
          break;
          ...
      }  
    }
    
    - (void)setTitle:(nullable NSString *)title forState:(UIControlState)state {
      // 空实现
    }
    
    @end
    
    // 然后再具体实现每个"子类"
    @interface UIButtonCustom : UIButton
    
    @end
    @implementation
    
    - (void)setTitle:(nullable NSString *)title forState:(UIControlState)state {
      // 实现各自不同的代码  
    }   
    @end
    

  • 得专注的是,这种措施下,因为 OC
    语言中从来不章程指名一个基类是空虚的,所以基类接口一般没有名叫吧 init
    的积极分子方法,这证明该基类并无该直接叫创造。而 UIButton
    中实际上有这种方式,所以其实 UIButton呢并无完全符合策略模式。

  • 当你所创造的目标在有类簇中,你就算待开始当心了。因为若或许以为好创建了某类,实际上创建的审该类的子类。所以未得以采取
    isMemberOfClass
    这个法子来判定你所创建的此类似是否是此类,因为它们实际上可能会见回 NO
    。所以明智的做法是使用 isKindOfClass 这个措施来判定。

  • COCOA框架中的类簇:NSArrayNSMutableArray
    ,不可变类定义了针对性所有数组都通用的章程,而只是变类定义了价值适用于可变数组的法。两个像样一起属于同一个类簇。这意味双方在实现各自类型的数组时,可以一起用实现代码。并且还能拿可变数组复制成不可变数组,反之亦然。

  • 咱们常用为类簇中初增子类,而当我们无法取得创建这些类似的“工厂方法”的源代码,我们就是无法为其中新增子类品种。但是事实上若死守以下几种方法,还是得以通往内添加的。

    • 子类应该继续自类簇的纸上谈兵基类
    • 子类应该定义自己的数量存储方
    • 子类应该覆写超累文档中指定需要覆写的不二法门

第10长条 在既来近似吃使用关联对象存放于定义数据

  • 奇迹用以靶中存放相关消息,这是我们平常会自目标所属的类中继承一个类,然后改用这个子类对象。但是不要有情况下还好这么做,有时实例可能是出于某种特殊体制所创办,而开发者无法令这种体制创建有自己所勾画的子类实例。OC受到来同等桩强大的特性可缓解者题材,那就是涉嫌对象。

  • 足于一个靶关联许多的其它对象,这些目标期间可以为此过 key
    来进行分。存储对象时,可以指明 “存储策略” ,用以保护相应的
    “内存管理”
    。存储策略类型如下:(加入关联对象成了性,那么它便会持有和存储策略相同之语义)

    typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
        OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
        OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                                *   The association is not made atomically. */
        OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                                *   The association is not made atomically. */
        OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                                *   The association is made atomically. */
        OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                                *   The association is made atomically. */
    };
    
  • 下列方法好管理关系对象

    // 通过给定的 key 和 value 和 objc_AssociationPolicy policy 为 object 设定 关联对象 值
    void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                             id _Nullable value, objc_AssociationPolicy policy)
    
    // 通过给定的 key 来从 object 中读取 关联对象 的值
    id getAssociatedObject(id object, void *key);
    
    // 移除指定的 object 的全部 关联对象
    void objc_removeAssociatedObjects(id object);
    
  • 咱得以把某某对象想象成是某 NSDictionary
    把干到拖欠目标的价值理解呢字典中的条文。
    于是,存取相关联的对象的价值就是一定给当 NSDictionary 上调用
    setObject: forKey:objectForKey:
    。然而两者之间有个根本之歧异,就是安关联对象时,使用的
    key指南针指向的经常无限量类的指针,而 NSDictionary
    当设置时,就理解该目标的品类了。所以一旦在点滴只 key上调用 isEqual
    方法,NSDictionary足回到YES,就可认为简单个 key 相等。而
    关联对象 却无是如此。 所以我们司空见惯会拿 关联对象 的 key 值设定为
    静态全局变量。

  • 关联对象的用法举例:可以利用关联对象,给类的归类在Runtime时期动态添加属性,因为
    Category 原本是无支持性的。这种办法好就此当夜模式时,给
    UIView 的分类动态添加属性。

  • 顾:只有当其余做法不可行时才会选用关联对象,因为这种做法屡见不鲜会引入难找寻的bug

第11条 理解objc_msgSend的作用

  • 在对象上调用方法是 OC 中时采取的效能, 用 OC 的术语来说即使是
    “传递消息” 。消息发出“name” 和 “selector” 可以领参数,
    并且有归值。

  • C 语言使用 static binding
    也就是说,在编译时即便都规定了运转时所调用的函数。于是会直接生成所调用函数的指令,而函数指令实际上是硬编码在指令中的。只有
    C 语言的编辑使用多态时, C 语言才会于某一个函数上运 dynamic
    binding

  • 假如以 OC 中, 如果向有目标传递消息,就见面动用 dynamic binding
    机制来支配要调用的方。在底部,所有方还是平常的 C
    语言函数,然而对象吸收信之后,究竟该调用异常方式了在运行时。甚至足以重程序运行时转,这些特征使得
    OC 成为同门户确乎的动态语言。

    • 于目标发送信息可以描绘成
      id returnValue = [someObject messageName:parameter];
      其中,翻译成容易懂的言语就是
      id returnValue = [receiver(接收者) selector(选择子):parameter(选择参数)];
      。编译器看到这长长的信息随后,会拿其直接转接成一修 C
      语言函数调用,这条函数就是消息传递机制面临之骨干函数
      objc_msgSend,
      其原型如下:void objc_msgSend(id self, SEL cmd, ...)
      。这是一个参数可转换的函数。第二单参数SEL代表选择子,后续参数是信之参数(也便是选择子的选项参数)。编译器会将刚的那漫长信息转化成为如下函数:

      // 原消息
      id returnValue = [someObject messageName:parameter];
      
      /*
       转化后的消息 -> 所谓的消息接受者,也就是说是这个消息是作用到谁身上的,比如[self method]; 这条消息啊的接受者就是 self
       **/
      id returnValue = objc_msgSend(someObject, 
                                  @selector(messageName:), 
                                  parameter);
      

    • objc_msgSend 函数会依据接收者(receiver) 与
      选择子(selector)的项目来调用适当的法门。为了成功这个操作:

      • 该法需要在接收者所属的类似中查找其“方法列表”
        list of methods
      • 而能找到与择子名称 messageName
        相符合的不二法门吧,就超过至其促成代码。并且会拿匹配结果缓存在“快速映射表”
        fast map
        中,每个接近都生一个这样的缓存,如果略微晚尚于该类发送和选择子相同的消息,那么执行起来就是见面飞,直接当
        fast map
        中检索即可。当然,这种方式或者不如“静态绑定”快速,但是要用选取子
        selector 缓存起来了,就未会见暂缓好多了。实际上
        message dispatch 并无是应用程序的瓶颈所在。
      • 比方找不顶之说话,就顺合体系并迈入找,等找到适合的点子还过反。
      • 设最终还是找不至适合的章程,就实行message forwarding信息转发
        操作。
    • 眼前仅称了片音之调用过程,其他边界情况虽然需要提交 OC
      环境遭到之其它一些函数来拍卖

      // 当待发消息要返回结构体时,可以交给这个函数来处理。
      objc_msgSend_stret
      // 如果返回是浮点数,这个函数处理
      objc_msgSend_fpret
      // 要给超类发送消息时,这个函数处理
      objc_msgSendSuper
      
    • 为此当 objc_msgSend 函数根据 selectorrecevier
      来找到相应调用的法子的 实现代码 后, 会 “跳转”
      到者法的落实, 是因为 OC 对象的每个方法还可以用作是简单的 C
      函数。其 原型
      如下:<return_type> Class_selector(id self, SEL _cmd, ...)
      ,其中,每个 Class 都发生相同摆设表, 其中的指针都见面借助为这种函数,
      而选择子 selector 的则是查表时所用之 keyobjc_msgSend
      函数正是通过就张表来寻觅应该执行之艺术并越反到其的落实之。

    • 内需专注的凡 原型 的规范和 objc_msgSend
      函数异常像。这不是偶合,而是为了使 尾调用优化
      技术,使得跳转到指定方法是操作变得愈简明些。如果有函数的末尾一宗操作是调用另一个函数,则就是可以下
      尾调用优化
      技术。编译器会生成调转至其他一样函数所欲之指令码,而且免会见为调用堆栈中推入新的“栈帧”frame

      只有当函数的尾声一个操作是调动用其他函数时,才好这样做。这项优化对
      OC 十分之重中之重,如果未这么做,这样每次调用 OC
      方法之前,都亟需吗调用 objc_msgSend 准备栈帧,我们得以当
      stack trace 中看这种frame
      此外,如果不优化,还会见过早的发出“栈溢出” stack overflow 现象。

  • 信来接受者 receiver ,选择子 selector 及参数 parameter
    所构成, 给某目标 “发送信息” invork a message
    也尽管是一定给当该目标及 调用方法 call a method

  • 发放某个对象的成套信息都是要是出于
    动态派发系统 dynamic message dispatch system
    来处理的,该系统会翻动相应之计,并履行其代码。

第12久 理解消息转发机制

  • 高达一样长长的道了靶的消息传递机制,这同一长将叙当目标无法解读收到的信不时的中转机制。

  • 如想使类能知晓有修消息,我们必须兑现对应之法才行。但是要是我们通往一个看似发送一个咱没有兑现之计,在编译器时连无见面报错。因为于运转时好延续为类吃补充加方,所以编译器在编译时还无法通知类中到底发生没产生有方法的兑现。当对象收取及无法解读的信息后,就见面启动
    “ 消息转发 message forwarding
    机制,而我辈尽管应经由此过程告诉对象应什么处理未知消息。而当您无告知对象应当怎样处理未知消息时,对象就会启动
    消息转发 机制。最后就是见面雷同薄薄的用信息转发给 NSObject
    的默认实现。如下表示:

    // 这就是 NSObject 对消息转发的默认实现。
    // 消息的接收者类型是 __NSCFNumber ,但是他并无法理解名为 lowercaseString 的选择子,就会抛出异常
    /*
      出现这种情况并不奇怪。因为 __NSCFNumber 实际上是 NSNumber 为了实现 “无缝桥接” 而使用的 内部类
      配置 NSNumber 对象时也会一并创建此对象。
      在本例中,消息转发过程以程序崩溃结束。但是实际上,我们在编写自己的类时,可以在转发过程中设置挂钩,就可以当程序执行 消息转发 时,处理所转发的消息,避免程序的崩溃。
    **/
    2017-12-01 11:30:19.942493+0800 NEUer[17853:2011205] -[__NSCFNumber lowercaseString:]: unrecognized selector sent to instance 0x87
    2017-12-01 11:30:19.964307+0800 NEUer[17853:2011205] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFNumber lowercaseString:]: unrecognized selector sent to instance 0x87'
    
  • 消息转发的进程 => 分为有限要命级

    • 率先不行阶段动态方法解析 : 首先,询问 接收者 receiver
      所属的切近, 能否动态增长方法来处理这个
      未知选择子 unknown selector , 这个历程叫做 动态方法分析

      • 目标当接到无法解读的信时,首先调用+ (BOOL)resolveInstanceMethod:(SEL)selector
        这个法的参数就是 未知选择子。返回值为 BOOL 表示是否在
        Runtime
        新增一个实例方法来拍卖者选择子。使用这方式的前提是:这个
        未知选择子 的相干落实代码都勾勒好了,只相当正在运行的时刻以
        Runtime 时期动态插入类中即可。
    • 其次异常级 完整的消息转发机制 full forwarding mechanism : 当
      接收者 无法解析这个 未知选择子 时, 询问 接收者 是否持有
      备援的接收者 replacement receiver ,又分为两聊等

      • 先是不怎么等:如果有,则 接收者
        就把信转发给她,消息转发了。

        • 顿时同样有点等的长河如下:当前 接收者 还起一致破机会来处理
          未知选择子。那就算是行使
          -(id)forwardingTargetForSelector:(SEL)selctor;
          这个方法的参数表示 未知选择子。 如果手上接收者 能够找到
          备援对象 则好将 备援对象 返回,如果找不顶, 就回 nil
          。 通过这种方案,我们好行使 __“组合” __ 来模拟
          基本上再次继承 的少数特点。在一个对象的里边,
          可能还有一样多样其他的靶子,而该 对象
          可以由此这个点子使得它的中的某可以处理这信息的靶子回来。在外边看来,就好像这个目标好处理了之未知方法一致。
        • 待专注的是:在此阶段 接收者
          没有权利去操作就等同步所转发的信,他只能完全交给
          备援的接收者 来处理这信息。
      • 其次小等:如果无 备援的接收者, 则 启动
        完整的音讯转发机制 。 Runtime
        系统会把与信有关的总体细节还打包到 NSInvocation 对象被,
        再受接收者最后之同等糟机会,让他灵机一动解决目前尚不处理的此消息。其中,这个
        NSInvocation 对象涵盖 选择子, 目标, 参数。 在触发
        NSInvocation 对象时, “消息转发系统”
        将亲自出吗,把消息转发给目标靶(也就是是目标接收者)。- (void)forwardInvocation:(NSInvocation *)invocation;

        • 当这个办法简单易行的落实:例如只是改变接收者目标,那么她的效果就算会见跟用
          备援的收信人 效果同样。
        • 以此方式的比较有含义的贯彻方式吗:在触及消息之前,先以
          invocation
          中改信息的内容,不如多另外一个参数,或切换选择子。
        • 当在实现之措施的时段,如果发小某个调用不应有由本类处理,则要调用超类的同名方法。这样的话,继承体系受到每个接近都发出会处理是调用请求,直到到
          NSObject 类。 如果最终调用了 NSObject
          类的计,该方法会接着调用 doesNotRecognizeSelector
          来抛来大。如果遗弃来了之好,就表明以整个消息转发的良过程中,没有丁能处理此消息!就会使程序崩溃。

  • 收信人
    在每个步骤都产生机遇处理消息,步骤越往后,处理是消息的代价就是更是充分。最好会当率先步就是完成,这样
    Runtime 系统便将以此办法缓存起来了。回顾 第11条 说道:”当 OC
    中有对象调用某个函数实际上就让该对象传递消息,这是一个使用
    动态绑定 的进程。在此历程遭到以 objc_msgSend
    这个函数,该函数会依据接收者(receiver) 与
    选择子(selector)的种来调用适当的章程。为了好这个操作:它要首先以斯看似的
    list of method
    中找寻相应的计,然后如找到了这方式,继而找到她的落实,然后再度把这法子放到
    fast map 中。” 这样就算贯彻了 Runtime
    时期的复苏存。在此之后,如果是近乎更接受了此选择子,那么从不管需启动信息转发机制了。

第13长长的 用“方法调配技术”调试“黑盒方法”

  • 咱俩还理解我们可在 Runtime
    时期,动态选择要调用的措施。实际上我们吧可以当 Runtime
    时期,动态的管让定选择子名称 (SEL)
    的不二法门开展更改。这个作用一旦我们可于不以持续就可以直接改动这类似本身的成效。这样一来,新成效就得以斯近乎吃的备实例都拿走利用。这个效果就吃做
    方法调配 method swizzling

  • 恍如的方列表会把选择子名称映射到有关办法的贯彻上。使得“动态消息派发系统”可以就此找到相应调用的计。这种办法为函数指针的形式来代表。这种指针就为做
    IMP 原型如下 id (*IMP)(id, SEL)

  • 原始方法发明底布局

    yuanshifangfabiao.png

  • 当使用 method swizzling
    改变内存中选择子与方式实现之投射后,就变成了如此

    newfangfabiao.png

这时候,对于这个看似的持有实例,这点儿单主意的兑现都更改了。

  • // 交换方法实现的章程。

    void method_exchangeImplementation(Method 1, Method 2);
    
    // 获取方法的实现。
    Method class_getInstanceMethod(Class aClass, SEL aSelector);
    
  • 当实际使用被,这样交换两单艺术没什么实际用。method swizzling
    主要的来意在于:可以在匪亮堂原来方法的里具体贯彻之状下,为原的方式上加新的附加功能。示例如下:

    • 初点子好长到一个 NSString 的一个 Category 中:

      @interface NSString (SLYMyAdditions)
      - (NSString *)sly_myLowerCaseString;
      @end
      
      @implementation NSString (SLYMyAdditions)
      - (NSString *)sly_myLowerCaseString {
       /*
        在这里调用了 sly_myLowerCaseString 这个方法, 一眼看上去好像是一个递归的循环调用,使这个方法永远都不会结束,但是实际上,这个方法在 Runtime 时期就已经绑定到 NSString 本身的 lowercaseString 方法上去了。所以这个分类的具体目的就是在实现原本 lowercaseString 功能的同时,打印一些额外信息。在我们的实际开发中,这也正是 method swizzling 的主要用途。
        **/
          NSString *lowercase = [self sly_myLowerCaseString];
          NSLog(@"%@ --> %@", self, lowercase);
          return lowercase;
      } 
      @end
      
    • 具体的置换方法代码如下:(一般的话,method swizzling 应该在
      load 方法吃实行实际的置换)

      // 具体交换两个方法实现的范例:
      Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
      Method swappedMethod = class_getInstanceMethod([NSString class], @selector(sly_myLowerCaseString));
      method_exchangeImplementation(originalMethod, swappedMethod);
      // 从现在起,这两个方法的实现与其方法名就互换了。
      
  • 内需专注的凡,这个作用虽然强大,但是非克滥用。一般的话还是当付出调试程序时才得以
    Runtime 时期修改章程实现。

第14条:理解“类对象”的用意

  • 第一来了解 OC 对象的实质:所有 OC
    对象的实例都是赖为某块内存数据的指针。但是对于通用的对象类型 id
    由于其本人都是指针了,所以我们可以免加 * 。

  • 讲述 OC 对象所用之数据结构定义在 Runtime 的峰文件里, id
    的概念如下:

    • /*

          每个对象结构体的首个成员是 Class 类的变量,该变量定义了对象所属的类,通常称为 is a 指针。
       **/
      typedef struct objc_object {
          Class isa;
      } *id;
      
    • Class 类的落实如下:

      typedef struct objc_class *Class;
      struct objc_class {
        Class isa; // 每个 Class 对象中也定义了一个 is a 指针,这说明 Class 本身也是一个 OC 对象,这个 isa 指针指向的是类对象所属的类型,是另外一个类,叫做 metaclass, 用来表述类对象所需要具备的元数据。“类方法”就定义于此处,因为这些方法可以理解成类对象的实例方法。每个类仅有一个“类对象”,而每个“类对象”仅有一个与之相关的“元类”。
        Class super_class; // 指向 Class 的超类
        const char *name; // 该类对象的名称
        long version;
        long info;
        long instance_size;
        struct objc_ivar_list *ivars; // 该类对象的变量列表
        struct objc_method_list **methodLists; 
        struct objc_cache *cache;
        struct objc_protpcol_list *protocols;
      }
      
  • 倘来个名为也SomeClass的子类从NSObject中延续而来,则该持续体系使图

继承图.png

  • 第12漫长则讲述了消息转发的规律:如果类似无法及时响应某个选择子,那么就会启动信息转发流程。然而,消息的收信人究竟是何物?是目标自我为?运行期系统如何理解有对象的品种为?对象类型并非以编译期就绑定好了,而是一旦当运行期查找。而且,还时有发生个独特之类叫做id,它能够代替任意的Objective-C对象类型。一般情形下,应该指明消息接收者的有血有肉项目,这样的话,如果向该发送了无法解读的信,那么编译器就会发生警告信息。而项目为id的对象则不然,编译器假定它亦可响应所有信息。

  • 编译器无法确定有型对象到底能解读多少种选择子,因为运行期还而为里动态新增。然而,即便使用了动态新增技术,编译器也以为该力所能及以某个头文件被找到办法原型的概念,据这而探听完的“方法签名”(method
    signature),并生成派发消息所需要的正确代码。“在运行期检视对象类型”这同样操作为称“类型信息查询”(introspection,“内省”),这个强大使中的特色内置于Foundation框架的NSObject协议里,凡是由国有根类(common
    root
    class,即NSObject与NSProxy)继承而来之目标都如遵照从此协议。在程序中永不一直比较对象所属的类,明智之做法是调用“类型信息查询方式”。

  • isMemberOfClass: 能够看清有目标是否也某某特定类的实例,而
    isKindOfClass: 则能够看清发生目标是否为某类或者其派生类的实例.

    // 例如:
    NSMutableDictionary *dict = [NSMutableDictionary new];  
    [dict isMemberOfClass:[NSDictionary class]]; ///< NO 
    [dict isMemberOfClass:[NSMutableDictionary class]]; ///< YES 
    [dict isKindOfClass:[NSDictionary class]]; ///< YES 
    [dict isKindOfClass:[NSArray class]]; ///< NO 
    // 像这样的类型信息查询方法使用isa指针获取对象所属的类,然后通过super_class指针在继承体系中游走。由于对象是动态的,所以此特性显得极为重要。
    
  • 切莫得以一直下有限独对象是不是等于来比

    // 例如:
    id object = /* ... */;  
    if ([object class] == [SLYSomeClass class]) {  
        // 'object' is an instance of EOCSomeClass  
    } 
    

    以消息可能实施了信转发机制,所以无得以如此针对性目标的切近进行比。比方说,某个对象可能会见将那吸收的具有选择子都转发让另外一个对象。这样的对象叫做“代理”(proxy),此种对象都以NSProxy为根类。而如利用了
    isKindOfClass: 这个主意开展比较,则可比,因为 isKindOfClass:
    这样的类型信息查询方式,那么代理对象就是见面把立即漫长信息转给“接受代理的目标”(proxied
    object)。也就是说,这条消息之返回值与直接在经受代理的目标方面查询其色所得的结果一致。也便足以收获正确的结果。