OC的分类和类拓展

编程入门 行业动态 更新时间:2024-10-09 06:22:55

<a href=https://www.elefans.com/category/jswz/34/1763299.html style=OC的分类和类拓展"/>

OC的分类和类拓展

一、分类(Category):

分类(Category)是OC中的特有语法,它是表示一个指向分类的结构体的指针。原则上它只能增加方法,不能增加成员(实例)变量。具体原因看源码组成:
Category
Category 是表示一个指向分类的结构体的指针,其定义如下:typedef struct objc_category *Category;struct objc_category {
char *category_name OBJC2_UNAVAILABLE; // 分类名
char *class_name OBJC2_UNAVAILABLE; // 分类所属的类名
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 实例方法列表
struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 类方法列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 分类所实现的协议列表
}

这个结构体主要包含了分类定义的实例方法与类方法,其中instance_methods 列表是 objc_class 中方法列表的一个子集,而class_methods列表是元类方法列表的一个子集。
但这个结构体里面,根本没有属性列表。

注意:
1.分类是用于给原有类添加方法的,因为分类的结构体指针中,没有属性列表,只有方法列表。所以< 原则上讲它只能添加方法, 不能添加属性(成员变量),实际上可以通过其它方式添加属性> ;
2.分类中的可以写@property, 但不会生成setter/getter方法, 也不会生成实现以及私有的成员变量(编译时会报警告);
3.可以在分类中访问原有类中.h中的属性;
4.如果分类中有和原有类同名的方法, 会优先调用分类中的方法, 就是说会忽略原有类的方法。所以同名方法调用的优先级为 分类 > 本类 > 父类。因此在开发中尽量不要覆盖原有类;
5.如果多个分类中都有和原有类中同名的方法, 那么调用该方法的时候执行谁由编译器决定;编译器会执行最后一个参与编译的分类中的方法。

分类不能添加属性的实质原因:
类中用@property声明属性,编译器会自动帮我们生成_成员变量和setter/getter,但分类的指针结构体中,根本没有属性列表。所以在分类中用@property声明属性,既无法生成_成员变量也无法生成setter/getter。
因此结论是:我们可以用@property声明属性,编译和运行都会通过,只要不使用程序也不会崩溃。但如果调用了_成员变量和setter/getter方法,报错就在所难免了。

报错原因如下:
//普通声明,无setter/getter
// programmer.nameWithoutSetterGetter = @“无setter/getter”;
//调用setter,编译成功,运行报错为:(-[Programmer setNameWithSetterGetter:]: unrecognized selector sent to instance 0x7f9de358fd70’)

// NSLog(@"%@",programmer.nameWithoutSetterGetter);
//调用getter,编译成功,运行报错为-[Programmer setNameWithSetterGetter:]: unrecognized selector sent to instance 0x7fe22be11ea0’

// NSLog(@"%@",_nameWithoutSetterGetter); //这是调用_成员变量,错误提示为:(Use of undeclared identifier ‘_nameWithoutSetterGetter’)

既然报错的根本原因是使用了系统没有生成的setter/getter方法,可不可以在手动添加setter/getter来避免崩溃,完成调用呢?
其实是可以的。由于OC是动态语言,方法真正的实现是通过runtime完成的,虽然系统不给我们生成setter/getter,但我们可以通过runtime手动添加setter/getter方法。那具体怎么实现呢?
按照这个思路,我们通过运行时手动添加这个方法。
#import <objc/runtime.h>
static NSString *nameWithSetterGetterKey = @“nameWithSetterGetterKey”; //定义一个key值@implementation Programmer (Category)
//运行时实现setter方法- (void)setNameWithSetterGetter:(NSString *)nameWithSetterGetter {
objc_setAssociatedObject(self, &nameWithSetterGetterKey, nameWithSetterGetter, OBJC_ASSOCIATION_COPY);}
//运行时实现getter方法- (NSString *)nameWithSetterGetter {
return objc_getAssociatedObject(self, &nameWithSetterGetterKey);}
@end

