返回

iOS 跨文件选择器调用失败问题解析与解决方案

IOS

iOS跨文件选择器调用失败问题解析

当在 iOS 开发中使用选择器 (Selectors) 作为回调机制时,常常会遇到“unrecognized selector sent to instance”的崩溃问题。这种错误通常发生在尝试在一个对象上调用另一个类中定义的方法时,而该对象却无法识别这个选择器。理解问题的根本原因以及有效的解决方法,有助于避免类似错误,保证代码的健壮性。

问题分析

这种错误通常意味着 performSelector: 方法无法在接收调用的对象(如:VuqioApi的实例)上找到指定的选择器 (如:postCurrenProgramRequestDidEnd)。根本原因在于选择器调用的对象 self 和包含方法定义的对象不是同一个。

具体来说,在这个例子中,选择器 postCurrenProgramRequestDidEnd 方法定义在 Controller.m 文件(可以理解成某个 Controller 类)中,但是方法却尝试在 VuqioApi 的实例对象上调用 performSelector:. 由于 VuqioApi 类没有该方法,因此产生未识别的选择器错误。performSelector: 不会去查找调用者本身,只能找到接受对象实现的方法。

解决方案

以下提供几种解决 “无法执行另一个文件选择器” 的常见方案。

1. 使用 Blocks 代码块

Blocks 是 C 语言的扩展,提供了匿名函数的功能。它可用于代替选择器作为回调函数,有效解决选择器跨文件调用的问题。相较于 selector ,Block 会捕获执行环境上下文,可以在定义它的环境中找到所需要访问的方法和变量。

步骤:

  1. VuqioApi.h 文件中声明一个 Block 类型属性,用于接收回调。
  2. 在调用处,创建一个 Block 作为回调传入。
  3. 在 API 类执行网络请求完成后执行传入的 Block。

代码示例:

  • VuqioApi.h

    typedef void(^RequestSuccessBlock)();
    typedef void(^RequestFailureBlock)();
    
    @interface VuqioApi : NSObject
    
    @property (nonatomic, copy) RequestSuccessBlock successCallback;
    @property (nonatomic, copy) RequestFailureBlock failureCallback;
    
    
    - (void)postCurrentProgram:(NSDictionary *)data withSuccess:(RequestSuccessBlock)successCallback andFailure:(RequestFailureBlock)failureCallback;
    - (void)postCurrentProgram:(NSDictionary *)data;
    
    - (void) defaultFailureCallback;
    
    @end
    
    
  • VuqioApi.m

 - (void)postCurrentProgram:(NSDictionary *)data withSuccess:(RequestSuccessBlock)successCallback andFailure:(RequestFailureBlock)failureCallback{
 
     self.successCallback = successCallback;
     self.failureCallback = failureCallback;

     [self postCurrentProgram:data];

 }


 - (void) postCurrentProgram:(NSDictionary *)data  {
    [self placePostRequest:@"api/programcurrent" withData:data withHandler:^(NSURLResponse *urlResponse, NSData *rawData, NSError *error) {
        
       NSString *string = [[NSString alloc] initWithData:rawData encoding:NSUTF8StringEncoding];

        if (![string isEqual: @"ok"])
         {
             if (self.failureCallback) {
               self.failureCallback();
             }
         }
        else {
            NSLog(@"OK");
            if(self.successCallback){
               self.successCallback();
            }
          
        }
     }];
  }

  - (void) defaultFailureCallback {
     NSLog(@"Failure");
  }
  • Controller.m
 -(void)softCheckIn:(NSString *)userId inProgram:(NSString *)program {
 
    // Hacemos un soft checkin
     VuqioApi *api = [[VuqioApi alloc] init];
     NSMutableDictionary *data = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
                                                     userId, @"userId",
                                                     program, @"programId",
                                                     nil];

    [api postCurrentProgram:data withSuccess:^{
           [self postCurrenProgramRequestDidEnd];

    } andFailure:^{
      NSLog(@"soft check in fail");
       [self defaultFailureCallback];
    }];
}

 -(void)postCurrenProgramRequestDidEnd
 {
     NSLog(@"Soft check-in");
 }


-(void) defaultFailureCallback {
    NSLog(@"Failure Controller");
}

2. 使用 Delegate 委托

委托模式是一种允许对象将某些任务的责任委托给其他对象的模式。这种模式能够解耦对象之间的依赖关系。在 VuqioApi 网络请求完成时通知它的委托对象,该对象处理具体回调逻辑。

步骤:

  1. 定义一个 VuqioApiDelegate 协议。
  2. 让 Controller 遵守 VuqioApiDelegate
  3. VuqioApi 对象持有委托对象的引用。
  4. 当网络请求完成后,VuqioApi 调用委托协议中对应的方法,完成回调。

