天天看點

【IOS 開發學習總結-OC-23】★objective-c的反射機制objective-c 提供的與運作環境互動的方式獲得 class檢查繼承關系動态調用方法

【IOS 開發學習總結-OC-23】★objective-c的反射機制

objective-c 提供的與運作環境互動的方式

objective-c 提供了3種程式設計方式與運作環境互動。

  1. 直接通過 oc 源代碼。——最常見的方式。其中,資料結構負責儲存類,類别中定義的資料,而函數負責處理方法調用。
  2. 通過 NSObject 類中定義的方法進行動态程式設計。

    大部分對象都可以直接調用NSObject 的方法進行程式設計——絕大部分類都是NSObject 類的子類(NSProxy例外)。少數情況下,NSObject隻提供了方法模闆,并沒有給方法提供實作代碼。

  3. 直接調用運作時函數進行動态程式設計。

獲得 class

objective-c程式中獲得 class 通常有3種方式:

1. 調用某個類的 class 方法來擷取該類對應的 class。推薦這種方式———代碼更安全(編譯時檢查 class 對象是否存在),程式性能更高(無需調用方法)。

2. 使用 class NSClassFromString(NSStrin* aClassName) 函數來獲得 class——該函數需要傳入字元串參數,該參數 是某個類的類名。

3. 調用某個對象的 class 方法——該方法為 NSObject 的一個方法,所有的 oc 對象都可以調用該方法。

示例程式:

#import <Foundation/Foundation.h>

int main(int argc , char * argv[])
{
    @autoreleasepool{
        // 通過字元串來擷取Class
        Class clazz = NSClassFromString(@"NSDate");
        NSLog(@"%@" , clazz);
        // 直接使用Class來建立對象
        id date = [[clazz alloc] init];
        NSLog(@"%@" , date);
        // 通過對象來擷取Class
        NSLog(@"%@" , [date class]);
        // 通過類來擷取class
        NSLog(@"%d" , clazz == NSDate.class);
    }
}
           

編譯運作結果:

2015-09-29 18:21:09.590 923[5960:414788] NSDate
2015-09-29 18:21:09.600 923[5960:414788] 2015-09-29 10:21:09 +0000
2015-09-29 18:21:09.600 923[5960:414788] __NSTaggedDate
2015-09-29 18:21:09.600 923[5960:414788] 1
           

由上面的代碼可以看出,調用 class 的 alloc 方法建立的并不是 class 的執行個體,而是該 class 對應的類的執行個體。

檢查繼承關系

确認一個類的繼承關系可以調用 NSObject 提供的如下方法進行判斷:

⭐️

-isKindOfClass:

需要傳入一個 Class 參數,判斷該對象是否為該類及其子類的執行個體;

⭐️

-isMemberOfClass:

需要傳入一個 Class 參數,判斷該對象是否為該類的執行個體———比

-isKindOfClass:

更為嚴格;

⭐️

-conformsToProtocol:

需要傳入一個 Protocol參數,判斷該對象是否為該類及其子類的執行個體;——為了擷取Protocol參數,可以使用如下2個方法:

1. objective-c 提供的@protocol 指令來實作;

2. 可以調用例如

