runtime的一些应用

1、便利类的成员变量、属性、方法git

/**遍历类因此成员变量github

     * @param <#__unsafe_unretained Class cls#> 要遍历的类算法

     * @param  <#unsigned int *outCount#> 成员变量数量编程

     */ide

    //    class_copyIvarList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)字体

    

    unsigned int count = 0;ui

 

    Ivar * ivars = class_copyIvarList([UIView class], &count);atom

    

    for (int i = 0; i < count; i++) {spa

        Ivar ivar = ivars[i];翻译

        

        const char * name = ivar_getName(ivar);

        

        NSString * str = [NSString stringWithUTF8String:name];

        

        NSLog(@"%d:%@",i,str);

    }

    free(ivars);

    /**遍历类因此属性

     * @param <#__unsafe_unretained Class cls#> 要遍历的类

     * @param  <#unsigned int *outCount#> 属性数量

     */

    //class_copyPropertyList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)

    unsigned int propertyCount = 0;

    objc_property_t * propertys = class_copyPropertyList([UIView class], &propertyCount);

    for (int i = 0; i < propertyCount; i++) {

        objc_property_t property = propertys[i];

        const char * name = property_getName(property);

        NSString * str = [NSString stringWithUTF8String:name];

        NSLog(@"%d:%@",i,str);

    }

    free(propertys);

    

    /**遍历类全部方法

     * @param <#__unsafe_unretained Class cls#> 要遍历的类

     * @param  <#unsigned int *outCount#> 属性数量

     */

    //class_copyMethodList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)

    unsigned int methodCount = 0;

    

    Method * methods = class_copyMethodList([UIView class], &methodCount);

    for (int i = 0; i < count; i++) {

        Method method = methods[i];

        SEL sel = method_getName(method);

        NSLog(@"%d:%@",i,NSStringFromSelector(sel));

    }

    free(methods);

 2、消息转发

一、重定向

消息转发机制执行前,Runtime 系统容许咱们替换消息的接收者为其余对象。经过 - (id)forwardingTargetForSelector:(SEL)aSelector 方法。

- (id)forwardingTargetForSelector:(SEL)aSelector { if(aSelector == @selector(mysteriousMethod:)){ return alternateObject; } return [super forwardingTargetForSelector:aSelector]; }

若是此方法返回 nil 或者 self,则会计入消息转发机制(forwardInvocation:),不然将向返回的对象从新发送消息。

二、转发

当动态方法解析不作处理返回 NO 时,则会触发消息转发机制。这时 forwardInvocation: 方法会被执行,咱们能够重写这个方法来自定义咱们的转发逻辑:

- (void)forwardInvocation:(NSInvocation *)anInvocation { if ([someOtherObject respondsToSelector: [anInvocation selector]]) [anInvocation invokeWithTarget:someOtherObject]; else [super forwardInvocation:anInvocation]; }

惟一参数是个 NSInvocation 类型的对象,该对象封装了原始的消息和消息的参数。咱们能够实现 forwardInvocation: 方法来对不能处理的消息作一些处理。也能够将消息转发给其余对象处理,而不抛出错误。

注意:参数 anInvocation 是从哪来的?
在 forwardInvocation: 消息发送前,Runtime 系统会向对象发送methodSignatureForSelector: 消息,并取到返回的方法签名用于生成 NSInvocation 对象。因此重写 forwardInvocation: 的同时也要重写 methodSignatureForSelector: 方法,不然会抛异常。

当一个对象因为没有相应的方法实现而没法相应某消息时,运行时系统将经过 forwardInvocation: 消息通知该对象。每一个对象都继承了 forwardInvocation: 方法。可是, NSObject 中的方法实现只是简单的调用了 doesNotRecognizeSelector:。经过实现本身的 forwardInvocation: 方法,咱们能够将消息转发给其余对象。

forwardInvocation: 方法就是一个不能识别消息的分发中心,将这些不能识别的消息转发给不一样的接收对象,或者转发给同一个对象,再或者将消息翻译成另外的消息,亦或者简单的“吃掉”某些消息,所以没有响应也不会报错。这一切都取决于方法的具体实现。

注意:
forwardInvocation:方法只有在消息接收对象中没法正常响应消息时才会被调用。因此,若是咱们向往一个对象将一个消息转发给其余对象时,要确保这个对象不能有该消息的所对应的方法。不然,forwardInvocation:将不可能被调用。

三、转发和多继承

转发和继承类似,可用于为 Objc 编程添加一些多继承的效果。就像下图那样,一个对象把消息转发出去,就好像它把另外一个对象中的方法接过来或者“继承”过来同样。


 

这使得在不一样继承体系分支下的两个类能够实现“继承”对方的方法,在上图中 Warrior 和 Diplomat 没有继承关系,可是 Warrior 将 negotiate 消息转发给了 Diplomat 后,就好似 Diplomat 是 Warrior 的超类同样。

