iOS - OC <<编写高质量iOS与OS X代码的多个有效方
7.以"类簇模式"隐藏实现细节
iOS中数组有多少种
NSArray *array0 = @[@1,@2,@3];
NSArray *array1 = [[NSArray alloc]init];
NSArray *array2 = [[NSArray alloc]initWithObjects:@1, nil];
NSMutableArray *array3 = array0.mutableCopy;
NSArray *array4 = [NSArray alloc];
NSLog(@"\n%@\n%@\n%@\n%@\n%@",array0.class,array1.class,array2.class,array3.class,array4.class);
//打印结果------------------------------
2020-09-16 18:36:22.140681+0800 testProj01[58466:3176044]
__NSArrayI
__NSArray0
__NSSingleObjectArrayI
__NSArrayM
__NSPlaceholderArray
1.创建一个对象的时候,类型并不像我们表面看到的那样,像数组这种类簇比较多的类;
2.子类尽量复写超类中指明需要复写的方法.;
3.从类簇的公共抽象积累中继承子类时要当心,若有开发文档,则应首先阅读.
8.在既有类中使用关联对象存放自定义数据
//该方法以给定的键和策略为某对象设置关联对象值
objc_setHook_getImageName(objc_hook_getImageName _Nonnull newValue, objc_hook_getImageName _Nullable * _Nonnull outOldValue)
//该方法以给定的键从某对选哪个中获取相应的关联对象值
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
//该方法移除指定对象的全部关联对象
objc_removeAssociatedObjects(<#id _Nonnull object#>)
使用如下关联对象的键一般采取全局静态变量的地址作为键,这里建议采用如下这种写法,
static const char MJReplacedKeyFromPropertyNameKey = '\0';
这点我在阅读 MJExtension框架 的时候,第五点有提及.(注意:objc_getAssociatedObject
设置的object 不能为nil,否则会报BAD_ACCESS
的错;存取的值必须是对象
类型).
9.理解objc_msgSend的作用
objective-c 中方法的调用直到运行期间才能确定,这个过程叫动态绑定(dynamic binding),方法的调用就是在类的方法列表(method_list)中寻找方法,当然,在这之前会先去方法缓存中去寻找,大致流程就是,personClass_cache-->personClass_method_list--->personClass_super_cache-->personClass_super_method_list ... -->message forwarding(消息转发)
,即从当前类的缓存中寻找方法,没有就从方法列表寻找,还没有,从父类缓存寻找,父类方法别表中寻找,直到找到元类还没有(即消息无法解读),就执行消息转发流程.
1.objc_msgSend还有一些边界情况,
objc_msgSend_stret
当消息的返回为结构体时,通过这个来处理;
2.objc_msgSend_fpret
如果消息的返回为浮点数,这个函数来处理;
3.如果要交给父类,则objc_msgSendSuper
,调用到父类的情况.
如果函数最后一项操作是调用另外一个函数,那可以调用"尾调用优化",编译器会生成调转至另一函数所需的指令码,而不会向调用堆栈推入新的栈针(frame stack)
消息转发流程图10.理解"类对象"的用意
typedef struct objc_object {
Class isa;
} *id;
isa 其实 就是 is a, 如果这个对象继承自NSString,则Class is a NSString,读起来是不是更像句子,所以isa其实是告诉你,对象的类型信息
类对象本身所具有的元数据,也是类对象所属的类型,叫做元类
当我们使用如下这些方法的时候,其实是调用 super_class 指针来确定的,
[dict isMemberOfClass:EOCAutoDictionary.class];
[dict isKindOfClass:EOCAutoDictionary.class];
不建议采取classA.class == ClassB.class,来判断两个类是否相等,
因为可能出现某个类继承自NSProxy的情况,这样就不会打印
MyProxy *tmpProx = MyProxy.alloc;
if (tmpProx.class == NSProxy.class) {
NSLog(@"OCAutoDictionary.class");
}
尽量使用类型信息查询方法(isKindOfClass,isMenberOfClass)来确定对象类型,不要直接比较类对象,因为某些对象可能实现了消息转发功能.
11.提供全能初始化方法
以 NSDate 为例
- (instancetype)init;
- (instancetype)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti;
- (nullable instancetype)initWithCoder:(NSCoder *)coder
- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs;
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)secs;
- (instancetype)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;
在上面几个初始化方法中 initWithTimeIntervalSinceReferenceDate
,是全能初始化方法,也就是说其他的方法都调用该初始化方法,当底层存储数据改变时,无需改动其他初始化方法,其实很多第三方开源框架中都采取了这种设计模式.
1.在类中提供一个全能初始化方法,并于文档里指明,其他初始化方法都应该调用此方法.
2.若全能初始化方法与父类不同,则需要覆盖父类中对应方法.
3.如果父类的初始化方法不适用于子类,那么应该覆盖这个父类方法,并抛出异常.
12.实现description方法
//In header file
@interface EOCPerson : NSObject
- (instancetype)initWithFirstName:(NSString *)firstName
lastName:(NSString *)lastName;
@end
//In implementation file
#import "EOCPerson.h"
@interface EOCPerson()
@property(nonatomic,strong)NSString *firstName;
@property(nonatomic,strong)NSString *lastName;
@end
@implementation EOCPerson
- (instancetype)initWithFirstName:(NSString *)firstName
lastName:(NSString *)lastName
{
self = [super init];
if (self) {
_firstName = firstName;
_lastName = lastName;
}
return self;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"%@ --- %@",_firstName,_lastName];
}
- (NSString *)debugDescription
{
return [NSString stringWithFormat:@"<%@: %p \"%@ %@\">",self.class,self,_firstName,_lastName];
}
@end
//使用
- (void)viewDidLoad {
[super viewDidLoad];
EOCPerson * person = [EOCPerson.alloc initWithFirstName:@"Steve" lastName:@"Bob"];
NSLog(@"%@",person);
}
description
方法就是我们在NSLog打印这个对象的时候出会调用,而debugDescription
方法是我们在调试阶段控制台lldb的时候,我们po person
才会打印出来,这样有利于我们调试程序,发现线上bug等等.