Ошибка функции objc_setAssociatedObject в 64-битном режиме, а не в 32-битном

Я использую аккуратный контроллер табличного представления под названием 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);

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

Вы можете скачать и поиграть с кодом здесь: https://github.com/sakkaras/SKSTableView

3 ответа

Решение

Кажется, что objc_setAssociatedObject() не работает с "помеченными указателями". Помеченные указатели являются специальными указателями, которые не указывают на что-то, но "значение" хранится в (некоторых битах) самого указателя. См., Например, теговые указатели в Objective-C, в которых есть ссылки на дополнительную информацию.

Помеченные указатели используются только с 64-битным кодом, например, для индексных путей с небольшими номерами строк и разделов или с небольшими объектами.

Можно легко проверить, что установка связанного объекта на теговом указателе завершается с помощью EXC_BAD_ACCESS в 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) 

То же самое происходит с

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

которые также помечены указателями в 64-битном режиме.

Я не смог найти документацию об этом ограничении и посчитал бы это ошибкой в ​​iOS. О подобной проблеме сообщалось здесь: https://github.com/EmbeddedSources/iAsync/issues/22. Я думаю, что это стоит сообщить об ошибке в Apple.

(Примечание. Проблема не возникает в OS X/64-bit.)


Обновление (возможное решение / обходной путь): проект "SKSTableView" (ссылка в конце вопроса) использует категорию наNSIndexPath и связанные объекты для добавления третьего свойства 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;
}

Измените настройку следующими способами:

- (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

Я также сталкивался с этим, но в другом сценарии, где мне нужно было добавить свойство к теговому типу указателя.

Вот что я придумал; он использует 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
Другие вопросы по тегам