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


【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,...)函數來調用。——第一個參數是方法調用者,第二個參數是調用的方法,接下來的參數將作為調用方法的參數。
Alt text
示例代碼:
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 = 0 ; i < num ; i++)
{
NSLog(@"%@", [NSString stringWithFormat
:@"汽車正在路上走~~%d" , i]);
}
}
- (double) addSpeed:(double) factor
{
// 此處希望能動態調用move方法
// 使用performSelector:動態調用move:方法
[self performSelector:@selector(move:)
withObject: [NSNumber numberWithInt: 2]];
[self performSelector:NSSelectorFromString(@"move:")
withObject: [NSNumber numberWithInt: 2]];
// 使用objc_msgSend()函數動態調用move:方法
objc_msgSend(self,@selector(move:),[NSNumber numberWithInt: 3]);
objc_msgSend(self , NSSelectorFromString(@"move:")
, [NSNumber numberWithInt: 3]);
NSLog(@"正在加速。。。%g" , factor);
return 100 * 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:3.4]];
// 使用objc_msgSend()函數動態調用方法
objc_msgSend(car , @selector(addSpeed:) , 3.4);
// 定義函數指針變量
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:) , 2.4);
// 輸出調用方法的返回值
NSLog(@"加速后的速度為:%g" , speed);
}
}

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
  © 2014-2022 ITdaan.com