在64位模式下出现了objc_setAssociatedObject函数错误,但在32位模式下没有。

9
我在项目中使用了一个整洁的表视图控制器,叫做SKSTableView,它允许每个表行有许多子行。这段代码在32位模式下完美运行,但是当我在我的iPhone 5S或模拟器中以4英寸64位模式运行时,当你点击一行获取子行时程序会崩溃。我不知道64位和32位iOS系统的区别,希望能够理解这里发生了什么。
您将注意到*SubRowObjectKey设置为void-我收到的错误是: EXC_BAD_ACCESS_(code=EXC_I386_GPFLT)
这是一种通用保护故障,试图访问不存在的内容(?)

当它崩溃时,Xcode会突出显示以下代码:

objc_setAssociatedObject(self, SubRowObjectKey, subRowObj, OBJC_ASSOCIATION_ASSIGN);

还有这个: Printing description of *(subRowObj): (id) [0] = Printing description of SubRowObjectKey:

它似乎在32位模式下工作得很好,但是在64位模式下它似乎会迷失方向。下面是我看到的代码部分。

#pragma mark - NSIndexPath (SKSTableView)
static void *SubRowObjectKey;
@implementation NSIndexPath (SKSTableView)
@dynamic subRow;
- (NSInteger)subRow
{
    id subRowObj = objc_getAssociatedObject(self, SubRowObjectKey);
    return [subRowObj integerValue];
}
- (void)setSubRow:(NSInteger)subRow
{
    if (IsEmpty(@(subRow))) {
        return;
    }
    id subRowObj = @(subRow);
    objc_setAssociatedObject(self, SubRowObjectKey, subRowObj, OBJC_ASSOCIATION_ASSIGN);
}
+ (NSIndexPath *)indexPathForSubRow:(NSInteger)subrow inRow:(NSInteger)row inSection:(NSInteger)section
{
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
    indexPath.subRow = subrow;   
    return indexPath;
}

您可以在此处下载并使用代码: https://github.com/sakkaras/SKSTableView

3个回答

14

看起来 objc_setAssociatedObject() 在 "tagged pointers" 上无法工作。 "Tagged pointers" 是一种特殊的指针,它们并不是指向某个位置,而是将“值”存储在指针本身的某些位中。 例如,请参阅Objective-C中的标记指针,其中包含更多信息的链接。

"Tagged pointers" 仅用于64位代码,例如具有小行号和区段号或小数字对象的索引路径。

可以轻松验证,在iOS 7 / 64位上,在标记指针上设置相关对象会导致EXC_BAD_ACCESS崩溃:

static void *key = &key;

id obj = [NSIndexPath indexPathForRow:1 inSection:1];
objc_setAssociatedObject(obj, key, @"345", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// --> EXC_BAD_ACCESS_(code=EXC_I386_GPFLT) 

同样的情况也会发生在

id obj = @1;
id obj = [NSDate dateWithTimeIntervalSinceReferenceDate:0];
在64位模式下也被标记为指针。我找不到有关此限制的任何文档,并认为这是iOS中的错误。这里报告了类似的问题:https://github.com/EmbeddedSources/iAsync/issues/22。我认为这值得向Apple报告错误。
(备注:在OS X / 64位上不会出现此问题。) 更新(可能的解决方案/解决方法):“ SKSTableView”项目(问题结尾处的链接)使用 NSIndexPath 和相关对象的类别将第三个属性“ subrow”添加到索引路径中。这个subrow是暂时设置的。
tableView:cellForRowAtIndexPath:
SKSTableView的作用是将当前子行传递给。
tableView:cellForSubRowAtIndexPath:indexPath

ViewController 类中的委托方法。

如果您使用“正式”的三级索引路径,则不需要关联对象,示例应用程序也可以在 64 位模式下运行。

我对示例项目进行了以下更改:

SKSTableView.m 中:

+ (NSIndexPath *)indexPathForSubRow:(NSInteger)subrow inRow:(NSInteger)row inSection:(NSInteger)section
{
    // Change:
    //NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
    //indexPath.subRow = subrow;
    // To:
    NSUInteger indexes[] = { section, row, subrow };
    NSIndexPath *indexPath = [NSIndexPath indexPathWithIndexes:indexes length:3];

    return indexPath;
}

属性访问方法

- (NSInteger)subRow;
- (void)setSubRow:(NSInteger)subRow;

不再需要。

ViewController.m 文件中:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForSubRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"UITableViewCell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (!cell)
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];

    // Change:
    //cell.textLabel.text = [NSString stringWithFormat:@"%@", self.contents[indexPath.section][indexPath.row][indexPath.subRow]];
    // To:
    cell.textLabel.text = [NSString stringWithFormat:@"%@", self.contents[[indexPath indexAtPosition:0]][[indexPath indexAtPosition:1]][[indexPath indexAtPosition:2]]];

    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    return cell;
}

