第一部分:【很重要,这个必须先看】
runtime.h文件中有如下方法,该方法实现了动态添加属性
的功能,这里说明一下含义。
OBJC_EXPORT void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,id _Nullable value, objc_AssociationPolicy policy);
解释:
参数一:id _Nonnull object
:给哪个对象添加关联。self的话,就是给当前类添加关联
参数二:const void * _Nonnull key
:setter和getter方法中这个参数一定要保持一致,就是图二中的userKey
,【详见图一】
。相当于字典中的key。
参数三:id _Nullable value
:外界传递过来的value,就是图一中的userID
。相当于字典中的value。
参数四:objc_AssociationPolicy policy
:关联的策略【关联策略的枚举值下面有界面】。
图一:
runtime.h文件中关联的策略
有以下枚举值,这里说明一下含义。
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
解释:
OBJC_ASSOCIATION_ASSIGN 等价 @property(assign)
OBJC_ASSOCIATION_RETAIN_NONATOMIC 等价 @property( nonatomic, strong)
OBJC_ASSOCIATION_COPY_NONATOMIC: @property( nonatomic,copy)
OBJC_ASSOCIATION_RETAIN 等价 @property(atomic,strong)
OBJC_ASSOCIATION_COPY 等价 @property(atomic,copy)
第二部分:
方法调用本质
-
利用runtime发送消息(即让对象发送消息,这个对象指的是创建出类的对象和类对象,是两个)
- 若想使用runtime消息,必须导入#import <objc/message.h>框架或者#import <objc/runtime.h>
- 若想知道发送消息的代码格式:
- 先在终端中cd到运行程序的目录
- 再输入 clang -rewrite-objc main.m 查看最终生成代码
-
消息机制的使用场景:
- 调用私有方法
- 调用系统底层的(没有暴露出来的)方法
- Runtime是在不得不使用的时候采用的。
第三部分:
(Runtime)消息机制
- 对象调用对象方法||类对象调用类方法。
- 当使用后者时,类是一个类对象,所以用[类名 class]获取类对象,再用类对象调用类方法.
- 区分OC语言哦,OC语言直接用类名调用类方法即可。不需要向上面那么用创建出来的类对象去调用
第四部分:
利用Runtime动态添加方法
- 为什么动态添加方法?
- 因为有些方法可能会就不会用到,所以OC都是懒加载机制。例如:会员机制,只有是会员,才具有某些功能(才会在代码的懒加载方法中让你成为会员),你不是会员,你永远不会被执行懒加载方法,不会让你在懒加载中实现成为会员的功能
第五部分:
普通添加属性
- 仅仅通过分类给NSObject中添加name属性
利用Runtime动态添加属性
- 给分类只能扩充方法,一般情况不能扩充属性。
- 如果想要扩充属性,必须用到RunTime添加属性,也就是动态添加属性。
- 普通添加属性的那个截图不属于扩充属性的范畴,_name不是.h属性底层的_name,而是定义的静态全局变量。(后面的可不看)因为在分类中添加属性,不会生成方法的实现和下划线开头的变量_name,你只能通过重写setter和getter方法的方式完成,但是_name不会生成,所以在setter方法中,等号左边的_name不存在,getter方法中的_name我们也不存在。通过定义一个Static修饰的下划线开头的这个全局变量_name,可以解决问题,但是这个_name是我们自己定义的,不是系统自动成的。
- NSObjct这个基类本身没有name属性,可以通过分类+Runtime相结合的方式动态添加属性
第六部分:
利用Runtime交换方法
- 有的时候,系统的类不能满足要求时,例如系统类(NSString,UIImage)可能并不能满足我们的要求,解决办法:
- 1.往往是给系统自带的类添加分类,就是对原有的类进行扩充方法,但是切记扩充的方法不要和系统的类相同.
- 2.或者自定义一个类继承系统的类,再重写父类底层的方法。可以达到给系统的类自定义某个功能的目的。
- 3.如果老板要求外界调用的类方法必须是系统的类方法,即给imageNamed方法提供功能,每次加载图片就判断下图片是否加载成功。那么上述两种情况就满足不了条件,可以使用Runtime运行时机制,即 调用imageNamed:的方法,实际上调用了ZBimageNamed:方法
- 3.1:创建分类
- 3.2:写一个这样功能的方法
- 3.3:用系统的方法与有这个功能的方法交换
- 3.4:调用imageNamed,先会调用分类的load方法,在load方法实现交换,然后才会去调用分类的ZBimageNamed
- 具体步骤:在分类中调用load方法,导入runtime框架,load方法中写上获取两个交换的类的类名,然后写上method_exchangexxxxx,实现交换。外界调用imageNamed:的方法,实际上调用了ZBimageNamed。代码如下:
ViewController.h文件
// 需求:给imageNamed方法提供功能,每次加载图片就判断下图片是否加载成功。
// 步骤一:先搞个分类,定义一个能加载图片并且能打印的方法+ (instancetype)ZBimageNamed:(NSString *)name;
// 步骤二:交换imageNamed和ZBimageNamed的实现,就能调用ZBimageNamed,间接调用ZBimageNamed的实现。
//表面上调用imageNamed,实际上跑到了分类中调用了ZBimageNamed,
//在ZBimageNamed方法中又调用了ZBimageNamed,实际上调用了系统底层的imageNamed
//注意:分类中的ZBimageNamed方法中的ZBimageNamed不能换成imageNamed,否则真实会调用ZBimageNamed,从而造成死循环
// 既能加载图片又能打印.方法为ZBimageNamed,不是ZBimageNamed11
#import "ViewController.h"
#import "UIImage+Image.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[UIImage imageNamed:@"123"];//内部调用ZBimageNamed方法
UIImage+Image.h文件
#import <UIKit/UIKit.h>
@interface UIImage (Image)
// 加载图片 只要是系统底层不存在的类一定要声明
//+ (UIImage *)ZBimageNamed:(NSString *)name;
@end
UIImage+Image.m文件
@implementation UIImage (Image)
// 加载分类到内存的时候调用
+ (void)load
{
// 交换方法
// 获取ZBimageNamed方法地址
//ZBimageNamed11只是用来交换地址用的,交换万完之后,就没有任何意义了,即以后用到的是ZBimageNamed,而不是ZBimageNamed11
Method ZBimageNamed11 = class_getClassMethod(self, @selector(ZBimageNamed:));
// 获取imageNamed方法地址
Method imageName11 = class_getClassMethod(self, @selector(imageNamed:));
// 交换方法地址,相当于交换实现方式
method_exchangeImplementations(ZBimageNamed11, imageName11);
}
// 不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉,而且分类中不能调用super.
+ (instancetype)ZBimageNamed:(NSString *)name
{
// 这里调用ZBimageNamed,本质调用的是imageNamed
UIImage *image = [self ZBimageNamed:name];
if (image == nil) {
NSLog(@"加载空的图片");
}
return image;
}
@end
在.h中要不要写方法的声明 大解析:
- 只要是系统底层不存在某个方法,当我们要调用这个方法时,一定要声明。
- 只要是A类不具有B方法或者B属性,但是C类具有B方法或者B属性,当我们用A类或者A的对象调用B的方法或者属性时(调用属性是调用set方法),必须要在A类的.h文件中声明B方法或者属性,还要在A的.m文件实现对应的方法(在本页搜 动态添加属性、普通添加属性)
- 1.子类继承父类的类,重写父类的方法,不需要声明该方法,只需要实现该方法.
- 2.给系统的类扩充方法,扩充的方法如果系统底层不存在,那么不仅要声明扩充的方法,还要实现扩充的方法.
- 对1和2的详细解释:
-
ZBImage
子类继承父类UIImage
,如果子类重写父类(系统底层)的+ (UIImage *)imageNamed:(NSString *)name
方法,那么ZBImage类不需要在自己的.h文件中声明系统的这个方法,只需在.m文件中重写父类的这个方法,最后只需要在外界用UIImage调用这个类方法即可. - 给
UIImage
添加分类,为的是给UIImage
类扩充新功能.那么不仅要在分类的.h文件声明方法+ (UIImage *)ZBimageNamed:(NSString *)name;
,还要在.m文件中实现该方法,最后只需在外界用UIImage
调用这个类方法即可.否则提示在接口中没有类方法(接口就是.h文件中声明的方法)
-
拓展:基础知识点
- 类的本质:
- 类本身也是一个对象,是class类型的对象,简称“类对象”。类名就代表着类对象,每个类只有一个类对象
- 获取内存中的类对象.例如[Person class]