iOS - OC <<编写高质量iOS与OS X代码的多个有效方

2020-09-17  本文已影响0人  洧中苇_4187

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等等.

上一篇下一篇

猜你喜欢

热点阅读