__unsafe_unretained 和 __weak 都阻止了对象的保留,但方式略有不同。对于 __weak,指向对象的指针将在释放它指向的对象时转换为 nil,这是非常安全的行为。顾名思义,__unsafe_unretained 将继续指向对象所在的内存,即使在对象被释放后也是如此。由于访问该解除分配的对象,这可能会导致崩溃。
在 BWDB 示例中有一行存储到枚举函数中的实例变量enumRows
- (NSUInteger) countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained *)stackbuf count:(NSUInteger)len
{
if ((*enumRows = [self getPreparedRow]))
{
state->itemsPtr = enumRows;
state->state = 0; // not used, customarily set to zero
state->mutationsPtr = state->extra; // also not used, required by the interface
return 1;
} else {
return 0;
}
}
并且enumRows定义为:
__unsafe_unretained NSDictionary * enumRows[1];
方法 GetPreparedRow 声明如下:
- (NSDictionary *) getPreparedRow
{
int retCode = sqlite3_step(m_statement);
if (retCode == SQLITE_DONE)
{
sqlite3_finalize(m_statement);
return nil;
}
else if (retCode == SQLITE_ROW)
{
int col_count = sqlite3_column_count(m_statement);
if (col_count >= 1)
{
NSMutableDictionary * dRow = [NSMutableDictionary dictionaryWithCapacity:1];
for(int i = 0; i < col_count; i++)
{
NSString * columnName = [NSString stringWithUTF8String:sqlite3_column_name(m_statement, i)];
[dRow setObject:[self columnValue:i] forKey:columnName];
}
return dRow;
}
}
else
{
NSLog(@"rowFromPreparedQuery: could not get row: %s", sqlite3_errmsg(m_database));
return nil;
}
return nil;
}
所以基本上从 GetPreparedRow 方法返回的NSDictiornay * 存储到 __unsafe_unretained 实例变量enumRows中。随着GetPreparedRow完成,NSDictionary * 被释放,因为它超出了范围,但enumRows仍然指向这个内存。因此可以枚举行,但每个当前行都指向无效内存。
我不确定为什么这在 Debug 和 Release/Simulator 中有效,但可能是内存没有立即被擦除或覆盖,所以enumRows仍然指向有效内存。我写信给 Bill Weinman,他说只有最新版本的 LLVM 编译器才会发生这种情况,并且只有优化开启。如果您有兴趣听到他修复它,请关注他的Facebook 页面。
同时,我通过使enumRows成为 __strong 所有权在我的代码中修复了它,简单地定义为:
NSDictionary * enumRows;
我改变了枚举函数只是为了设置一个指向这个 __strong enumRows指针的 __unsafe_unretained 指针
- (NSUInteger) countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained *)stackbuf count:(NSUInteger)len
{
if ((enumRows = [self getPreparedRow]))
{
__unsafe_unretained id row = enumRows;
state->itemsPtr = &row;
state->state = 0; // not used, customarily set to zero
state->mutationsPtr = state->extra; // also not used, required by the interface
return 1;
} else {
return 0;
}
}