OC52个有效方法·


1、 OC使用了“消息结构”(message structure)而非“函数调用” (function calling) 。 由smalltalk 演化而来。

关键在于:
(1)消息结构的语言,其运行时所应用执行的代码由运行环境来决定的;无论是否多态,总是在运行时才查找所要执行的方法。 实际上,编译器甚至不关心接收消息的对象是何种类型。 接收消息的对象问题也要在运行时处理, 这个过程 ——“动态绑定”

(2)函数调用语言, 则是由编译器决定的。 在C++那个的, 函数是多态的,那么在运行时就按照“虚方法表[virtual table]” 来查找到底应该执行哪个函数实现。

OC 为C语言添加了面面向对象特新,其超集。 OC使用了动态绑定的消息结构,也就是说, 在运行时才会检查对象类型。 接收一消息之后,究竟应执行何种代码, 由运行期环境而非编译器俩决定。


2、 在类的头文件中尽量少引入其他头文件
(1)使用@class 类名; // 向前声明(forward declaring) , 在实现文件中才引入头文件。
(2) 解决了两个类相互引用的问题。 使用#import 而非#include 指令虽然不会导致死循环,但却意味着两个类里有一个无法被正确编译。
(3) 如果声明你写的类遵循某个协议(prot0c0l) ,那么该协议必须有完整的定义。 如代理, 尽量把“该类遵循的某协议”的这条声明移至“class-continuation 分类” 中。 如果不行的话,就把协议单独放在一个头文件中,然后将其引入。


3、多用字面量语法,少用与之等价的方法
(1) 应该使用字面量语法来创建字符串、数值、数组、字典。 与创建此类对象的常规方法相比, 这么做更多见面扼要
(2)应该通过取下标操作来访问数组下标或字典中的键所对应的元素
(3)字面量语法创建数据或字典时,若值位nil, 则会抛出异常,因此,务必确保值里不含有nil。


4、多用类型变量,少用#define 预处理指令
(1)不要用预处理指令定义常量, 因为不含有类型信息, 编译器只是在编译钱执行和查找与替换操作。 如果有人重新定义了常量值, 编译器也不会产生警告信息,这将导致程序中的常量不一致。
(2)在实现实现文件中使用static const 来定义“只在编译单元内可见的常量”(translation-unit-specific constant) 。 由于此类常量不再全局符号表中, 所以无须在其名称中加入前缀。
(3)在头文件中使用extern 声明全局常量, 并在相关实现文件中定义其值。 这种常量要出现在全局符号表中, 所以,其名称应该加以区隔, 通常用与之相关的类名做前缀。


5、 用枚举表示状态、选项、状态码
(1)应该用枚举来表示状态机的状态、 传递给方法的选项以及状态码等值, 给这些值起个易懂的名字
(2)如果把传递给某个方法额选项为枚举类型,而多个选项又可能同时使用,那么就讲各选项定义为2的幂,以便于通过按位或操作将其组合起来。
(3)用NS_ENUM 与NS_OPTIONS 宏来定义枚举类型,并指明其底层数据类型。 这样做可以确保是用开发者所选的底层数据类型实现出来的, 而不会采用编译器所选的类型。
(4)在处理枚举类型的switch 语句中不要实现default分支。 这样的话,加入新枚举之后,,编译器就会提示开发者:switch 语句并未处理所有枚举。


对象、消息、运行期


6、理解“属性”这一概念


(1)可以用property愈发来定义对象中所封装的数据
(2)通过“特质”来指定存储数据所需要的正确语义
(3)在设置属性所对应的实例变量时, 一定要遵从该属性所声明的语义。
(4)iOS程序开发中使用nonatomic属性,以为atomic 属性会严重影响性能。

实例变量一般通过“存取方法”【access method】来访问。 “属性” 这个在编译器中自动编写与属性相关的存取方法。

定义实例变量的作用域

