其實在日常iOS開發(fā)中啄栓,真正使用invocation的地方還是很少铁追,趁著這段時間公司項目空閑期咳燕,對之前的面試題做個簡單回憶總結(jié)勿决。
怎么實現(xiàn)performSelector支持2個以上的參數(shù)?
說說對isa指針的理解招盲?
常見的crash低缩?說說unrecognize selector to instance 具體發(fā)生過程
以上,其實都涉及到方法具體是如何調(diào)用執(zhí)行的曹货。以下都是對問題一做個簡單說明使用
使用一:實現(xiàn)performSelector傳入>2參數(shù)
OC里常用調(diào)用方法的幾種方式
// 方式一:中括號形式
[super viewDidLoad];
// 方式二:系統(tǒng)performSelector形式咆繁,相關(guān)的幾個api,但最多只支持2個參數(shù)
[obj performSelector:@selector(setName:) withObject:@"bfoOnline"];
// 方式三:NSMethodSignature + NSInvocation形式控乾,沒有參數(shù)限制
[obj performSelector:@selector(run:str2:str3:str4:) withObjects:arrArgs];
NSMethodSignature+ selector+ NSInvocation簡單說明
NSMethodSignature:方法簽名中保存了方法的名稱/參數(shù)/返回值么介,協(xié)同NSInvocation來進行消息的轉(zhuǎn)發(fā)。方法簽名一般是用來設(shè)置參數(shù)和獲取返回值的, 和方法的調(diào)用沒有太大的關(guān)系蜕衡。
selector :SEL類型 它簡單的唯一標(biāo)示了一個方法,扮演類似于一個動態(tài)函數(shù)指針设拟,可以簡單理解為一個唯一的id
NSInvocation:它是命令模式的一種實現(xiàn)慨仿,它包含選擇器、方法簽名纳胧、相應(yīng)的參數(shù)以及目標(biāo)對象镰吆。當(dāng)NSInvocation被調(diào)用,它會在運行時通過目標(biāo)對象去尋找對應(yīng)的方法跑慕,從而確保唯一性万皿。
方式三 selector+NSMethodSignature + NSInvocation使用
封裝在NSObject分類里的核心代碼(具體步驟和使用請看注釋)
// 擴展performSelector方法,支持多參數(shù)(系統(tǒng)最多只支持2個參數(shù))
- (id)performSelector:(SEL)aSel withObjects:(NSArray*)objects {
id res = nil;
// 1 根據(jù)sel實例化NSMethodSignature
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSel];
if (!signature) {
// 沒找到核行,拋出異常
@throw [NSException exceptionWithName:@"拋異常錯誤" reason:@"沒有這個方法牢硅,或這個方法名字錯誤" userInfo:nil];
return res;
}
// 2 創(chuàng)建invocation對象
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
// 3 設(shè)置調(diào)用者
[invocation setTarget:self];
// 4 設(shè)置selector
[invocation setSelector:aSel];
// 5 設(shè)置參數(shù)
NSUInteger argsCount = signature.numberOfArguments - 2; // 去掉默認(rèn)的2個參數(shù):self和_cmd(調(diào)用者target和selector)
NSUInteger arrCount = objects.count;
// 不能簡單的通過遍歷參數(shù)數(shù)組來設(shè)置參數(shù),因為外界傳進來的參數(shù)個數(shù)是不可控的芝雪,而是取上述兩者之間最小值
NSUInteger resultCount = MIN(argsCount, arrCount);
for (int i=0; i<resultCount; i++) {
id obj = objects[i];
if ([obj isKindOfClass:[NSNull class]]) {
obj = nil;
}
[invocation setArgument:&obj atIndex:i+2];
}
// 6 調(diào)用
[invocation invoke];
// 7 判斷當(dāng)前調(diào)用方法是否有返回值
if (signature.methodReturnLength != 0) {
[invocation getReturnValue:&res];
}
return res;
}
被調(diào)用的方法實現(xiàn)(暫時也封裝在該分類减余,在使用過程中 特別是消息轉(zhuǎn)發(fā)場景下 注意實現(xiàn)地方)
// 調(diào)用的方法實現(xiàn) (多參數(shù))
- (id)run:(NSString*)str1 str2:(NSString*)str2 str3:(NSString*)str3 str4:(NSString*)str4{
NSLog(@"%@-----%@-----%@-----%@",str1,str2,str3,str4);
return nil;
}
vc里簡單使用
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *obj = [NSObject new];
NSArray *arrArgs = @[@"Hello",@"how",@"are",@"you"];
[obj performSelector:@selector(run:str2:str3:str4:) withObjects:arrArgs];
}
運行結(jié)果
注意
參數(shù)個數(shù)計算問題,注意signature.numberOfArguments - 2惩系,傳入的參數(shù)需要和其保持一致位岔,只能小于其而不能大于它。
使用二: block+NSInvocation
api聲明部分
id invokeBlock(id block,NSArray *args);
@interface NSObject (Test)
@property (nonatomic,copy)NSString *name;
- (id)performSelector:(SEL)aSelector withObjects:(NSArray *)objects;
- (id)run:(NSString*)str1 str2:(NSString*)str2 str3:(NSString*)str3 str4:(NSString*)str4;
@end
核心代碼(NSObject+Test.m 全局實現(xiàn)部分)
id invokeBlock(id block,NSArray *args){
if (!block) {
return nil;
}
id target = [block copy];
const char *_Block_signature(void*);
const char *signature = _Block_signature((__bridge void*)target);
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:signature];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.target = target;
if ([args isKindOfClass:[NSArray class]]) {
NSInteger count = MIN(args.count, methodSignature.numberOfArguments-1);
for (int i=0; i<count; i++) {
const char *type = [methodSignature getArgumentTypeAtIndex:1+i];
NSString *typeStr = [NSString stringWithUTF8String:type];
if ([typeStr containsString:@"\""]) {
type = [typeStr substringToIndex:1].UTF8String;
}
if (strcmp(type, "@")==0) {
id argument = args[i];
[invocation setArgument:&argument atIndex:1+i];
}
}
}
[invocation invoke];
id returnVal;
const char *type = methodSignature.methodReturnType;
NSString *returnType = [NSString stringWithUTF8String:type];
if ([returnType containsString:@"\""]) {
type = [returnType substringToIndex:1].UTF8String;
}
if (strcmp(type, "@") == 0) {
[invocation getReturnValue:&returnVal];
}
return returnVal;
}
ViewController.m里面調(diào)用
- (void)viewDidLoad {
[super viewDidLoad];
// NSObject *obj = [NSObject new];
// NSArray *arrArgs = @[@"Hello",@"how",@"are",@"you"];
// [obj performSelector:@selector(run:str2:str3:str4:) withObjects:arrArgs];
// // 奔潰 :-[CFString release]: message sent to deallocated instance
// id returnValue = invokeBlock((id)^(NSString *a,NSString *b){return [NSString stringWithFormat:@"%@ and %@",a,b];}, @[@"01",@"bfo"]);
self.returnValue = invokeBlock((id)^(NSString *a,NSString *b){return [NSString stringWithFormat:@"%@ and %@",a,b];}, @[@"01",@"bfo"]);
NSLog(@"%@",self.returnValue);
}
注意
注意上面上面注釋的局部變量returnValue堡牡,若打開注釋抒抬,執(zhí)行程序會奔潰,顯示EXC_BAD_ACCESS內(nèi)存訪問錯誤晤柄。打開edit scheme里面zombie objects選項(如下圖設(shè)置)擦剑,再次運行 console里面會打印-[CFString release]: message sent to deallocated 相關(guān)錯誤,即訪問了已被釋放的對象
解決:改成如上內(nèi)存策略屬性,運行正常抓于。
可能原因:自定義的c語言函數(shù)做粤,沒有被自動釋放池包裹,當(dāng)函數(shù)返回值時捉撮,就已經(jīng)被釋放怕品。改成copy 屬性,引用的時候copy一份巾遭,即使之前那個被釋放肉康,也不會影響。
后續(xù):待源碼解讀支撐~