基础知识是很重要的,但往往容易被忽视,就个人而言出现的问题很多都是由于自身对基础知识的理解不充分导致的。所以,我们就OC中比较容易混淆的几个概念做详细的剖析,明确知识点。------前言
一、iOS中,各类添加方法有两种:继承和类别。
a、继承
面向对象三大特性之一(封装、继承、多态),子类会继承父类所有的方法和属性。
b、类别
类别也可以称为分类,是OC的特性,可以在不改变原有类的前提下,实现对类方法的扩展。类别是不能增加属性的,但是使用runtime可以做到。我们在前面介绍runtime的文章中有介绍到。
二、两者区别:
a、继承和类别最大的区别就是,一般情况下类别不能扩展属性,而继承可以扩展属性。
b、类别的方法比原类的方法的优先级高,如果方法相同,类别的方法会覆盖原类方法。
//.m文件内继承
#import "SDImageCache.h"
#import "SDWebImageDecoder.h"
#import "UIImage+MultiFormat.h"
#import <CommonCrypto/CommonDigest.h>
// See https://github.com/rs/SDWebImage/pull/1141 for discussion
@interface AutoPurgeCache : NSCache
@end
//设置净化子类,移除所有监听
@implementation AutoPurgeCache
- (id)init
{
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
return self;
}
@end
@interface SDImageCache ()
@property (strong, nonatomic) NSCache *memCache;
@property (strong, nonatomic) NSString *diskCachePath;
@property (strong, nonatomic) NSMutableArray *customPaths;
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t ioQueue;
@end
@implementation SDImageCache {
NSFileManager *_fileManager;
}
+ (SDImageCache *)sharedImageCache {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
········
@end
三、场景使用
继承使用场景:
a、当需要扩展方法与原方法重名时,并且需要调用父类的同名方法。
b、当需要扩展属性时。
类别使用场景:
a、针对系统的一些类进行扩展。如:NSString、NSArray、NSDictionary等。因为这些类内部实现对继承有所限制,所以最好使用分类的方式。
b、类别支持开发人员针对自己构建的类,把相关的方法分组到多个单独的文件中,针对大型且复杂的类,可以提高可维护性和可读性,并简化单个源文件的管理。
- 知识扩展
类别为什么不能添加成员变量?
1、类别里面虽然可以添加添加 property,但是这些 properties 并不会自动生成 Ivar,也就是不会有@synthesize 的作用,dyld 加载的期间,这些 categories 会被加载并 patch 到相应的类中。这个过程是一个动态过程,Ivar 不能动态添加,因为表示 ObjC 类的结构体运行时并不能改变。
2、根本原因:分类里面不能添加ivar是因为分类本身并不是一个真正的类,它并没有自己的ISA指针。类最开始生成了很多基本属性,比如IvarList,MethodList,分类只会将自己的method attach到主类,并不会影响到主类的IvarList。这就是为什么分类里面不能添加成员变量的原因。
ivar是成员变量。
思考:那么添加的成员变量放到哪里去了呢?
其实runtime是生成一个AssociationsManager类用来管理分类的关联对象,让分类能正常使用成员变量,有点类似于前面说到的用字典去保存的原理。通过查看源码,我们可以知道AssociationsManager类有一个AssociationsHashMap属性,这个属性是相当于一个字典,用来存储对象-关联对象的,也就是它的key是我们关联对象时传的self,也就是这个Person分类,以这个为key,然后value是一个ObjectAssociationMap,ObjectAssociationMap对象也相当于是一个字典,这个字典的key是我们关联对象时传进去的那个key,value是ObjcAssociation,ObjcAssociation对象里面有两个属性_value和_policy,这两个就是我们关联对象的值和关联策略了。
简单说AssociationsHashMap存储的是项目中所有分类的关联对象,里面应该是长这样{Person分类: AssociationsHashMap,Student分类: AssociationsHashMap},我们项目中有几个分类有关联对象,那AssociationsHashMap里面就有多少个元素。而AssociationsHashMap里面存放的就是关联对象的key和ObjcAssociation,里面应该长这样{@selector(weight):@(weight),@selector(name):name},一个分类里面有多少个关联对象AssociationsHashMap里面就有多少个元素。最后ObjcAssociation就是保存着关联对象的值和关联策略了。ObjcAssociation{unitptr_t _policy= OBJC_ASSOCIATION_RETAIN_NONATOMIC; id _value=@(weight)}
链接:https://www.jianshu.com/p/841a02ca8468
四、类的扩展
a、定义:扩展是类别(分类)的一种特殊形式。
b、格式:
interface 主类类名()
@end
扩展通常定义在主类.m文件中,扩展中声明的方法直接在主类.m文件中实现。
c、注意事项:
扩展中可以声明实例变量,可以声明属性。因为扩展通常定义在主类的.m文件中,所以扩展声明的方法和属性通常是私有的。
d、类别和扩展的区别
1、类别不能声明实例变量,且类别是公开的,文件名为:主类名+分类名
2、扩展可以声明实例变量,扩展是私有的,文件名:主类名_扩展标识.h,在主类的.m文件中#import该头文件。示例如下:
在.h中定义扩展的类方法:
@interface NSData (Utils)
- (NSString *)detectImageSuffix;
@end
在.m中实现扩展方法:
@implementation NSData (Utils)
- (NSString *)detectImageSuffix
{
uint8_t c;
NSString *imageFormat = @"";
[self getBytes:&c length:1];
switch (c) {
case 0xFF:
imageFormat = @".jpg";
break;
case 0x89:
imageFormat = @".png";
break;
case 0x47:
imageFormat = @".gif";
break;
case 0x49:
case 0x4D:
imageFormat = @".tiff";
break;
case 0x42:
imageFormat = @".bmp";
break;
default:
break;
}
return imageFormat;
}
@end
思考
一、子类中方法的优先级为什么会高于主类(或父类)呢?
在这里先说下结论:在执行方法时,子类的优先级高于父类,分类的优先级高于主类。
首先,编译器将类别方法[obj name];转化为objc_msgSend(obj, @selector (name));,在objc_msgSend函数中。首先通过obj的isa指针找到obj对应的class。在Class中先去cache中 通过SEL查找对应函数method,若 cache中未找到。再去methodList中查找,若methodlist中未找到,则取superClass中查找。若能找到,则将method加 入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。
上边的解释已经说得很清楚了,在查找name方法时,先从子类开始查找,若找到了就直接执行不再向父类查找,若没有找到,才会向父类中查找。
二、但是还有一个问题,分类的优先级为什么会高于主类呢?
我们知道,无论我们有没有主动引入 Category 的头文件,Category 中的方法都会被添加进主类中。我们可以通过- performSelector: 等方式对 Category 中的相应方法进行调用,之所以需要在调用的地方引入 Category 的头文件,只是为了"照顾"编译器的感受。
在函数中对 Category 做了如下处理:
1.将 Category 和它的主类(或元类)注册到哈希表中;
2.如果主类(或元类)已实现,那么重建它的方法列表。
类中的旧有方法和 Category 中新添加的方法整合成一个新的方法列表(也就是重建了方法列表),并赋值给method_lists 或 method_list。通过探究这个处理过程,我们也印证了一个结论,那就是主类中的方法和 Category 中的方法在 runtime 看来并没有区别,它们是被同等对待的,都保存在主类的方法列表中
链接:https://www.jianshu.com/p/4cd699a66f9b
参考资料:http://blog.csdn.net/lvxiangan/article/details/44600947
http://blog.csdn.net/LVXIANGAN/article/details/76510870
http://blog.csdn.net/fiona_yang123456/article/details/41044979