Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

objc_setAssociatedObject function error in 64bit mode not in 32bit

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 32bit mode but when I run it on my iPhone 5S or in the Simulator in 4-inch 64bit mode, when you tap on a row to get the sub rows it crashes. I do not have any knowledge of the differences of 64bit and 32bit iOS systems. I would love to understand what it happening here.

You will notice that the *SubRowObjectKey is set to void- the error I get is: EXC_BAD_ACCESS_(code=EXC_I386_GPFLT)
Which is a general protection fault trying to access something that is not there(?)

When it crashes Xcode highlights this line of code:
objc_setAssociatedObject(self, SubRowObjectKey, subRowObj, OBJC_ASSOCIATION_ASSIGN);

Also there is this: Printing description of *(subRowObj): (id) [0] = Printing description of SubRowObjectKey:

It seems to be working great in 32bit mode but some how in 64bit it seems to loose where it is. Here below is the section of the code I am looking at.

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

You can download and play with the code here: https://github.com/sakkaras/SKSTableView

like image 945
tony.stack Avatar asked Feb 04 '14 19:02

tony.stack


2 Answers

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;
}
like image 173
Martin R Avatar answered Nov 16 '22 04:11

Martin R


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

like image 20
Tirupati Balan Avatar answered Nov 16 '22 05:11

Tirupati Balan