селектор и PerformSelector

У меня есть UITableView с несколькими разделами. Каждый раздел содержит разный набор данных: номера телефонов, адреса....

Для каждого из этих наборов у меня есть модель: PhoneNumber, Address. Они совершенно разные, но имеют некоторые общие методы.

В моем UITableView у меня есть массив, содержащий эти модели/имена классов:

NSMutableArray *имя_класса;

В viewDidLoad моего UITableView я делаю некоторые инициализации для всех этих разделов:

//section 1: PhoneNumbers
phoneNumbers = [PhoneNumbers getAllIDs];
if (phoneNumbers && (phoneNumbers.count >0)) {
    [classNames addObject:@"PhoneNumber"];
    [dataIDs addObject:phoneNumbers];
}

Я делаю это снова для всех других разделов/моделей:

 //section 2: Addresses
    addresses = [Address getAllIDs];
    if (addresses && (addresses.count >0)) {
        [classNames addObject:@"Address"];
        [dataIDs addObject:addresses];
    }
    // section 3: .....

Хорошо пока для инициализации. Это выглядит хорошо и работает нормально.

Затем позже в моем cellForRowAtIndexPath я получаю фактические данные через эти идентификаторы

NSInteger section = [indexPath section];                    
NSInteger row = [indexPath row];

NSArray *rows = [dataIDs objectAtIndex:section];        
NSNumber *recordID = [rows objectAtIndex:row]; 

Затем я выясняю, в каком классе мы должны получить фактические данные:

Class displayedDataClass = NSClassFromString ([classNames objectAtIndex:section]);

и получить данные для заполнения ячейки.

id displayedRecord = [[displayedDataClass alloc] init];      
[displayedRecord getByID:recordID]; 

Затем я могу установить метки в своей ячейке, используя:

[cell.someLabel setText:[displayRecord fullDesciption]];

Пока все хорошо, я успешно все абстрагировал, cellForRowAtIndexPath не нужно знать, откуда берутся вещи, пока эти классы реагируют на методы получения данных для меток (в случае выше fullDesciption)

Теперь мне нужен actionButton в каждой ячейке, выполняющий какое-то действие. Чтобы убедиться, что я понял концепцию селекторов и PerformSelection, я просто быстро и грязно сделал в действии в своем классе TableView:

- (void) buttonTarget {
    NSLog (@"yes");
}

И в моем методе cellForRowAtIndexPath создал кнопку со следующей целью:

button addTarget:self action:@selector(buttonTarget) forControlEvents:UIControlEventTouchUpInside];

Хорошо, пока все хорошо, все работает, как и ожидалось. Но это не то, чего я действительно хотел. Действие должно выполняться не здесь, а в самом классе (PhoneNumber,Address,...).

Чтобы все было чисто, я сделал модель Action, содержащую иконку для кнопки, описание и селектор:

@interface Action : NSObject

@property (nonatomic, strong) NSString *description;
@property (nonatomic, strong) UIImage *icon;
@property (nonatomic ) SEL selector;

@end

В моем классе PhoneNumber (и подобных классах) действие установлено на правильный селектор:

Action  *phoneAction = [[Action alloc] init];

phoneAction.description = NSLocalizedString(@"Call", @"Call button description");
phoneAction.icon = [UIImage imageNamed:@"phone"];
phoneAction.selector = @selector(callPhone);

Конечно, callPhone реализован в классе PhoneNumber.

В моем TableView я получаю действия для этой ячейки

action = [displayedRecord action];

Затем я пытаюсь использовать этот селектор в своей кнопке:

[button addTarget:displayedRecord action:[action selector] forControlEvents:UIControlEventTouchUpInside];

Но здесь что-то идет не так: мы никогда не приходим к этому методу, и я получаю следующую ошибку:

[UIDeviceWhiteColor callPhone]: в экземпляр 0x874af90 29-12-2013 23:23:03.629 отправлен нераспознанный селектор. UIDeviceWhiteColor callPhone]: нераспознанный селектор отправлен экземпляру 0x874af90'


person Glenn    schedule 29.12.2013    source источник


Ответы (2)


Похоже, у вас есть зомби. Когда вы получаете действие, отправленное объекту, которое не имеет смысла, это обычно означает, что ваш объект освобождается, прежде чем вы сможете отправить ему сообщение.

В вашем случае вы добавляете «displayedRecord» в качестве цели для своей кнопки.

Чтобы это работало, вам нужно сохранить сильную ссылку на вызов displayRecord в течение всего времени существования вашего объекта кнопки. Чему принадлежит ваш объект displayRecord?

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

person Duncan C    schedule 29.12.2013
comment
Это, вероятно, ответ в правильном направлении! displayRecord определяется в cellForRowAtIndexPath (как написано в моем вопросе). Но поскольку это локальная переменная, вероятно, она недостаточно "сильна". Как мне это сделать? - person Glenn; 30.12.2013
comment
Есть только один объект displayRecord или вы создаете по одному для каждой ячейки? Если есть только один, сделайте его свойством вашего контроллера представления. Если для каждой ячейки существует другой, создайте собственный подкласс UITableViewCell со свойством (идентификатора типа) для хранения объекта displayDataClass для этой ячейки. - person Duncan C; 30.12.2013
comment
Большое спасибо, Дункан. Это было действительно решение. Я знаю, что у меня есть хорошая реализация, в которой функциональность абстрагирована и размещена там, где она должна быть! У меня уже был пользовательский класс для моего UITableViewCell, поэтому добавить это свойство было легко (сначала это не сработало, но я забыл сделать переменную экземпляра в своем пользовательском UITableViewCell сильной!) - person Glenn; 31.12.2013

В вашей нераспознанной ошибке селектора вы отправили сообщение объекту с именем UIDeviceWhiteColor. Есть ли в этом классе метод callPhone? Мне кажется, что displayRecord не указывает на объект, который вы думаете.

person Cameron Lowell Palmer    schedule 29.12.2013
comment
В моем коде нет класса UIDeviceWhiteColor. В моем классе PhoneNumber есть класс callPhone. - person Glenn; 30.12.2013
comment
Трассировка стека сообщает вам, что вызываемый объект, UIDeviceWhiteColor — частный класс Apple SDK, получает ваше сообщение. Это означает, что ваша переменная displayRecord по какой-то причине указывает на это. Я бы предположил, что где-то вы случайно назначаете его. Установите несколько точек останова и проверьте тип класса - person Cameron Lowell Palmer; 30.12.2013
comment
Как заметил другой джентльмен, у вас может быть зомби. Может быть полезно зайти в настройки отладки и включить зомби. - person Cameron Lowell Palmer; 30.12.2013