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]
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

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