消息转发弥补了 Objc 不支持多继承的性质,也避免了由于多继承致使单个类变得臃肿复杂。

 

四、转发与继承

虽然转发能够实现继承的功能,可是 NSObject 仍是必须表面上很严谨,像 respondsToSelector: 和 isKindOfClass: 这类方法只会考虑继承体系,不会考虑转发链。

若是上图中的 Warrior 对象被问到是否能响应 negotiate消息:

if ( [aWarrior respondsToSelector:@selector(negotiate)] ) ...

回答固然是 NO, 尽管它能接受 negotiate 消息而不报错,由于它靠转发消息给 Diplomat 类响应消息。

若是你就是想要让别人觉得 Warrior 继承到了 Diplomat 的 negotiate 方法,你得从新实现 respondsToSelector: 和 isKindOfClass: 来加入你的转发算法:

- (BOOL)respondsToSelector:(SEL)aSelector { if ( [super respondsToSelector:aSelector] ) return YES; else { /* Here, test whether the aSelector message can * * be forwarded to another object and whether that * * object can respond to it. Return YES if it can. */ } return NO; }

除了 respondsToSelector: 和 isKindOfClass: 以外,instancesRespondToSelector: 中也应该写一份转发算法。若是使用了协议,conformsToProtocol: 一样也要加入到这一行列中。

若是一个对象想要转发它接受的任何远程消息,它得给出一个方法标签来返回准确的方法描述 methodSignatureForSelector:,这个方法会最终响应被转发的消息。从而生成一个肯定的 NSInvocation 对象描述消息和消息参数。这个方法最终响应被转发的消息。它须要像下面这样实现:

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { NSMethodSignature* signature = [super methodSignatureForSelector:selector]; if (!signature) { signature = [surrogate methodSignatureForSelector:selector]; } return signature; }
3、对象关联

对象关联容许开发者对已经存在的类在 Category 中添加自定义的属性:

1
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

·object 是源对象

·value 是被关联的对象

·key 是关联的键,objc_getAssociatedObject 方法经过不一样的 key 便可取出对应的被关联对象

·policy 是一个枚举值,表示关联对象的行为,从命名就能看出各个枚举值的含义:

1
2
3
4
5
6
7
8
9
10
11
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_getAssociatedObject 方法便可,要删除一个被关联的对象,使用 objc_setAssociatedObject 方法将对应的 key 设置成 nil 便可:

1
objc_setAssociatedObject(self, associatedKey, nil, OBJC_ASSOCIATION_COPY_NONATOMIC);

objc_removeAssociatedObjects 方法将会移除源对象中全部的关联对象.

举个栗子,假如咱们要给 UIButton 添加一个监听单击事件的 block 属性,新建 UIButton 的 Category,其.m文件以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#import "UIButton+ClickBlock.h"
#import static const void *associatedKey = "associatedKey";
@implementation UIButton (ClickBlock)
//Category中的属性,只会生成setter和getter方法,不会生成成员变量
-(void)setClick:(clickBlock)click{
     objc_setAssociatedObject(self, associatedKey, click, OBJC_ASSOCIATION_COPY_NONATOMIC);
     [self removeTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
     if  (click) {
         [self addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
     }
}
-(clickBlock)click{
     return  objc_getAssociatedObject(self, associatedKey);
}
-(void)buttonClick{
     if  (self.click) {
         self.click();
     }
}
@end

而后在代码中,就可使用 UIButton 的属性来监听单击事件了:

1
2
3
4
5
6
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = self.view.bounds;
[self.view addSubview:button];
button.click = ^{
     NSLog(@ "buttonClicked" );
};

完整的对象关联代码点这里

4、方法交换

  Method imp = class_getInstanceMethod([self class], @selector(initWithCoder:));

  Method myImp = class_getInstanceMethod([self class], @selector(myInitWithCoder:));

      method_exchangeImplementations(imp, myImp);

举个UILabel字体适配plus屏的例子

#import "UILabel+AdjustSize.h"

#import <objc/runtime.h>

#define isiPhone6P ([[UIScreen mainScreen] bounds].size.height == 736)

#define SizeScale (isiPhone6P?1.18:1)

@interface UILabel()

@property (nonatomic, strong) UIButton *but;

@end

@implementation UILabel (AdjustSize)

+ (void)load{

    Method imp = class_getInstanceMethod([self class], @selector(initWithCoder:));

    Method myImp = class_getInstanceMethod([self class], @selector(myInitWithCoder:));

    method_exchangeImplementations(imp, myImp);

}

- (id)myInitWithCoder:(NSCoder *)aDecode{

    

    [self myInitWithCoder:aDecode];

    if (self) {

        // 部分不想改变字体的 把tag值设置成555跳过

        if (self.tag != 555) {

            CGFloat fontSize = self.font.pointSize;

            self.font = [UIFont systemFontOfSize:fontSize * SizeScale];

        }

    }

    return self;

}

 @end

相关文章
相关标签/搜索