代码示例:

  • VuqioApi.h
    @class VuqioApi;
    @protocol VuqioApiDelegate <NSObject>
    
    - (void) postCurrenProgramRequestDidEnd:(VuqioApi *)api;
    - (void) defaultFailureCallback:(VuqioApi *)api;
    
    
    @end
    
    
    @interface VuqioApi : NSObject
    
    @property (nonatomic,weak) id<VuqioApiDelegate> delegate;
    
    
    - (void)postCurrentProgram:(NSDictionary *)data ;
    
    @end
    
  • VuqioApi.m
     - (void) postCurrentProgram:(NSDictionary *)data  {
      [self placePostRequest:@"api/programcurrent" withData:data withHandler:^(NSURLResponse *urlResponse, NSData *rawData, NSError *error) {
    
           NSString *string = [[NSString alloc] initWithData:rawData encoding:NSUTF8StringEncoding];
          if ( ![string isEqual: @"ok"])
           {
                if(self.delegate && [self.delegate respondsToSelector:@selector(defaultFailureCallback:)]){
                      [self.delegate defaultFailureCallback:self];
                 }
    
            }
            else {
               NSLog(@"OK");
               if(self.delegate && [self.delegate respondsToSelector:@selector(postCurrenProgramRequestDidEnd:)])
               {
                     [self.delegate postCurrenProgramRequestDidEnd:self];
               }
    
            }
         }];
     }
    
  • Controller.m
    #import "VuqioApi.h"
    
    @interface Controller()<VuqioApiDelegate>
    
    
    @end
    
     @implementation Controller
    
     -(void)softCheckIn:(NSString *)userId inProgram:(NSString *)program {
    
        // Hacemos un soft checkin
         VuqioApi *api = [[VuqioApi alloc] init];
          api.delegate = self;
         NSMutableDictionary *data = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
                                                          userId, @"userId",
                                                         program, @"programId",
                                                          nil];
        [api postCurrentProgram:data];
    
      }
    
    
    
      - (void) postCurrenProgramRequestDidEnd:(VuqioApi *)api
     {
        NSLog(@"Soft check-in delegate");
      }
    
    
       - (void) defaultFailureCallback:(VuqioApi *)api{
       NSLog(@"Failure Delegate");
       }
    
    
     @end
    
    

3. 直接传递目标对象和选择器

当 Block 或者 delegate 的方案对场景不是最优选择时(如需要在方法中实现高度定制化的选择器转发行为),还可以使用这种方法,传递一个包含选择器和目标对象的结构。该方案在 performSelector: 不确定回调方法接受者的时候有效。

步骤:

  1. 在调用时同时传递目标对象 self 和选择器,并修改 postCurrentProgram 方法参数,接受该参数
  2. 在API中判断目标对象是否存在并执行 performSelector: 调用。

代码示例:

  • VuqioApi.h
    
    @interface VuqioApi : NSObject
    
    
    - (void)postCurrentProgram:(NSDictionary *)data withTarget:(id)target andSuccess:(SEL)successCallback andFailure:(SEL)failureCallback;
    
     - (void) defaultFailureCallback;
    
    @end
    
    
  • VuqioApi.m
      - (void)postCurrentProgram:(NSDictionary *)data withTarget:(id)target andSuccess:(SEL)successCallback andFailure:(SEL)failureCallback{
    
        [self placePostRequest:@"api/programcurrent" withData:data withHandler:^(NSURLResponse *urlResponse, NSData *rawData, NSError *error) {
    
              NSString *string = [[NSString alloc] initWithData:rawData encoding:NSUTF8StringEncoding];
    
            if (![string isEqual: @"ok"])
            {
                 if([target respondsToSelector:failureCallback]){
                    [target performSelector:failureCallback withObject:self];
                 }
    
              } else {
                  NSLog(@"OK");
    
               if ([target respondsToSelector:successCallback]) {
                 [target performSelector:successCallback];
               }
    
               }
         }];
    
    }
    
    
    - (void) defaultFailureCallback {
       NSLog(@"Failure API");
    }
    
    
  • Controller.m
    #import "VuqioApi.h"
    @interface Controller ()
    
    @end
    
    @implementation Controller
    
    
    -(void)softCheckIn:(NSString *)userId inProgram:(NSString *)program {
    
       // Hacemos un soft checkin
       VuqioApi *api = [[VuqioApi alloc] init];
         NSMutableDictionary *data = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
                                                     userId, @"userId",
                                                    program, @"programId",
                                                    nil];
    
        [api postCurrentProgram:data withTarget:self andSuccess:@selector(postCurrenProgramRequestDidEnd) andFailure:@selector(defaultFailureCallback)];
    
     }
    
     -(void)postCurrenProgramRequestDidEnd
      {
       NSLog(@"Soft check-in");
       }
     - (void) defaultFailureCallback {
        NSLog(@"Failure Controller");
       }
    @end
    

总结

理解“unrecognized selector sent to instance”错误的根源对于有效地解决这类问题至关重要。Block, Delegate 和直接传递目标对象这三种方式都能解决跨文件调用选择器失败问题,但它们各有适用场景。开发者应根据项目的实际情况,选择最合适的方案来处理回调,保证代码质量与可维护性。选择哪种方案还依赖于你项目的架构和需要实现的目标,以上方案基本能涵盖绝大多数回调的使用场景。