3

I'm coding a library (Obj-C for iPhone) that I want to package and sell, so I obviously need to work out any design kinks before listing it for sale. I am also utilizing this library to help me develop another app.

My library is heavily built on task delegation. The primary function I have is to launch a (potentially) long-running process, and when it's done, I call a Delegate Protocol method in the class's delegate.

An additional complicating factor here is that I will often schedule this task to fire off every 30 seconds or so. Usually, I do this with [self performSelector:@selector(someMethod:) withObject:nil afterDelay:30] rather than using an NSTimer. Then, when the delegate method successfully returns, I process the returned data and trigger the method to fire in another 30 seconds. This gives me 30 seconds BETWEEN method calls, rather than 30 seconds FROM THE START OF ONE CALL TO THE NEXT. (This is mainly just in case the call ever takes more than 30 seconds, which shouldn't happen.)

The error that I'm catching is that sometimes, the Delegate callback method is failing with an EXC_BAD_ACCESS error. Based upon my investigation, it appears that the delegate of my class library has disappeared (been released/dealloced) since the long-running process was initiated. Thus, when it calls [[self Delegate] doSomeDelegateMethod], it's accessing a released object.

I tried first checking [[self Delegate] respondsToSelector:@selector(doSomeDelegateMethod)], but even that access apparently also throws the EXC_BAD_ACCESS.

It doesn't yet seem that checking for [self Delegate] == nil is the right way to go, either.

One way I think I have solved the problem, in this specific instance, is when the view controller that instantiates my object is disappearing (and therefore on its way to the garbage dump), I call [NSObject cancelPreviousPerformRequestsWithTarget:self]. This apparently fixes the problem. (Does this "fix" also indicate that my object "knows" about the call to come and keeps itself in memory until it can successfully, desperately, fire off its final shot?)

This appears to put a band-aid on a bullet wound. Yes, it appears to stop my app from breaking this time, but my gut tells me that this is a poor solution.

I've also considered setting the custom object to nil in my viewWillDisappear:animated: method, which is probably the correct coding pattern, but it doesn't seem right that the customer has to be so precise in handling my objects.

What's really bugging me, though, is that I haven't yet found a way, as a library developer, to "box in" my code so that it won't throw an exception for the user if they don't do just the right things. Basically, I'd like a way to have my object:

  1. Get a request.
  2. Go look for the answer.
  3. Find the answer.
  4. Try to return the answer.
  5. Realize that there's nothing on the other end.
  6. Give up and die on its own. (OK, so "die on its own" probably won't happen, but you get the point.)

One interesting side point:

A main reason I have for preventing this type of error from occurring is this:

I did the following steps:

  1. Built my library's .h/.m files.
  2. Generated my library's .a output file.
  3. Imported my library's .a/.h files into another project.
  4. Had the error described above.
  5. Got to peruse the code from one of the .m files that SHOULD have been hidden inside the .a file.

Am I missing something here? Am I really risking exposing my entire source code if it ever throws an error for a client? (This is just a side issue, but I'm fairly concerned here!)

Thanks for any help you can provide to help me be a better programmer!

---EDIT---

I have found another reason why this is important. In another view controller, where I am using this library, I implemented the NSTimer strategy. If the view is popped from the navigation stack (i.e., in the viewWillDisappear:animated: method), I invalidate said timer. So, no more calls will go to my library after the view disappears.

Here's the rub: what if the view disappears IN THE MIDDLE of the long-running call? Yes, it's tricky and unlikely to do, but I just had it happen on the simulator. In particular, THIS is why I'm looking for a workaround to let my code realize "hey, there's nothing on the other end of this pipe" and then fail gracefully. Anyone?

Thanks!

mbm29414
  • 11,558
  • 6
  • 56
  • 87

3 Answers3

3

There are several approaches to this problem:

  • The traditional delegate approach (UITableViewDelegate) makes it a requirement to clear yourself as delegate before going away. This is traditionally done in dealloc of the delegate with otherObject.delegate = nil. Failure to do so is a programming error. That's basically what you're seeing. This is the common pattern when the delegate and the delegator have basically the same lifespan.

  • Another approach is how NSURLConnection handles it: retain your delegate until you're done. The key to this working well is that NSURLConnection has a lifespan of its own, so the retain loop will work itself out automatically. UITableView could not retain its delegate because this would almost always create a permanent retain loop. If your object lives for a while and then goes away, then this makes sense. Typically here the delegate has a much shorter lifespan than the delegator, so the retain loop doesn't hurt anything.

Any object that calls performSelector:withObject:afterDelay: should always call cancelPreviousPerformRequestsWithTarget:self in its own dealloc. This has nothing to do with your delegate, though. It should be self-contained to the object itself. (I don't know why I keep thinking this is true, and then proving to myself again that it isn't. When you call performSelector:...afterDelay:, you are retained, so you can't deallocate before it fires. My SIDE NOTE, while true, isn't relevant here.)

SIDE NOTE cancelPrevious... is really expensive in my experience. If you have to call cancelPrvious... very often, I recommend keeping your own one-shot NSTimer and just resetting it when it fires to get the same effect. performSelector:withObject:afterDelay: is just a wrapper around a one-shot timer.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Rob, thanks for the quick reply. I'm not sure if it matters, but I'm using iOS 5 with ARC, and retain/release, etc... are not available to me. As to your first approach, since dealloc is no longer called under ARC, where/how would I enable that technique? – mbm29414 Jul 13 '11 at 00:59
  • As to your answer regarding `performSelect:withObject:afterDelay`, I think what I'm seeing is that my view controller is kept alive just long enough to fire off the `getAnswerWithDelegate:self` method and then it dies. Then, when the custom object tries to return the answer to its delegate, nothing is at the other end of the pipe. I'm not sure I yet understand how to remedy this, especially using ARC. – mbm29414 Jul 13 '11 at 01:03
  • I've used UITableViews and NSURLConnections (along with NSXMLParsers) and I've noticed that, for some reason, the methods associated with NSURLConnection seem to be "built-in", at least to view controllers. Why is it that I don't have to register for an NSURLConnectionDelegate protocol? (Not sure this is important, but it's buggin' me, too!) – mbm29414 Jul 13 '11 at 01:09
  • Regarding ARC, if you use a `weak` property for `delegate`, then when it is deallocated, the `delegate` property is set to `nil`. This is just an automatic behavior doing what we used to do manually. "Retain your delegate" is the same as a `strong` property. – Rob Napier Jul 13 '11 at 01:16
  • I don't know what you mean about "built-in" methods. There's no magical connection between view controllers and NSURLConnection. – Rob Napier Jul 13 '11 at 01:18
  • Well, when I instantiate an NSURLConnection and set `self` as delegate, I don't have to reference (for example) `` in order to get access to the NSURLConnection's delegate methods, right? – mbm29414 Jul 13 '11 at 01:20
  • Regarding `getAnswerWithDelegate:self` (use "fetch" rather than "get"; "get" means something else in ObjC), are you then storing `delegate` as a `weak` property? If so, then it will be `nil` when you go and look at it later. – Rob Napier Jul 13 '11 at 01:20
  • NSURLConnection's delegate is of type `id` rather than `id` for historical (ObjC 1.0) reasons. So you don't *have* to declare in order to keep the compiler happy. – Rob Napier Jul 13 '11 at 01:22
  • When I'm declaring a delegate using `id`, the compiler forces me to declare the instance variable as `__unsafe_unretained`. I'm used to declaring them without a qualifier and using `(nonatomic, assign)`. – mbm29414 Jul 13 '11 at 01:23
  • You need to use __weak as your qulaifier, not __unsafe_unretained. __unsafe_unretained is... unsafe, and is the cause of your problem (basically it's "assign"). You may be encountering un-implemented parts of the betas. You need to read carefully the ARC PG before using it: http://developer.apple.com/library/prerelease/ios/#documentation/General/Conceptual/ARCProgrammingGuide/Introduction.html – Rob Napier Jul 13 '11 at 13:47
0

Try setting Delegates to nil in dealloc.

example:

self.fetchedResultsController.delegate = nil;

I've seen this problem a lot lately and usually fix the problem. Even though delegates are supposed to be weak references, sometimes some private implementation is using them as well.

If I release, I get bad access, if I retain, I leak

That's where I had a similar problem.

Edit: When using ARC, you can still override dealloc for cleanup, you just can't call [super dealloc] or release anything.

Community
  • 1
  • 1
Myron Slaw
  • 886
  • 9
  • 21
  • Beyond the problem that I no longer have access to `dealloc` (see above: using ARC), I don't know in which dealloc you'd recommend placing the code above. In your example, what is `self`? – mbm29414 Jul 13 '11 at 01:06
  • Added the ARC rule to my answer – Myron Slaw Jul 18 '11 at 03:55
0

I'm answering myself because the page warned me to not have extended discussions in the comments... :)

OK, so it appears that part of my answer is that [self performSelector:withObject:afterDelay:] automatically retains my object until it gets to "fire that shot", at which point I'm guessing the view controller dies.

So, now it makes sense why my custom class is trying to access a released object when it tries to return its answer to its delegate, which is an __unsafe_unretained object, meaning that it can die at will (I think).

What I'd like now is a way to prevent this from causing an error. In .NET, I've got all sorts of error handling options to do this, but I'm unable to think of a fail-safe "bail out" here.

I've tried [[self Delegate] isKindOfClass:..., but can't be sure what kind of class the delegate will be, so it won't work.

I've also tried [[self Delegate] respondsToSelector:@selector(...)]. I'm not sure why this fails, but I get the EXC_BAD_ACCESS here, too.

What I don't want is my customers to be able to crash my product with such a simple, innocent mistake.

As an aside, does anyone know why this sort of failure gives me such easy access to the contents of the .m file that should be hidden inside my .a file? Did I build my library incorrectly?

Thanks!

mbm29414
  • 11,558
  • 6
  • 56
  • 87