实际使用效果
//通过runtime实现了setter/getter
programmer.nameWithSetterGetter = @“有setter/getter”; //调用setter,成功
NSLog(@"%@",programmer.nameWithSetterGetter); //调用getter,成功// NSLog(@"%@",_nameWithSetterGetter); //这是调用_成员变量,错误提示为:(Use of undeclared identifier ‘_nameWithSetterGetter’)

问题解决。

⚠️以上代码仅仅是手动实现了setter/getter方法,但调用_成员变量依然报错。

二、类扩展(Class Extension)

Extension是Category的一个特例。类扩展与分类相比只少了分类的名称,所以称之为“匿名分类”。
类扩展格式:
@interface XXX ()//私有属性//私有方法(如果不实现,编译时会报警,Method definition for ‘XXX’ not found)@end
作用:
为一个类添加额外的原来没有变量,方法和属性
一般的类扩展写到.m文件中
一般的私有属性写到.m文件中的类扩展中

类别与类扩展的区别:
①类别中原则上只能增加方法(能添加属性的的原因只是通过runtime解决无setter/getter的问题而已);
②类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型的(
用范围只能在自身类,而不是子类或其他地方);
③类扩展中声明的方法没被实现,编译器会报警,但是类别中的方法没被实现编译器是不会有任何警告的。这是因为类扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中。
④类扩展不能像类别那样拥有独立的实现部分(@implementation部分),也就是说,类扩展所声明的方法必须依托对应类的实现部分来实现。
⑤定义在 .m 文件中的类扩展方法为私有的,定义在 .h 文件(头文件)中的类扩展方法为公有的。类扩展是在 .m 文件中声明私有方法的非常好的方式。

三、分类运用场景举例:想要收集每个页面的启动时间。

1:上百个界面怎么处理

项目中已经有上百个页面了,如果一个一个的加,浪费时间不说,以后增加了新页面,还需要添加方法。

解决方法:
我们可以发现页面都继承了UIViewController,想要在每个页面都执行的代码,可以写在这些页面的父类中。我们可以把代码写在UIViewController中。

本类是系统的类,这里是UIViewController,我们可以使用分类扩展他的方法,也可以重写他的方法,可以我需要在调用的地方加头文件,所有子类都写头文件和直接在子类写方法没有什么区别,怎么样可以使得不写头文件,子类就能调用我们写的代码呢?

回答:

我们可以进行方法交换(这样可以不必在调用的地方增加头文件),从而使得在实现的时候调用重写的方法。

2:分类的执行优先级

在本类和分类有相同的方法时,优先调用分类的方法再调用本类的方法。
如果有两个分类,他们都实现了相同的方法,如何判断谁先执行?分类执行顺序可以通过targets,Build Phases,Complie Source进行调节,注意执行顺序是从上到下的。(只有两个相同方法名的分类)

3:UIViewController是官方类,想修改实现

我们只能调用期接口,并不能修改他的实现。
解决方法:使用分类(category)。

1.分类(category)的作用
作用:可以在不修改原来类的基础上,为一个类扩展方法。
最主要的用法:给系统自带的类扩展方法。
2.分类中能写点啥?
分类中只能添加“方法”,不能增加成员变量。
分类中可以访问原来类中的成员变量,但是只能访问@protect和@public形式的变量。如果想要访问本类中的私有变量,分类和子类一样,只能通过方法来访问。
如果一定要在分类中添加成员变量,可以通过getter,setter手段进行添加,详细以后再写,TODO。
3.分类的书写方法
假设我们有一个类,名叫Person,意义是人类。在person类中我们有一个方法,-(void)eat;意义是吃,因为每个人类都会吃。
人类有很多共同特点,也有很多不同点,比如,并不是每个人都会踢足球。这时候我写一个分类,给喜欢运动的人,这个分类的名字叫做sport。因为是person类的分类,你会看到生成的名字叫做Person+sport。

