I have ran into this problem on a project and have boiled it down to the following sample code:
#import <map>
typedef std::map<int, int> exampleMap;
@interface ViewController ()
@property (nonatomic, assign) exampleMap map;
@end
@implementation ViewController
- (IBAction)iterateWithSelf:(id)sender
{
for (exampleMap::iterator iter = self.map.begin(); iter != self.map.end(); ++iter)
{
NSLog(@"We should never hit this line as map is empty");
}
}
- (IBAction)iterateWithIVar:(id)sender
{
for (exampleMap::iterator iter = _map.begin(); iter != _map.end(); ++iter)
{
NSLog(@"We should never hit this line as map is empty");
}
}
@end
- iterateWithIVar:
executes fine and nothing is logged to the console.
- iterateWithSelf:
goes into the for loop, prints a line to the console and then crashes with EXC_BAD_ACCESS
I then tried inserting... self.map.insert(std::pair<int, int>(0, 1));
appears to affect nothing while _map.insert(std::pair<int, int>(0, 1));
adds an iteration to iterateWithIvar
Any ideas?
The important thing to note is that
self.map
sends a message (i.e. calls a method). It does not access a structure member. It’s like the following C++ code:followed by
That being the case, this is "expected behaviour". C++ objects are copied by value. Which means that every time you call
self.map
you get a new copy of your map. As it is invalid C++ to use iterators from one map to iterate over a second map, you get a crash.In the second case, you directly access the instance variable, so you're dealing with the same object every time. There are several workarounds:
Rewrite your class to provide accessors (e.g.
-(int) numberOfItemsInMap
,-mapItemForKey:
etc.) which directly access the ivar.Change the accessors to no longer copy: Use an ivar for the map, manually write your own getter to return a pointer or reference to the map (If you use a pointer, you can declare the accessors as pointer properties anyway, but then use operator new to create it in your constructor).
Rewrite
-iterateWithSelf:
to take an explicit copy of the map and then iterate over that (this is probably going to be inefficient, assuming thatstd::map
is an efficient container for your use-case).The first is probably the cleanest approach, as it also ensures your ObjC object is always aware of accesses/changes made to the underlying map, whereas directly exposing a pointer to the map allows anyone to modify it without your knowledge.