objc_setAssociatedObject function error in 64bit mode not in 32bit

為{幸葍}努か 提交于 2019-11-29 00:09:57
Martin R

It seems that objc_setAssociatedObject() does not work with "tagged pointers". Tagged pointers are special pointers that do not point to something, but the "value" is stored in (some bits of) the pointer itself. See for example Tagged pointers in Objective-C, which has links to more information.

Tagged pointers are used with 64-bit code only, e.g. for index paths with small row and section numbers, or small number objects.

It can easily be verified that setting an associated object on a tagged pointer crashes with EXC_BAD_ACCESS on iOS 7/64-bit:

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) 

The same happens with

id obj = @1;
id obj = [NSDate dateWithTimeIntervalSinceReferenceDate:0];

which are also tagged pointers in 64-bit mode.

I could not find any documentation of this restriction and would consider this a bug in iOS. A similar problem was reported here: https://github.com/EmbeddedSources/iAsync/issues/22. I think this is worth a bug report to Apple.

(Remark: The problem does not occur on OS X/64-bit.)


Update (possible solution/workaround): The "SKSTableView" project (link at the end of the question) uses a category on NSIndexPath and associated objects to add a third property "subrow" to an index path. This subrow is set temporarily in

tableView:cellForRowAtIndexPath:

of SKSTableView to pass the current subrow to the

tableView:cellForSubRowAtIndexPath:indexPath

delegate method in the ViewController class.

If you use "proper" three-level index paths instead, the associated object is not needed and the sample app runs in 64-bit mode as well.

I made the following changes to the sample project:

In 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;
}

The property accessor methods

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

are not needed anymore.

In 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;
}

Change setting in below methods:

- (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);
}

Special thanks to @tufnica at https://github.com/sakkaras/SKSTableView/issues/2

I came across this as well but in a different scenario where I was needing to add a property to a tagged pointer type.

Here is what I came up with; it makes use of NSMapTable weakToStrongObjectsMapTable. It can even be used as a drop-in replacement for objc_setAssociatedObject/objc_getAssociatedObject for tagged pointers and regular object types:

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!