您是否使用错误报告工具提交了错误报告? - David H
@DavidH:还没有,我刚刚成功地找到了导致 OP 项目崩溃的原因。 - Martin R
即使 objc_setAssociatedObject() 可以使用标记指针,它也不能实现您想要的效果。在您的情况下,结果将是具有相同 subRow 值关联的每个具有该路径值的 NSIndexPath 看起来是相同的。您无法同时拥有 row=1, section=1, subrow=1 的 NSIndexPath 和 row=1, section=1, subrow=2 的另一个 NSIndexPath。 - Greg Parker
@MartinR 感谢您详细而清晰的回复。我将在我的项目中实施这些更改并重新测试应用程序。您可以向苹果报告此错误以确保清晰。再次感谢! - tony.stack
这个解决方案不适用于UITableView类别的NSIndexPath:出现了断言“无效的索引路径,不能与UITableView一起使用。传递给表视图的索引路径必须恰好包含指定部分和行的两个索引。如果可能,请在UITableView.h中使用NSIndexPath的类别。” - Dmitry
显示剩余3条评论

5

在以下方法中更改设置:

- (NSInteger)subRow;
- (void)setSubRow:(NSInteger)subRow;

- (NSInteger)subRow
{
    id myclass = [SKSTableView class];

    id subRowObj = objc_getAssociatedObject(myclass, SubRowObjectKey);
    return [subRowObj integerValue];
}

- (void)setSubRow:(NSInteger)subRow
{
    id subRowObj = [NSNumber numberWithInteger:subRow];
    id myclass = [SKSTableView class];
    objc_setAssociatedObject(myclass, nil, subRowObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

特别感谢@tufnica在https://github.com/sakkaras/SKSTableView/issues/2上的贡献。


只有一个类对象 [SKSTableView class]。如果您使用它来存储子行号,那么您也可以使用全局变量(没有任何关联的对象)。 - Martin R

0

我也遇到了类似的情况,但是场景不同,我需要向标记指针类型添加一个属性。

这是我想出来的方法;它使用了NSMapTable weakToStrongObjectsMapTable。它甚至可以用作objc_setAssociatedObject/objc_getAssociatedObject的替代品,适用于标记指针和常规对象类型:

static NSString* SomeVariableKey = @"SomeVariableKey";

@interface NSObject (SomeAddition)

- (void)setSomeVariable:(NSObject*)var {
    [[APObjectAssociationManager defaultManager] setValue:var forKey:SomeVariableKey forAssociatedObject:self];
}

- (NSObject*)someVariable {
    return [[APObjectAssociationManager defaultManager] valueForKey:SomeVariableKey forAssociatedObject:self];
}

@end


@interface APObjectAssociationManager ()

@property (nonatomic, strong) NSMapTable *associations;

@end

@implementation APObjectAssociationManager


- (id)init {
    self = [super init];

    if (self) {
        self.associations = [NSMapTable weakToStrongObjectsMapTable];
    }

    return self;
}

+ (APObjectAssociationManager*)defaultManager {
    static APObjectAssociationManager *instance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[APObjectAssociationManager alloc] init];
    });

    return instance;
}

- (void)setValue:(id)value forKey:(NSString *)key forAssociatedObject:(NSObject*)associatedObject {


    NSMutableDictionary *dictionary = [self.associations objectForKey:associatedObject];

    if (!dictionary) {
        dictionary = [NSMutableDictionary dictionary];
        [self.associations setObject:dictionary forKey:associatedObject];
    }

    [dictionary setValue:value forKey:key];
}

- (id)valueForKey:(NSString *)key forAssociatedObject:(NSObject*)associatedObject {

    NSDictionary *dictionary = [self.associations objectForKey:associatedObject];
    return dictionary[key];
}

@end

我喜欢这个想法,但是当使用被标记指针的对象时,弱键是否会变得无用?我的印象是,被标记的指针永远不会被认为已经被释放,因此一旦插入值,NSMapTable就永远不会释放它们。 - John Estropia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接