这种写法的问题: 对象布局在编译期(compile time)就已经固定了, 只要碰到访问_firstName变量的代码,编译器就把其替换为“偏移量(offset)”, 这个偏移量是“硬编码(hardcode)”, 表示该变量距离存放对象的内存区域的起始地址有多远。 如果增加一个实力变量_dateOfBirth。 原来的变量的偏移量发生了改变。—— 编译器计算出来的偏移量,, 那么修改类定义之后必须重新编译,否则就会发生错误。
1) eg: 某个代码库中的代码使用了一份旧的类定义。 如果和其相连的代码使用了新的类定义,那么运行就会出现不兼容的现象(incompatibility)。 OC 解决方法: 吧实例变量当做一种存储偏移量所用的“特殊变量”(special variable) , 交由“类对象”【class object】保管。 偏移量会在运行期查找,如果类定义变量,那么存储的偏移量也就变了, 这样的话, 无论何时访问实例变量, 总能使用正确的偏移量。 甚至可以在运行期向类增加实例变量。 —— 这就是稳固的“应用程序二进制接口”【application binary Interface, ABI】。 有了稳固到的ABI, 我们就可以在“class-continuation分类”或实现文件中定义实例变量了。 所以, 不一定要在接口中把全部实例变量都声明好, 可以把将某些变量从接口的public区段里移走,以便于保护与类实现有关的内部信息。
2)尽量不要直接访问实例变量, 而应该通过存取方法来做。虽说属性最终还是通过实例变量来实现,但它却提供了一种简洁的抽象机制。 @property 自动创建出存储方法。 : 编译器会自动写出一套存取方法,用来访问给定类型中具有给定名称的变量。
这里面的get、set方法和使用点语法没有什么区别。
属性 对应的变量, 在属性前面加上下划线。
@synthesize 属性=变量名
如果不想编译器自动实现, 使用@dynamic关键字。 它会告诉编译器: 不要自动创建实现属性所用的实例变量, 也不要为其创建存储方法。

属性特质: 原子性、读写权限、内存管理语义、方法名

1)原子性
默认,由于编译器所合成的方法会通过锁定机制确保其原子性。 如果属性几倍nonatomic 特质,则不使用同步锁。

2) 读/写权限
1)readwrite 读写 ,拥有setter和getter方法
2)readonly 只读, 只有getter方法

3) 内存管理语义

属性用于封装数据,而数据则要有"具体的所有权语义"

assign: "设置方法", 只会执行针对“纯量类型”的简单赋值操作。eg: NSInteger , CGFloat
strong: 定义了一种“拥有关系”(owning relationship) 。 这种属性设置新值时, 设置方法会先保留新值, 并释放旧值, 然后再将新值设置上去。
weak: 定义了"非拥有关系"(nonowning relationship) 。 设置新值时, 设置方法既不保留新值, 也不释放旧值。 类似assign, 属性所指的对象遭到摧毁时, 属性值会清空(nil out)
unsafe_unretained: (与assign相同), 当时它适用于“对象类型”, 表示一种非拥有关系,当目标遭到摧毁时,属性值不会自动清空。 这一点与weak有区别。
copy: 与strong类似, 设置方法并不会保存新值,而是将其“拷贝”。 当属性类型为NSString 时候, 经常用此特质保存其封装性, 因为传递给设置方法的新值有可能指向一个NSMutableString类的实例。 这个类是NSString子类。 表示一种可以修改其他值的字符串, 此时若是不拷贝字符串, 那么设置完属性之后, 字符串的值就可能会在对象不知情的情况下遭人更改。 所以这个时候,需要拷贝一份出来为不可变的字符串。

方法名: getter= <name> 和setter= <name> setter方法,注意对有关的修饰词进行操作,比如:copy就要实现对应的拷贝。

在init 和dealloc 方法中不应该调用存取方法。

readonly: 这个字段不会创建对应的setter方法。

atomic 和nonatomic 区别是什么呢?
atomic: 获取方法会通过锁定机制俩确保其操作的原子性。
nonatomic: 非原子性

历史原因:
在iOS中使用同步锁的开销较大, 这回带来性能问题。 一般情况下不要求舒心的线程安全, 若是要“线程安全” 的操作, 还需要采用更为深层的锁定机制才行。
eg: 一个线程在连续多次读取某属性的过程中有别的线程在同时改写该值,那么即便将属性声明为atomic, 也还是会读到不同的属性值。 因此, 开发iOS程序时一般会使用nonatmoic属性。


