iOS 利用Runtime(运行时) 动态添加属性、方法、交换方法、发送消息

第一部分:【很重要,这个必须先看】

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:关联的策略【关联策略的枚举值下面有界面】。

图一:
image.png

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语言直接用类名调用类方法即可。不需要向上面那么用创建出来的类对象去调用
33-01.png

第四部分:

利用Runtime动态添加方法

  • 为什么动态添加方法?
    • 因为有些方法可能会就不会用到,所以OC都是懒加载机制。例如:会员机制,只有是会员,才具有某些功能(才会在代码的懒加载方法中让你成为会员),你不是会员,你永远不会被执行懒加载方法,不会让你在懒加载中实现成为会员的功能
33-02.png

第五部分:

普通添加属性

  • 仅仅通过分类给NSObject中添加name属性
34-03.png

利用Runtime动态添加属性

  • 给分类只能扩充方法,一般情况不能扩充属性。
  • 如果想要扩充属性,必须用到RunTime添加属性,也就是动态添加属性。
  • 普通添加属性的那个截图不属于扩充属性的范畴,_name不是.h属性底层的_name,而是定义的静态全局变量。(后面的可不看)因为在分类中添加属性,不会生成方法的实现和下划线开头的变量_name,你只能通过重写setter和getter方法的方式完成,但是_name不会生成,所以在setter方法中,等号左边的_name不存在,getter方法中的_name我们也不存在。通过定义一个Static修饰的下划线开头的这个全局变量_name,可以解决问题,但是这个_name是我们自己定义的,不是系统自动成的。
  • NSObjct这个基类本身没有name属性,可以通过分类+Runtime相结合的方式动态添加属性
34-15.png
34-02.png

第六部分:

利用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]
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 对于从事 iOS 开发人员来说,所有的人都会答出【runtime 是运行时】什么情况下用runtime?大部分人能...
    梦夜繁星阅读 3,732评论 7 64
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,636评论 33 466
  • 导读:11、12月注定是不太平的月份,好多小型互联网创业公司都突然崩塌,最近一个朋友跟我抱怨道,说终于感受到了互联...
    柳骏阅读 9,728评论 11 166
  • 强调自己的不幸,放大自己的委屈,自以为是的特殊性,统统都是懦夫的体现。
    陈乡阅读 132评论 0 0
  • 针对大功率密度的应用场合,比如激光、服务器、光伏能源、医疗设备、军工设备中,由于对温度的苛刻要求,水冷板就成了唯一...
    sanre123阅读 6,186评论 0 1