Behavior difference between UIView.subviews and [N

2019-04-07 04:53发布

问题:

I have a piece of code in an iPhone app, which removes all subviews from a UIView subclass. It looks like this:

NSArray* subViews = self.subviews;
for( UIView *aView in subViews ) {
    [aView removeFromSuperview];
}

This works fine. In fact, I never really gave it much thought until I tried nearly the same thing in a Mac OS X app (from an NSView subclass):

NSArray* subViews = [self subviews];
for( NSView *aView in subViews ) {
    [aView removeFromSuperview];
}

That totally doesn’t work. Specifically, at runtime, I get this:

*** Collection <NSCFArray: 0x1005208a0> was mutated while being enumerated.

I ended up doing it like so:

NSArray* subViews = [[self subviews] copy];
for( NSView *aView in subViews ) {
    [aView removeFromSuperview];
}
[subViews release];

That's fine. What’s bugging me, though, is why does it work on the iPhone?

subviews is a copy property:

@property(nonatomic,readonly,copy) NSArray *subviews;

My first thought was, maybe @synthesize’d getters return a copy when the copy attribute is specified. The doc is clear on the semantics of copy for setters, but doesn’t appear to say either way for getters (or at least, it’s not apparent to me). And actually, doing a few tests of my own, this clearly does not seem to be the case. Which is good, I think returning a copy would be problematic, for a few reasons.

So the question is: how does the above code work on the iPhone? NSView is clearly returning a pointer to the actual array of subviews, and perhaps UIView isn’t. Perhaps it’s simply an implementation detail of UIView, and I shouldn’t get worked up about it.

Can anyone offer any insight?

回答1:

This is a bug in -[NSView subviews]. I could have sworn it was fixed for Snow Leopard, but can't find any evidence now.

Fortunately, there's a simpler way to remove all subviews:

[self setSubviews:[NSArray array]];


回答2:

Better use

while([self.subviews count] > 0){
    [[self.subviews objectAtindex:0] removeFromSuperview];
}

Hope this help.



回答3:

Yes, it is just how Apple implemented it. On the mac, subviews returns the actual (mutable) array used by the view, so you cannot use fast enumeration while adding or removing views. If you want to use the reverseObjectEnumerator, you might be able to. Do not use the normal enumerator because it might skip subviews as they get moved up to replace those removed. In iOS, however, Apple seems to have realized this problem and fixed it by returning a copy automatically. The property declaration only describe how it is set, but apple chose to copy for the getter too.