第7条: 在对象内部尽量直接访问实例变量

在外部访问实例变量的时候使用属性来做, 在对象内部访问实例变量尽量访问实例变量。

PS 小结:

  1. 在对象内部读取数据时, 应该直接通过实例变量来读, 而写入数据时,则应通过属性来写
  2. 在初始化以及dealloc方法中, 总是应该直接通过实例变量来读写数据
  3. 有时会使用惰性初始化技术配置某粉数据,这种情况下, 需要通过属性来读取数据。
  1. 由于不经过OC的“方法派发”(method dispatch) 步骤,所以直接访问实例变量的速度当然比较快。 这种情况下,编译器所生成的代码会直接访问保存对象实例变量的那块内存。
  2. 直接访问实例变量时,不会掉员工其“设置方法”, 这就绕过了为相关属性所定义的“内存管理寓意”。eg: 如果在ARC下直接访问一个声明为copy的属性,那么并不会拷贝该属性,只会保留新值并释放旧值。
  3. 如果直接访问实实例变量,那么不会触发“键值观测[KVO]”通知。
  4. 通过属性来访问有助于排查与之相关的错误,因为可以给“获取方法”和“/或”"设置方法"中新增“断点”, 监控该属性的调用者以及访问时机。

第8条: 理解“对象等同性” 这一概念

小结:

  1. 若想检测对象的等同性, 请提供“isequal” 与hash方法
  2. 相同的对象必须具有相同的hash码,但是两个hash下同的对象未必相同
  3. 不要盲目地逐个检测每条属性,而是应该依照具体需求来指定检测方案
  4. 编写hash方法, 应该使计算速度快而hash冲突率低的算法。

== 并不一定是我们想要的, 以为这个是比较两个指针本身。

等同性(equality):

  1. isequal 来判断两个对象的等同性, isequalToString 判断两个字符串的等同性
  2. 一般来说, 两个类型不同的对象总是不相等的, 某些对象提供了特殊的“等同性方法” ; 如果已经知道两个受测对象都属于同一个类,那么久可以使用这种方法。
关键方法;
- (BOOL)isEqual:(id)object; 判断等同性的方法
@property (readonly) NSUInteger hash;  hash值
也许我们会这样去判断这些属性

QR : 不过有的时候,我们可以实现一个实例和其子类实例相等。 在集成体系中判断等同性时, 经常要遭遇此类问题。 所以,实现isEqual要考虑这种情况。

判断等同性: 若是两个对象相等, 则其hash也相等,但是hash相同,未必是同一个对象。

hash算法: 如果每个元素都去检查,那么如果有上千个元素的话,那么就会遍历上千次数。

- (NSUInteger)hash {
    return 1377;
}

这样写,会产生性能问题, 因为collection在检索hash索引表时, 会用对象的hash作索引。 加入某个collection 是用set实现的,那么set可能会根据hash吧对象分装到不同数据中。 在向set中添加新对象时, 要根据其hash找到与之相关的那个数组, 一次检查其中各个元素, 看数组中已有的对象是否和将添加的新对象相等。 如果相等,那就说明添加的对象已经在set里面了。 由此可知, 如果令每个对象都返回相同的hash, 那么set中已有多个对象的情况下, 若是继续向其中添加对象, 则需将多个对象全部扫描一遍。

第二种方法:

- (NSUInteger)hash {
    NSString *stringToHash = [NSString stringWithFormat:@"%@_%@_%zd",_firstName, _lastName, _age];
    return [stringToHash hash];
}

这样需要额外创建字符串的开销, 所以比返回单一值要慢。 把对象添加到collection中时, 就会产生性能问题。

第三种方法

- (NSUInteger)hash {
    return [_firstName hash] ^ [_lastName hash] ^ _age;
}

能生成的hash至少在一定范围内, 而不会过于频繁重复。

特定类所具有的 等同性判定方法

