ObjectiveC回调

Objective-c 之 回调模型问题

 release 一直都是IOS开发人员最为头疼的事情。 一旦过度释放,App立即"死"给你看。 而console报的信息总是那么让人难以跟进。

 这里谈谈自己在实际写代码时遇到的一个问题。

 

需求是这样的: 系统需要实现与服务端的Http交互。为了在UI体验上更友好一点,一般都是选择使用异步请求。

 (或者用新线程发起同步请求)

 下面是我之前的一个实现。

 

  Controller 调用Model发送异步请求,提供回调方法,向用户反馈结果。

 Model调用 Http 类,发起异步请求, 提供回调方法,处理返回数据。

处理完毕后,再调用controller回调方法,提供最终结果数据。

  (因服务端返回的数据可能并不适合直接展示给用户。例如,400错误,5xx错误,或者一些需要另加修饰的其他系统错误,数据结构需要转换等。。。因此在model层做了预处理之后才交回给controller)

 

  大致的类实现如下:

 @interface MainPageController : UIViewController{

       UserModel* model;

}

 

@implementation MainPageController{

   -(void) getUserInfo{

       [model doQueryUser:@”123” delegate:self callbackMethod:@”getUserInfoDone:”];

   }

 

 -(void)dealloc{

   

    [model release];    //3)

    [super dealloc];

  }

}

 

@interface MainPageController : UIViewController{

       NSMutableDictionary* requestInfo;  //用以记录controller回调信息

}

 

 

@implementation UserModel

 

-(void) doQueryUser:(NSString*) uId  delegate:(id)delegate callbackMethod:(NSString*)callbackMethod{

      

       NSString* reqURL = @"……….";

       NSDictionary* params = …;

 

 

       [HttpWrapper* wrapper = [HttpWrapper sendAsyncReq: reqURL reqMethod:@"POST"  params:params delegate:self  callbackMethod:@"doQueryUserDone:result"];

 

     [self saveCallbackInfo: wrapper target: delegate selector: callbackMethod];

 

}

 

 

-(void) doQueryUserDone:(HttpWrapper*)wrapper result:(HTTPResult*)result{

    id delegate;

    SEL callbackMethod;

       [self getCallbackInfo: wrapper target: &delegate selector:&callbackMethod];

     //2)

     //model handle result

     // …..

 

    //回调controller的 方法

    [delegate performSelector: callbackMethod withObject:result];

 

 

}

 

 

-(void) saveCallbackInfo:(id)request target:(id)target selector:(NSString*) selector{

   

    NSDictionary* value = [NSDictionary dictionaryWithObjectsAndKeys:target, @"target", selector, @"callback",nil];

    [requestInfo setValue:value forKey:[NSString stringWithFormat:@"%x", request]];

   

}

 

-(void) getCallbackInfoAndRemove:(id)request target:(id*)target selector:(SEL*)selector{

    NSDictionary* value = [requestInfo objectForKey:[NSString stringWithFormat:@"%x", request]];

    *target = delegate;

    NSString* action = [value objectForKey:@"callback"];

    *selector = NSSelectorFromString(action);

 

   

    [requestInfo removeObjectForKey:[NSString stringWithFormat:@"%x", request]];     //1)

}

 

 

问题: 上述类结构表面上看,似乎没什么问题,经初步测试也一切正常。心里正得意,交给测试人员。

       测试人员一般都是一些“不正常”的人. 拿到App后随便点点几下,App竟然闪退了。

       闪退。。。这是个头疼的问题。

具体操作是这样的, 进入一个界面.(程序推入push一个新的controller),controller自动加载数据,界面显示 activityIndicator

 controller 未拿到返回数据之前,按返回,回到上一级界面(pop controller)。等一会刚才那个controllermodel返回数据时,App就挂掉了。

   

原因: 经模拟器调试,每次重复同样操作,断点都是停在2) 处理。看变量内容,似乎是已经released掉了,但为何会released掉?

如果是因为controller返回后,controller的dealloc 3) 处 释放了model, 但为何model的回调方法还能被执行?难道是执行到一半的时候恰好被释放掉?这个。。。接下来继续重复测试,还是同样的结果。结论,不可能是恰巧。没理由那么多次都恰巧。(但是不是存在这样的可能,这个,本人偷懒,到现在依然没去证实。) 那为何会被释放? 重新检查了所有代码,都没有任何地方有调用[model release],看来也不是错误过度释放的问题。

经冥思苦想,终于想到个好方法,打印log,具体点说就是打印[model retainCount]. 在所有关键行都插入打印语句。

这个。。。后来证实依旧不可靠。具体大家可以去搜搜有关retainCount的一些文章,在此不再重复。反正最后结论就是,千万不要指望retainCount能准确告诉你,你的对象什么时候被引用了,什么时候被release了,什么时候又retainCount=0了,要被释放了,起码在你需要把它加入到系统的一些类中去的时候。

正在苦逼的时候,反复的测试倒也给了些提示:每次都是在执行完1)之后,就会出错。

经慢慢冷静,整理清整个调用顺序后,终于明白了问题。

首先,进入controller之后,调用model发送了异步请求.在请求未返回之后退出。

返回后,controller被pop出。retainCount并不等於0,因在model中用于记录回调信息 的 requestInfo 对象中有引用。

 问题就在于此。http 请求返回后,model 回调方法doQueryUserDone 需要从 requestInfo 中取controller 的回调信息,取到后清除掉(见1)处代码)。

当1)执行完后,requestInfo释放掉controller引用。 这时controller retainCount ==0 ,系统开始执行controller的dealloc方法。其中会调用model release。 这样,在代码2)处,就会遇到还在执行中,但类对象已释放的情况。结果只有一个,就是闪退。

 

这个最后说白了,就是犯了个controller 和model 相互引用的问题。 表面看似正常的逻辑,背后是不正常的引用关系。

因异步请求 和 模型中预处理数据 都是一些比较常见的需求。特写此文,以提醒大家不要犯作者这种错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值