4:分类(category)和类扩展(extension)的关系

1.类扩展(extension)是category的一个特例,有时候也被称为匿名分类。他的作用是为一个类添加一些私有的成员变量和方法。
2.类扩展能写点啥?和分类不同,类扩展即可以声明成员变量又可以声明方法。
3.类扩展听上去很复杂,但其实我们很早就认识他了。你记得继承自UIViewController的ViewController和继承自NSObject的类有什么不同么?
4.继承自UIViewController的ViewController类

5:分类写法

@interface 待扩展的类(分类的名称)
@end
@implementation 待扩展的名称(分类的名称)
@end
类扩展的写法:
@interface ViewController() @end
问题6:
类扩展可以定义在.m文件中,这种扩展方式中定义的变量都是私有的,也可以定义在.h文件中,这样定义的代码就是共有的,类扩展在.m文件中声明私有方法是非常好的方式。类扩展中添加的新方法,一定要实现。categorygory中没有这种限制。

继承、分类、拓展:

以下情况,使用继承:
1)新扩展的方法与原方法同名,但是还需要使用父类的实现。
2)扩展类的属性。
// ViewControllerEx.h
@interfaceViewControllerEx : UIViewController
// 自己需要添加的方法
@end

// ViewControllerEx.m
@implementationViewControllerEx
// 方法的实现
@end

以下情况,使用类别:
1)针对系统特定类,例如:NSString,NSArray,NSNumber等。
2)针对自定义类,对于大型而复杂的类,为提高可维护性,把相关的方法分组到多个单独的文件中。

@interface 主类类名(分类类名)
//不可以定义成员属性
@end

@implementation 主类类名(分类类名)

@end
// 这里有一个约定俗成的规定,类别文件命名时,是原类名+扩展标识名
// NSString+ex.h
@interface NSString(ex)
// 扩展的类回别方法
@end

// NSString+ex.m
@implementation NSString(ex)
// 方法的实现
@end

3)虽然不能在分类(类别)中定义成员属性,但是有办法也可以让它支持添加属性和成员变量

一种常见的办法是通过runtime.h中objc_getAssociatedObject / objc_setAssociatedObject来访问和生成关联对象。通过这种方法来模拟生成属性。

“NSObject+SpecialName.h”文件:

@interface NSObject (SpecialName)
@property (nonatomic, copy) NSString *specialName;
@end

“NSObject+SpecialName.m”文件:
#import “NSObject+Extension.h”
#import <objc/runtime.h>
static const void*SpecialNameKey = &SpecialNameKey;
@implementation NSObject (SpecialName)
@dynamic specialName;

  • (NSString *)specialName {
    //如果属性值是非id类型,可以通过属性值先构造OC的id对象,再通过对象获取非id类型属性
    return objc_getAssociatedObject(self, SpecialNameKey);
    }

  • (void)setSpecialName:(NSString *)specialName{
    //如果属性值是非id类型,可以通过属性值先构造OC的id对象,再通过对象获取非id类型属性
    objc_setAssociatedObject(self, SpecialNameKey, specialName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

@end

注意事项

分类中方法的优先级比原来类中的方法高,也就是说,在分类中重写了原来类中的方法,那么分类中的方法会覆盖原来类中的方法
分类中只能声明方法,不能添加属性变量,在运行时分类中的方法与主类中的方法没有区别
通常来讲,分类定义在.h文件中,但也可以定义.m文件中,此时分类的方法就变成私有方法

// 这里有一个约定俗成的规定,类别文件命名时,是原类名+扩展标识名
// NSString+ex.h
@interfaceNSString(ex)
// 扩展的类回别方法
@end
// NSString+ex.m
@implementationNSString(ex)
// 方法的实现
@end

更多推荐

OC的分类和类拓展

本文发布于:2024-02-14 02:20:26,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1761225.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:OC

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!