- (BOOL)isEqualToArray:(NSArray<ObjectType> *)otherArray;
- (BOOL)isEqualToDictionary:(NSDictionary<KeyType, ObjectType> *)otherDictionary;

有时候需要自定义等同性判断:

- (BOOL)isEqualToPerson:(XNPerson *)otherPerson {
    if (self == otherPerson) return YES;
    if (![_firstName isEqualToString:otherPerson.firstName]) return NO;
    if (![_lastName isEqualToString:otherPerson.lastName]) return NO;
    if (_age != otherPerson.age) return NO;
    return YES;
}

- (BOOL)isEqual:(id)object {
    if ([self class] == [object class]) {
        return [self isEqualToPerson:object];
    }
    return [super isEqual:object];
}

等同性判定的执行深度

并不一定,我们要判断完所有的属性, 也许只是判断一些重要的属性。

容器可变类的等同性

容易放入可变类对西安共的时候; 把某个对象放入collection之后, 就不应再改变其hash了。 确保hash值不是根据可变部分计算出来的。

判断等同性,为什么需要hash, 主要是用在集合上


第9条 : 以“类族模式” 隐藏实现细节

小结:

  1. 类族模式可以吧实现细节隐藏在一套简单的公共能接口后面
  2. 系统框架中经常使用类族
  3. 从类族的公共抽象积累中继承子类时要当心,若是开发文旦个,则应该首先阅读。

OC中普遍使用了这个模式, 比如: UIButton , 里面只需要传入一个创建的类型。
不用考虑里面绘制的方式。

类族中,我们经常使用到简单个的工厂方法创建 。

OC里面有很多类族的累; NSArray 和NSMutalbeArray 是两个不同的抽象基类。

需要遵守几个规则:
1) 子类应该继承自类族中的抽象基类
2)子类应该定义自己的数据存储方式
3)子类应当覆盖写超类文档中之名需要复习的方法


第10条: 在既有类中使用关联对象存放自定义数据

1》 可以通过“关联对象”机制来把两个对象连接起来
2》定义关联对象时可 指定内存管理的语义, 用来模仿定义属性时所采用的“拥有关系”与“非拥有关系”
3》只有在其他做法不可行时下选用关联对象,因为这种做法通常会引入难以查找的bug

通常: 我们再不修改类的时候,添加新的属性的时候, 一般使用继承的方式

OC —— 关联对象,不需要进行继承父类

1、关联策略 、 assign 、retain ,copy, retain_notomic, copy_nonatomic



11、理解objc_msgSend 的作用


1》 消息由接受者、选择子以及参数构成。 给某对象“发送消息”(invoke a message )也就是相当于在给对象上调用方法
2》发给某对象的全部消息都要由“动态消息派发系统”来处理, 该系统查处对应的方法,并执行其代码

OC : 传递消息: 名称 + 选择子

OC是C语言的超集合。
C语言使用了静态绑定,也就是编译的时候就确定了调用

//  静态绑定

#import <stdio.h>
void printHello() {
    printf(@"Hello word :\n");
}

void printGoodbye() {
    printf(@"good bye \n");
}

void doTheThing(int type) {
    if (type ==0) {
         printHello();
    }
    else {
        printGoodbye();
    }
    func();
    return ;
}


// 动态绑定
#import <stdio.h>
void printHello() {
    printf(@"Hello word :\n");
}

void printGoodbye() {
    printf(@"good bye \n");
}

void doTheThing(int type) {
    void (*func)();
    if (type ==0) {
        func = printHello();
    }
    else {
        func = printGoodbye();
    }
    func();
    return ;
}
因为所要调用的函数知道运行时期才能够确定。 
这里只有一个函数调用指令,不过待调用的函数地址无法硬编码在指令中,而是哟啊在运行期就读取出来。 

OC里面,底层都是C函数,但是里面有运行时机制,也就是在在运行时候再确定调用的是哪个地址。

void objc_msgSend(id self , SEL cmd, .... )

objc_msgSend 函数会依据接受者与选择子的类型来调用适当的方法,为了完成此操作,该方法需要在接受者属性的类中搜寻其“方法列表”。 如果能够重大搜与选择器的名称相符的方法,就跳转到实现代码,如果没有找到, 就沿着继承体系继续向上查找,等找到合适的方法之后在跳转。 如果最终没有找到符合的方法, 那就执行“消息转发”操作。

