objc_setAssociatedObject function error in 64bit mode not in 32bit

后端 未结 3 1127
既然无缘
既然无缘 2020-12-15 11:48

I am using a neat table view controller called SKSTableView in my project which allows each table row to have a number of sub rows. This code works perfectly in 32

相关标签:
3条回答
  • 2020-12-15 12:22

    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

    0 讨论(0)
  • 2020-12-15 12:25

    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
    
    0 讨论(0)
  • 2020-12-15 12:26

    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;
    }
    
    0 讨论(0)
提交回复
热议问题