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

相關文章
相關標籤/搜索