I've been over the CCTouchDispatcher code a few times looking for memory leaks people claim, and I've never been able to find one. But you've found a bug, well done. (Not a memory leak, but will easily cause an object to be referenced long after it's expected to be forgotten, which is the next best thing. ^_^)
You're calling addSprite from within a touch handler, aren't you? That's why your "expected rc" value doesn't match at 4. Your expectation there is only correct if we are not inside a touch handler. Inside a touch handler, removeDelegate causes a retain (as I mentioned earlier) which is balanced out after the touch handlers are done along with the other release you expect.
(As an aside here, your 'expected rc' values are your expectations based on how you think the internals of the code should work. I hope you realise that? They don't match because they don't reflect the actual internals of the code you're calling.)
The bug is that your object gets added to the "to be added' list (because we're inside a touch handler) and then added to the "to be removed" list (because we're inside a touch handler) and cocos2d then processes the "to be removed" list before the "to be added" list.
If we weren't inside a touch handler, we'd be adding the touch handler directly to the handlers list, then removing it from the same list, so no bug.
Instead we're (after touch handling is finished) removing it from the handlers list (it already wasn't there, so it's a no-op) and then adding it to the handlers list. That's why it sees the next touch, because it was left in the handlers list when it really shouldn't have been there at all.
So the bug only happens if you call add*Delegate: object and removeDelegate: object inside the same touch event.
Please logdge this one on the issue tracker, I've never seen it happen before, but it's not so far fetched that I believe it's never happened.
However, I'm not sure that this bug affects your original code. Your original code wasn't adding and removing the sprite in the same touch event, that I can see. If you could post the relevant parts of BoardCtr like you did for CButton, that'd probably help. And as far as I can see, the bug you've found _won't_ cause a crash. It'll cause an object to be kept alive too long, your crash is an object killed too soon.
Briefly, is CButton supposed to call its target in touchBegan? The name of the callback suggests it's expecting it to be in touchEnd. Also, your CButton will currently claim any touches on the screen, whether they hit the button or not.
Given the intermittent nature of the original crash, I'm somewhat mystified as to what the problem is. I also find your code to be rather hard to follow. If I was reviewing your code in person, I'd keep pointing at things and asking questions starting with "Why..."
I'm also going to pull you up on the following, because it's a common mistake:
3. “So you should be doing an [s release] at the end of addSprite”
--- This made me think hard. Should I?
Somehow, I like to keep the sprite and release it later myself. There are a few subtle reasons for that. I do not like the idea that it is at the mercy of other classes, which may or may not retain it. If I keep the sprite I do not have to trust or know internal implementation of library classes. They may change tomorrow.
There's a couple of problems here.
You're not keeping the sprite. Your reference to the sprite (s in the code in question) is a local variable, lost at the end of the function. So you're actually claiming ownership, and then forgetting about it, and later on getting it back some other way and releasing that ownership. So you're actually doing the opposite of what you say you want to do.
You're claiming ownership, then throwing that away, and relying on later on getting that object back to release. What if some mutation on that object causes you to get a copy of what was originally referenced in s back? You've just leaked the original object, and over-released the new object.
Also, "containers retain their contents" is not internal class implementation details. It's part of the API of that class. You don't program with protection in case NSArray's getObjectAtIndex: method starts requiring NSString instead of NSUInteger, do you? In this case, the rule is actually also Cocoa idiom, so when programming Cocoa it's the assumed case unless the class specifically claims to _not_ do that.
To do what you actually describe in that first sentence, use a "retain" declared property. That's pretty much what they're for.
On the note of Cocoa idiom and retained properties, it's normal and correct Cocoa idiom to _not_ retain one's delegate. Usually one's delegate is retaining you, or ensuring you are retained, so retaining one's delegate creates a circular reference. (This is the reason you use CCTouchDispatcher's add*Delegate and removeDelegate calls from onEnter and onExit, _not_ init and dealloc. If you do the latter, you get circular references and things responding to touches which you thought were deleted.)
Your whole approach to memory management concerns me as trying to take too much control. Memory management in Cocoa is a shared exercise. You may own an object, but you are almost certainly not the _only_ owner of an object, and insisting that you be so is liable to cause bugs and confusion. ^_^