这个过程似乎很长, 但是做了优化:
objc_msgSend 会将匹配结果缓存在“快速映射表”【fast map】 里面, 每个类都有这样一块缓存, 若是稍后还向该类发送与选择子相同的消息, 那么执行起来就很快了。

上面的过程是部分消息的调用过程(基本)
其他边界情况需要交给OC运行环境中的另一些函数来处理。
1》objc_msgSend_stret 。如果待发送的消息要返回结构体,可以交给此函数处理。
只有当CPU的寄存器能欧股容纳得下消息返回类型时, 这个函数才能够处理此消息。 若是返回值无法容纳与CPU 寄存器中,那么就由另一个函数执行派发。 此时,那个函数会通过分配在栈中的某个变量来处理消息所返回的结构体。
2》objc_msgSend_fpret 。 如果消息返回的是浮点数,那么久交给此函数处理。 因为某些架构的cpu调用函数时, 需要对“浮点寄存器”做特殊处理,这个时候,我们通常所使用的objc_msgSend 这种情况下并不合适。
3》 objc_msgSendSuper 。 给超类发送消息。

刚才说的找到方法就会跳转过去,因为OC对象的每个方法都可以视为简单的C函数, 原型:
<return_type> Class_selector(id self, SEL _cmd,...)
me这里使用了“尾调用优化”技术,令跳转方法实现编的简单。

如果某个函数最后一项操作调用另外一个函数,那么就可以用“尾调用优化”技术。 编译器会生成跳转到另一个函数所需要的指令码, 而且不会向调用堆栈中推入新的“栈帧”。

尾调用优化


12、 理解消息转发机制

1》 若对象无法响应某个选择子,则进入消息转发流程
2》通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中
3》对象可以把其无法解读的某些选择子转交给其他对象来处理
4》经过上述两部之后,如果还是没有办法处理选择子,那么就启动完整的消息转发机制。

三个过程对应着三个阶段,越是到后面,代价越大

消息转发分为两大阶段:
1)动态方法解析: 征询接受者,所属的类,是否能够动态添加方法,以处理当前这个未知的选择子。
2)完整的消息转发机制:第一步骤没有成功, 运行期系统会请求接受者以其他手段来处理与消息相关的方法调用。
细分为两步:
1》请接受者看看有没有其他对象能处理这条消息。若是有,就将这个消息转为这个对象
2》若是没有,启动完整的消息转发机制,运行期系统会把与消息有关的全部细节封装到NSInvocation对象中,再给接受者最后一次机会,令其设法解决当前还未处理的这条消息。

动态方法解析
  • (BOOL)resolveInstanceMethod:(SEL)sel
    返回值表示是否新增实例方法处理次选择子: YES:是


    直接就在这个类里面实现了关联
快速转发阶段[备援接受者 ]

当前接受者还有第二次机会能处理未知的选择自, 判断当前类是否能够把这台哦消息转给其他接受者处理。

  • (id)forwardingTargetForSelector:(SEL)selector
    sel 表示未知的选择子,若是当前接受者能够知道找到备援对象,则将其返回,若是找不到,就返回nil。

通过次方案,我们可以用“组合”来模拟出多重继承的某些特性。 在一个对象内部,可能还有一系列其他对象,该对象可经由此方法将能够处理某些选择子的相关内部对象返回, 这样在外界看来,好像是该对象亲自处理这些消息一样。

NOTE: 我们无法操作经由这一步所转发的消息,若是想在发送给备援接受者之前先修改消息内容,那就得通过完整的消息转发机制来做了。

