Кто-нибудь успешный с NSProxy из UIView (например, UILabel?)
Я экспериментирую с добавлением функциональности в мои UIViews (настраивая CALayers в соответствии с состоянием), настраивая подкласс NSProxy, чтобы он заменял любой UIView, который я выбрал. Вот что я попробовал:
В моем подклассе NSProxy у меня есть следующий код:
#pragma mark Initialization / Dealloc
- (id)initWithView:(UIView *)view
{
delegate = view;
[delegate retain];
return self;
}
- (void)dealloc
{
[delegate release];
[super dealloc];
}
#pragma mark Proxy Methods
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation setTarget:delegate];
[anInvocation invoke];
return;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
return [delegate methodSignatureForSelector:aSelector];
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
BOOL rv = NO;
if ([delegate respondsToSelector:aSelector]) { rv = YES; }
return rv;
}
И, используя мой подкласс NSProxy таким образом:
UILabel *label = [[HFMultiStateProxy alloc] initWithView:[[[UILabel alloc] initWithFrame:cellFrame] autorelease]];
label.text = text;
label.font = font;
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor clearColor];
label.opaque = NO;
[self addSubview:label];
Кажется, работает, пока я не попал в строку addSubview:.
Включение трассировки сообщений ( instrumentObjcMessageSends(YES);) показывает переадресацию для каждого из предыдущих сообщений, работающих до глубины внутри addSubview:, где эта серия вызовов методов отображается в журнале (первое показанное здесь сообщение было вызвано через прокси):
- UILabel UIView _makeSubtreePerformSelector:withObject:
- UILabel UIView _makeSubtreePerformSelector:withObject:withObject:copySublayers:
- CALayer CALayer sublayers
- NSMethodSignature NSMethodSignature methodReturnType
- NSMethodSignature NSMethodSignature _argInfo:
- NSMethodSignature NSMethodSignature _frameDescriptor
+ UILabel NSObject resolveInstanceMethod:
- UILabel NSObject forwardingTargetForSelector:
- UILabel NSObject forwardingTargetForSelector:
- UILabel NSObject methodSignatureForSelector:
- UILabel NSObject methodSignatureForSelector:
- UILabel NSObject class
- UILabel NSObject doesNotRecognizeSelector:
И я получаю следующую ошибку:
2011-02-20 16:38:52.048 FlashClass_dbg[22035:207] -[UILabel superlayer]: unrecognized selector sent to instance 0x757d470
если я не использую подкласс NSProxy и вместо этого использую подкласс UILabel (HFMultiStateLabel), он работает нормально. Вот трассировка сообщения, которая происходит после вызова addSubview: (HFNoteNameControl - это суперпредставление метки):
- HFNoteNameControl UIView addSubview:
- HFNoteNameControl UIView _addSubview:positioned:relativeTo:
- HFMultiStateLabel UIView superview
- HFMultiStateLabel UIView window
- HFNoteNameControl NSObject isKindOfClass:
- HFNoteNameControl NSObject class
- HFNoteNameControl UIView window
- UIWindow NSObject isKindOfClass:
- UIWindow NSObject class
- HFNoteNameControl UIView _shouldTryPromoteDescendantToFirstResponder
- HFMultiStateLabel UIView _isAncestorOfFirstResponder
- HFMultiStateLabel UIView _willMoveToWindow:withAncestorView:
- HFMultiStateLabel UIView _willMoveToWindow:
- HFMultiStateLabel UIView willMoveToWindow:
- HFMultiStateLabel UIView _makeSubtreePerformSelector:withObject:withObject:copySublayers:
- CALayer CALayer sublayers
- HFMultiStateLabel UIView willMoveToSuperview:
- HFMultiStateLabel UIView _unsubscribeToScrollNotificationsIfNecessary:
- HFMultiStateLabel UIView _makeSubtreePerformSelector:withObject:
- HFMultiStateLabel UIView _makeSubtreePerformSelector:withObject:withObject:copySublayers:
- CALayer CALayer sublayers
- CALayer CALayer superlayer
Я могу убедиться, что каждый из методов вплоть до -superlayer успешно вызывается при использовании NSProxy. По какой-то причине, с NSProxy, суперслой на UILabel вызывается вместо CALayer. Возможно, где-то что-то путается, и UILabel вставляется в подслои вместо своего CALayer?
Я что-то пропустил?
UIKit выполняет какую-то оптимизацию, которая обходит нормальный механизм, к которому подключается NSProxy?
Другие мысли?
Спасибо!
Генри
PS Я пробовал это только в симуляторе, а не на устройстве. Будет ли это поведение другим?
4 ответа
Я бросил пытаться. Я пришел к выводу, что NSProxy является настолько недоиспользуемым объектом, что его потенциал для использования за пределами примеров Apple не был полностью исследован и отлажен. Короче говоря, я считаю, что NSProxy не готов к использованию в качестве универсального способа расширения функциональности объекта без создания подклассов или добавления категории.
В старые времена я использовал вызов poseAsClass для реализации желаемой функциональности.
Мое решение закончилось примерно так:
Я добавил категорию в UIView, которая добавила дополнительные свойства. Эти реализации свойств пересылали свои set и get сообщения в свойство addOn UIView, которое я также поместил в категорию. Значение по умолчанию этого свойства "addOn" в реализации категории UIView, конечно, равно nil. (Я мог бы реализовать статическую хеш-таблицу, чтобы разрешить ассоциирование экземпляра AddOn для любого UIView, но это показалось мне рискованной уловкой для правильного управления счетом сохранения.)
Класс AddOn содержал дополнительный код для непосредственного управления UIView, и он добавил дополнительный код для рисования.
Для каждого типа UIView, в который я хотел добавить эту добавленную функциональность, мне пришлось разделить его на подклассы с помощью кода, который: а) создал метод экземпляра и соответствующий код свойства для класса "AddOn"; б) подклассировал все функции, которые я рассмотрел, чтобы получить " Код AddOn "дает возможность добавить свою функциональность.
Каждый из этих подклассов имеет, по сути, один и тот же код для передачи желаемой функциональности в экземпляр AddOn.
Итак, я закончил тем, что минимизировал дублирование кода настолько, насколько мог, но каждый из подклассов UIView-потомков, которые позволяют использовать функцию "AddOn", заканчивал тем, что дублировал код.
Похоже, что я мог бы еще больше свести к минимуму дублирование кода с помощью функций манипулирования методами класса, но эта кривая обучения и дальнейшее запутывание кода помешали мне пойти по этому пути.
Я пытался решить ту же проблему - использовать NSProxy с UIView (в моем случае UITableViewCell), когда я столкнулся с этой проблемой. Я записал все звонки на консоль:
...
App[2857:c07] MyHeaderCell: --- method signature for: _unsubscribeToScrollNotificationsIfNecessary:
App[2857:c07] MyHeaderCell: --- _unsubscribeToScrollNotificationsIfNecessary:
App[2857:c07] MyHeaderCell: --- method signature for: _makeSubtreePerformSelector:withObject:
App[2857:c07] MyHeaderCell: --- _makeSubtreePerformSelector:withObject:
App[2857:c07] +[MyHeaderCell superlayer]: unrecognized selector sent to class 0x1331f8c
App[2857:c07] CRASH: +[SMSHeaderCell superlayer]: unrecognized selector sent to class 0x1331f8c
App[2857:c07] Stack Trace:...
Он падает на unrecognized selector
исключение.
Обычно объект задается - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
сначала метод, и когда он возвращается, он вызывает - (void) forwardInvocation:(NSInvocation *)invocation
в прокси. Таким образом, мы можем перенаправить сообщения. Если нет NSMethodSignature
вернулся, doesNotRecognizeSelector:
метод вызывается на объекте. Таким образом, мы получаем даже нераспознанные селекторные вызовы.
Это работает для методов экземпляра, но этот сбой вызван методом класса, над которым у нас нет власти - сам объект не вызывается (класс есть). Я хотел заставить среду выполнения вызывать мой прокси-класс даже для методов класса, переопределив метод получения моего подкласса NSProxy
- (Class) class
{
return _myRealClass;
}
Который не работал. Так что NSProxy недостаточно для этого. Прямо сейчас я пытаюсь использовать NSObject вместо NSProxy для достижения желаемого поведения, и так как NSObject имеет + (BOOL)resolveClassMethod:(SEL)sel
метод, который может быть полезен. Я отредактирую этот пост, как только узнаю, подходит ли для этого NSObject.
//Редактировать
Кажется, что проблема в том, что с NSProxy, superlayer
вызывается на UIView
вместо CALayer
, Пруф: http://t2523.codeinpro.us/q/508112244f1eba38a4fb032e
Так что это действительно похоже на проблему с ярлыком UIKit - они не отправляют обычный вызов сообщения (я думаю, оптимизация скорости).
В любом случае, это я ищу способ обойти это сейчас.
Попробовав то же самое, и попытался найти ошибку (которая привела меня сюда), я попытался обойти проблемы... Это было не красиво.
Выявить корневую проблему было легко. Где-то в рамках Apple использует прямой указатель доступа к переменным в UIView
подклассы. Если вы проверяете заголовки, переменные объявляются с @package
идентификатор доступа.
В основном я пытался:
Создайте прокси-класс во время выполнения с копиями ivars из
UIView
определение класса, а затем установите значения этих указателей на объекты вUIView
, Не мог пройти далеко.Объявить только
CALayer *
в подклассе прокси, и только скопируйте этот указатель с защищенногоUIView
пример. Сработало, но думаю глючит? Тем не менее, он не работал с автоматическим макетом, поэтому я решил отойти от этого решения.
Код, который я пробовал, можно найти в RTLSegmentedControl
репо под веткой proxy-pattern
Я также написал сообщение в блоге о деталях.
Я никогда не пытался использовать NSProxy с представлениями, но я сделал нечто подобное, используя пользовательский класс представления для отображения другого представления. Возможно, система требует фактического представления, а не прокси-объекта. Существует два способа использования представления "прокси":
Сделайте прокси-представление подпредставлением прокси-представления. Прокси-сервер извлекает кадр, маску авторазмера и т. Д. Из прокси-представления, затем добавляет прокси-представление в качестве своего подпредставления и устанавливает его в качестве границ прокси-представления, а маску авторазмера - так, чтобы он всегда заполнял представление прокси. Когда прокси-представление удаляется, любые настройки копируются обратно в него из прокси-представления. Любые свойства, не скопированные в прокси-сервер, передаются в прокси-представление с помощью пересылки.
Прокси-представление передает почти каждое сообщение в прокси-представление. Прокси-представление не отменяет методы блокировки / разблокировки фокуса, отображения и т. Д. Он переопределяет drawRect: для вызова drawRect: в прокси-представлении.