我在项目中遇到了这个问题,并将其归结为以下示例代码:
#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:
执行正常,并且没有任何内容记录到控制台。
- iterateWithSelf:
进入for循环,在控制台上打印一行,然后与EXC_BAD_ACCESS
我尝试插入... self.map.insert(std::pair<int, int>(0, 1));
似乎在_map.insert(std::pair<int, int>(0, 1));
向iterateWithIvar
添加迭代时没有任何影响
有什么想法吗?
答案 0 :(得分:5)
需要注意的重要一点是self.map
发送消息(即调用方法)。 它不访问结构成员。它类似于以下C ++代码:
class ViewController {
public:
typedef std::map<int, int> ExampleMap;
private:
ExampleMap _map;
public:
ExampleMap map() { return _map; }
setMap(const ExampleMap &o) { _map = o; }
};
接着是
for (auto iter = vc.map().begin(); iter != vc.map().end(); ++iter) {
std::cerr << "We should never hit this line as map is empty" << std::endl;
}
在这种情况下,这是“预期的行为”。 C ++对象按值复制。这意味着每次拨打self.map
时,您都会获得地图的新副本。因为从一个地图使用迭代器迭代第二个地图是无效的C ++,所以你会崩溃。
在第二种情况下,您可以直接访问实例变量,因此每次都要处理同一个对象。有几种解决方法:
重写您的课程,以提供直接访问ivar的访问者(例如-(int) numberOfItemsInMap
,-mapItemForKey:
等)。
更改访问者不再复制:使用ivar作为地图,手动编写自己的getter以返回指针或对地图的引用(如果使用指针,则可以将访问器声明为指针属性无论如何,但是然后使用operator new在你的构造函数中创建它。)
重写-iterateWithSelf:
以获取地图的显式副本,然后对其进行迭代(假设std::map
是您的用例的有效容器,这可能会效率低下)。
第一种可能是最干净的方法,因为它还确保您的ObjC对象始终了解对底层地图的访问/更改,而直接暴露指向地图的指针允许任何人在您不知情的情况下修改它。