完整的消息转发
  • (void)forwardInvocation:(NSInvocation *)invocation
    将需要的封装到invocation里面,然后转发到目标对象。
  • (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector 同时也需要实现这个方法前面,用于上面这个方法的调用。

这个方法可以实现得很简单: 只需要改变调用目标, 使消息在新目标上得以调用即可。
然而, 这样实现出来的方法与 快速转发方案的方法等效, 所以很少有人采用这么简单的实现方式。 比较有用的实现方式为:在触发消息前面,先以某种方式改变消息内容,比如:追加另一个参数,或是改选择子,等等。

实现此方法时,若发现某调用操作不应该由本来处理, 则需要调用超累同名方法。这样,继承体系中的每个父类都有机会调用这个请求。


13 、 用“方法调配技术” 调试 “黑盒方法”

1》在运行期, 可以向类中新增或替换选择器对应的方法实现
2》使用另一份实现来替换原有的方法实现,这道工序叫做“方法调配”,开发者常用此技术向原有实现中添加新功能
3》一般来说,只有调试程序的时候才需要在运行期修改方法实现。

方法交换: method swizzling
KVO的实现这个过程。


14、理解 “类对象”的用意

1》每个实例都有一个指向Class对象的指针,用以表明其类型,而这些Class对象则构成了类的继承体系
2》如果对象类型无法在编译器确定,那么就应该使用类型信息查询方法来探知
3》尽量使用类型信息查询方法来确定对象类型, 而不要直接比较类对象,因为某些对象可能实现了消息转发功能。

详细的再继续细看里面的内容


15 、 用前缀避免命名空间冲突

OC里面没整体就是一个命名空间,所以,我们避免文件冲突,我们尽量给文件加了一个前缀。
__


16 、提供“全能初始化方法”

1》 在类中提供一个全能初始化方法, 并于文档中指明。 其他初始化方法均调用这个方法
2》若是全能方法和超类不同,应该复写方法
3》如果超类的初始化方法不适用于子类,那么应该覆盖这个超类方法,并在其中抛出异常。



17、实现description方法

1》 实现description 方法犯规一个有意义的字符串, 用来描述该实例
2》若想调用时打印出更详尽的对象信息,则实现debugdescription方法。



18 、尽可能使用不可变对象

1》 尽量创建不可变对象
2》若某属性仅可用于内部修改,则在"class-continuation分类"中将其由readonly属性扩展为readwrite属性
3》不啊哟吧可变的collection作为属性公开,而应提供相关方法,一次修改对象中的可变collection。



19、使用清晰而协调的命名方式

1》 起名时候遵从标准的OC命名规范, 这样穿件出来的接口更加容易为开发者所理解、
2》方法名要言简意赅,从左到右读起来像个日常的雨中的橘子才好
3》方法名里不要使用缩略后的类型名称
4》给方法起名时的第一要务就是确保其风格与你自己的diamante或索要集成的额框架相符。



20、 为私有方法家前缀

1》 给私有方法加入前缀,这样可以很容易将其公共方法分开
2》不要单用一个下划线做私有方法的前缀,因为这种做法预留给苹果公司用的



21 、理解OC错误模型

1》 只要发生了可使整个应用程序崩溃严重错误时,才应该使用异常
2》在错误不那么严重的情况下,可以指派“委托方法”来处理错误,也可以把错误信息放在NSError对象里, 经由“输出参数”返回给调用者。

主要理解NSError类就好



22 、理解NSCopying协议

1》若想令自己缩写的对象具有拷贝功能,则需实现NSCopying协议
2》如果自定义的对象分为可变版本与不可变版本,那么需要同时实现NSCopying 与NSMutableCopying
2》复制对象时需要决定采用浅拷贝还是深拷贝,一般情况下应该尽量执行浅拷贝
3》如果你缩写的对象需要深拷贝,那么需要考虑一个专门执行深拷贝的方法。

@protocol NSCopying
- (id)copyWithZone:(nullable NSZone *)zone;
@end

@protocol NSMutableCopying
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
@end

为什么会出现NSZone呢? 因为以前开发程序时,会据此把内存分成不同的“区”, 而对象会创建在某个区里面。 现在不用了,每个程序只有一个区:“默认区”。 所以,尽管实现这个方法,但是不必要担心其中的zone参数。

copy 方法由NSObject实现,该方法只是以“默认区”为参数调用copyWithZone。 我们老是想腹泻copy,其实真正需要实现的确实copyWithZone方法。

- (id)copyWithZone:(NSZone *)zone {
    KMBookComicInfo *copy = [KMBookComicInfo allocWithZone:zone];
    if (copy) {
//        。。。。 写有关的内容
    }
    return copy;
}



协议与分类


23、 通过委托与数据源协议进行对象间通信

1》 委托模式为对象提供了一套接口, 使其可由此将相关事件告知其他对象。
2》将委托对象应该支持的接口定义成协议,在协议中把可能需要处理的事件定义成方法
3》当某对象需要从另外一个对象中获取数据时,可以使用委托模式。 这样情境下,该模式亦称为“数据源协议”。
4》若有必要, 可实现含有位段的结构体,将委托对象是否能欧股响应吸纳骨干协议方法这一信息缓存至其中。

参考代理模式


24 将类的实现代码分散到便于管理的数个分类之中

1》使用分类机制吧类的实现代码划分成易于管理的小块
2》将应该视为“私有”的方法归入名叫做Private的分类中, 以隐藏实现细节。

将有关的代码精心划分为小的区块, 然后间隔业务块。


25、 总是为第三方类的分类名称加前缀

1》向第三方类中添加分类时,总应给其名称加上你专用的前缀
2》向第三方类中添加分类时,总应给````其中的方法名```加上你专用的前缀。


26、不要在分类中声明属性

1》把封装数据锁用的全部属性都定义在主接口里
2》在“class-continuation分类”之外的其他分类中,可以定义存取方法,但尽量不要定义属性。

除了“class-continuation分类”之外,其他分类无法向类中新增实例变量。 因此, 它们无法吧实现属性锁需的实例变量合成出来。

定义属性, 是可行,但是不太理想。


27、使用“class-continuation 分类”隐藏实现细节

1》通过“class-continuation 分类”向类中新增实例变量
2》如果某属性在主接口中声明为“只读”,而类的内部又要用设置方法修改次属性,那么久在“class-continuation 分离”中将其扩展为“可读写”
3》把私有方法的原型声明在“class-coninuation分类”里面
4》若想使类锁遵循的协议部位人所知,则可于“class-continuation 分类”中声明。

28 、通过协议提供匿名对象

1》 协议可在某种程度上提供匿名类型,具体的对象类型可以淡化成遵循某协议的id类型,协议里规定了对象所应实现的方法
2》使用匿名对象来隐藏类型名名称(或类名)
3》如果具有类型不重要,重要的是对象能够响应(定义在协议里的)特定方法,那么可使用匿名对象标识。


五、 内存管理


六、块 与 大 中枢派发


51 精简initialize 与load的实现代码

主要了解: 在父类、子类、父类分类、子类分类的调用关系

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

+ (void)load

对于加入运行期系统中的每个类以及分类来说,必定光辉调用此方法,而且近调用一次。 当博阿航类或分类的程序载入系统时, 就会执行此方法,而这通常就是指应用程序启动的时候。
如果分类和其所属的累都定义了load方法,则选调用类里的,再调用分类里的。

调用顺序: 父类 > 子类 > 分类

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

eg:

@implementation XNClassB
+ (void)load {
    NSLog(@"load clas sA");
    XNClassA *obj = [XNClassA new];
//    use obj 
}
@end

这里NSLog是没有问题的,而相关字符串也照常记录,因为Foundation框架肯定在运行load方法之前就已经载入系统了。 但是,classB的load方法里使用classA却不太安全,因为无法确定在执行classB的load方法之前,classA是不是已经加在好了。

NOTE: load 方法并不想普通的方法那样,它并不是遵从集成规则。如果某个类本身没有实现load方法,那么不管其各级超类是否实现此方法,系统都不会调用。此外,分类和其他所属的累里,都可能出现load方法。 此时两种实现代码都会调用,类的实现比分类的实现先执行。

load方法里面实现的内容尽量精简一点(eg:我们常常用在方法交换上面)

+ (void)initialize 与类相关的初始化操作





鉴于这个原因,我们initialize经常需要判断一下





  • initialize 这个类方法一般是用来初始化一些静态变量。

拓展:
init 和initialize去呗:
init是对象方法; init是类实例化的手调用一次, eg:[class new] = [[class alloc] init] , 也就是每次这个类实例化一个对象,就会调用一次init方法;
initialize 是类方法; 向该类放松消息的时候调用一次,也就是只有第一次的时候才调用。 eg: [class new] 第一次执行的时候调用, 第二次执行[class new] 就不会调用了;如果子类没有实现initialize的重写,在子类收到消息的时候,会调用两次initialize,一次是父类调用,一次是子类调用。


实例化单例

初始化一个静态变量


load 和initialize
调用时机:
initialize:在第一次初始化这个类之前会被调用,我们用它来初始化静态变量;
在创建子类的时候,子类会去调用父类的+ initialize方法,initialize方法的调用时机,当向该类发送第一个消息(一般是类消息首先调用,常见的是alloc)的时候,就会调用这个方法;

在只有初始化子类的的情况下: Son = [[Son alloc] init];
1) 如果只是子类son继承父类Father,(1)那么都重写的话,父类调用之后子类再调用(2)如果子类没有重写的话,父类调用父类的方法,然后子类调用父类的这个方法,
2)如果只有Father类有类别,而子类没有的时候,发现:父类调用自己类别的方法,然后子类调用自己的方法;
3)如果两个都是有方法的,发现: 子类调用自己的分类方法,父类再去调用自己的分类方法;
【可以看到initialize这个方法,catergory会覆盖掉原来的initialize的调用】
总结:
1】 如果有分类,就直接调用了分裂的方法不会再调用原类的方法
2】调用顺序是先调用父类的方法,再去调用子类的方法;


类别之间,和这个的先后顺序是有关系的,后面的会覆盖掉前面的;

image.png

正确使用方式;

image.png

也就是一些静态变量的方法;

http://blog.leichunfeng.com/blog/2015/05/02/objective-c-plus-load-vs-plus-initialize/

https://www.jianshu.com/p/200e57fab0de

代码解析;

load : 在加载类时候调用(应用启动),在main函数之前会加载所有的类,也就会调用这个方法。先调用原类的方法,再调用分类的方法;

调用顺序: 先调用父类的方法,再调用子类方法, 在调用子类的类别方法,在调用父类的类别方法;

调用顺序是,先调用原类的方法,再调用类别的方法 ;
PS:调用父类的方法, 调用子类的方法, 调用分类(在plist中先后顺序)【不管子类有没有重写load方法,都是会调用父类的load方法的】

image.png

分类之间的调用顺序是和在这里的先后顺序有关系的; 而原类还是从父类到子类;
主要铭记: 先调用原类再调用分类;

两者的调用: 继承之间的类别以及原来的调用顺序不会受到在plist文件的位置的影响;


image.png


所以,在调用的时候,注意这里面的plist文件里面先后顺序;

但是不要随意,根据这个来处理先后顺序,可能以后开发中有问题;
由于调用load方法时的环境很不安全,我们应该尽量减少load方法的逻辑。另一个原因是load方法是线程安全的,它内部使用了锁,所以我们应该避免线程阻塞在load方法中。

一般来说,除了Method Swizzle,别的逻辑都不应该放在load方法中实现。
如果一个类没有实现load方法,那么就不会调用它父类的load方法;【意思是子类不会去调用父类的方法,而父类还是会调用自己的load方法】

这个load方法还是需要进一步去整理的

52 : 别忘了NSTimer会保留其目标对象

1》 NSTimer对象会保留其目标,知道定时器本身是失效位置, 调用invalidate方法可令计时器失效, 另外啊,一次性的计时器在触发完成任务之后也会失效
2》反复执行任务的计时器,很容易引入保留环, 如果这种计时器的目标对象又保留了计时器本身,那肯定会导致保留环。 这种保留关系,可能是直接发生,也可能是通过对象图里的其他对象简洁发生的。
3》可以扩展NSTimer的功能, 用块来进行打破循环引用。 不过, 除非NSTimer奖励啊在公共接口提供此功能,否则必须创建分类,将相关实现代码加入其中。

iOS 中的定时器

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容