Protocol* abd=[NSProtocolFromString(<#NSString * _Nonnull namestr#>)];

的方法根據協定名字元串擷取對應的協定。

示例代碼:

FKEatable.h

#import <Foundation/Foundation.h>

// 定義協定
@protocol FKEatable
@optional
- (void) taste;
@end
           

FKApple.h

#import <Foundation/Foundation.h>
#import "FKEatable.h"

// 定義類的接口部分,實作FKEatable協定
@interface FKApple : NSObject <FKEatable>
@end
           

FKApple.m

#import "FKApple.h"

// 為FKApple提供實作部分
@implementation FKApple
@end
           

checkObject.m

#import <Foundation/Foundation.h>
#import "FKApple.h"

int main(int argc , char * argv[])
{
    @autoreleasepool{
        FKApple* app = [[FKApple alloc] init];
        // 通過對象來判斷該對象的Class
        NSLog(@"%@" , [app class]);
        // 判斷對象是否為某個類的執行個體
        NSLog(@"app是否為FKApple的執行個體:%d", 
            [app isMemberOfClass: FKApple.class]);
        NSLog(@"app是否為NSObject的執行個體:%d",
            [app isMemberOfClass: NSObject.class]);
        // 判斷對象是否為某個類及其子類的執行個體            
        NSLog(@"app是否為FKApple及其子類的執行個體:%d",
            [app isKindOfClass: FKApple.class]);
        NSLog(@"app是否為NSObject及其子類的執行個體:%d",
            [app isKindOfClass: NSObject.class]);
        // 判斷對象是否實作了指定協定
        NSLog(@"app是否實作FKEatable協定:%d",
            [app conformsToProtocol: @protocol(FKEatable)]);
    }
}
           

編譯運作結果;

2015-09-29 18:51:20.621 923[6083:426035] FKApple
2015-09-29 18:51:20.623 923[6083:426035] app是否為FKApple的執行個體:1
2015-09-29 18:51:20.623 923[6083:426035] app是否為NSObject的執行個體:0
2015-09-29 18:51:20.624 923[6083:426035] app是否為FKApple及其子類的執行個體:1
2015-09-29 18:51:20.624 923[6083:426035] app是否為NSObject及其子類的執行個體:1
2015-09-29 18:51:20.624 923[6083:426035] app是否實作FKEatable協定:1
           

動态調用方法

如果程式需要通路對象執行個體變量的值,程式都可以通過 KVC 機制來設定,通路執行個體變量的值——不管該執行個體變量是在類的接口部分定義,還是在類的實作部分定義,也不管該變量使用哪種通路控制符修飾,

判斷某個對象是否可以調用某個方法

使用方法:

對象 respondsToSelector:<#(SEL)#>

——需要傳入一個 SEL參數(objective-c 使用 SEL對象來代表方法),如果該對象可以調用該方法,傳回YES,否則傳回 NO。

那麼如何在程式中動态擷取 SEL 對象呢?

可用方法如下:

1. 使用@selector 指令來擷取目前類中指定的方法。——需要用完整的方法簽名關鍵字作為參數,僅有方法名不夠。

2. 使用

SEL NSSelectorFromString(<#NSString * _Nonnull aSelectorName#>)

函數根據方法簽名關鍵字的字元串擷取對應的方法。

那如何動态調用對象的普通方法呢?有2種方法:

1. 通過

performSelector:<#(SEL)#>

方法實作,如果調用方法需要傳入參數,還可以通過

withObject:<#(id)#>

标簽來實作,

2. 使用

objc_msgSend(receiver,selector,...)

函數來調用。——第一個參數是方法調用者,第二個參數是調用的方法,接下來的參數将作為調用方法的參數。

【IOS 開發學習總結-OC-23】★objective-c的反射機制objective-c 提供的與運作環境互動的方式獲得 class檢查繼承關系動态調用方法

示例代碼:

FKCar.h

#import <Foundation/Foundation.h>

// 定義類的接口部分
@interface FKCar : NSObject

@end
           

FKCar.m

#import <objc/message.h>
#import "FKCar.h"

// 為FKCar提供實作部分
@implementation FKCar

- (void) move:(NSNumber*) count
{
    int num = [count intValue];
    for (int i =  ; i < num ; i++)
    {
        NSLog(@"%@", [NSString stringWithFormat
            :@"汽車正在路上走~~%d" , i]); 
    }
}
- (double) addSpeed:(double) factor
{
    // 此處希望能動态調用move方法
    // 使用performSelector:動态調用move:方法
    [self performSelector:@selector(move:) 
        withObject: [NSNumber numberWithInt: ]];
    [self performSelector:NSSelectorFromString(@"move:") 
        withObject: [NSNumber numberWithInt: ]];
    // 使用objc_msgSend()函數動态調用move:方法
    objc_msgSend(self,@selector(move:),[NSNumber numberWithInt: ]);
    objc_msgSend(self , NSSelectorFromString(@"move:")
        , [NSNumber numberWithInt: ]);
    NSLog(@"正在加速。。。%g" , factor);
    return  * factor;
}
@end
           

這個示例也說明了 objective-c 中并沒有真正的私有的方法。

傳回函數指針的方法

NSObject 提供的方法

-(IMP)methodForSelector:<#(SEL)#>

——該方法根據傳入的 SEL 參數,傳回該方法對應的 IMP 對象。

IMP ——代表指向 objective-c 方法的函數指針。相當于一個指向函數的指針變量,也就代表了函數的入口。接下來就可以通過 IMP 來調用該函數——也就是調用了 OC 的方法。

對于一個指向 objective-c 方法的函數指針變量,它指向的函數簽名的第一參數,通常是方法的調用者,第二個參數通常是方法對應的 SEL對象,接下來是調用方法的參數。是以,通常會用如下代碼格式定義指向 OC 方法的函數變量。

傳回值類型 (*指針變量名)(id,SEL,....);

一旦擷取了指向 OC方法 的函數指針變量,就可以通過它來通路函數——執行 OC 的方法

示例程式:

#import <Foundation/Foundation.h>
#import <objc/message.h>
#import "FKCar.h"

int main(int argc , char * argv[])
{
    @autoreleasepool{
        // 擷取FKCar類
        Class clazz = NSClassFromString(@"FKCar");
        // 動态建立FKCar對象
        id car = [[clazz alloc] init];
        // 使用performSelector:方法來動态調用方法
        [car performSelector:@selector(addSpeed:) 
            withObject: [NSNumber numberWithDouble:]];
        // 使用objc_msgSend()函數動态調用方法
        objc_msgSend(car , @selector(addSpeed:) , );
        // 定義函數指針變量
        double (addSpeed*)(id , SEL , double) ;
        // 擷取car對象的addSpeed:方法,并将該方法賦給addSpeed函數指針變量
        addSpeed = (double(*)(id ,SEL , double))[car
             methodForSelector:NSSelectorFromString(@"addSpeed:")];
        // 通過addSpeed函數指針變量來調用car對象的方法
        double speed = addSpeed(car , @selector(addSpeed:) , );
        // 輸出調用方法的傳回值
        NSLog(@"加速後的速度為:%g" , speed);
    }
}