Кто-нибудь успешный с 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 вместо CALayer вызывается суперслоя на UILabel. Возможно, где-то что-то запутывается, а UILabel вставляется в подслои вместо своего CALayer?
Я что-то пропустил?
Выполняет ли UIKit какие-то оптимизации, которые обходят нормальный механизм, который NSProxy подключает?
Другие мысли?
Спасибо!
Генри
PS Я только пробовал это в Симуляторе, а не в устройстве. Будет ли это поведение другим?
Ответы
Ответ 1
Я бросил попытку. Я пришел к выводу, что NSProxy - такой недоиспользуемый объект, который потенциально для использования за пределами примеров Apple не был полностью изучен и не отлажен. Короче говоря, я считаю, что NSProxy не готов к использованию в качестве общего способа расширения функциональности объекта без подкласса или добавления категории.
В старые времена я бы использовал вызов poseAsClass для реализации моей желаемой функциональности.
В моем решении оказалось что-то вроде этого:
-
Я добавил категорию в UIView, добавив дополнительные свойства. Эти реализации свойств пересылали свой набор и получали сообщения к свойству "addOn" UIView, которое я также помещал в категорию. Значение по умолчанию этого свойства addOn в реализации категории UIView, конечно, равно нулю. (Я мог бы реализовать статическую хеш-таблицу, чтобы включить ассоциирование экземпляра AddOn для любого UIView, но это показалось мне рискованным уловкой, чтобы справиться с правильным счетом.)
-
В классе AddOn был добавлен дополнительный код для непосредственного управления UIView, и он добавил в него дополнительный код чертежа.
-
Для каждого типа UIView, который я хотел добавить в эту добавленную функциональность, мне пришлось подклассифицировать его кодом, который:
a) Создал метод экземпляра и соответствующий код свойства для класса AddOn
b) Подклассифицированы любые функции, которые я рассмотрел, чтобы дать коду "AddOn" возможность добавить его функциональность.
-
Каждый из этих подклассов имеет по существу один и тот же код в нем для перенаправления желаемой функциональности в экземпляр AddOn.
SO, я в конечном итоге минимизировал дублирование кода, насколько мог, но каждый из подклассов UIView-потомков, которые позволяют использовать функциональность "AddOn", заканчивает дублирование кода.
Похоже, что я мог бы еще больше минимизировать дублирование кода с помощью функций манипуляции с методами класса, но эта кривая обучения и дальнейшая обфускация кода не позволяли мне следовать этому пути.
Ответ 2
Я пытался решить ту же проблему - использовать 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:
. Таким образом, мы получаем даже нераспознанные вызовы выбора.
Это работает, например, методами, но этот сбой вызван методом класса, который у нас нет власти - сам объект не называется (класс есть). Я хотел заставить рабочую среду вызвать мой класс прокси даже для методов класса, переопределив getter моего подкласса NSProxy
- (Class) class
{
return _myRealClass;
}
Что не сработало. Поэтому NSProxy недостаточно для этого. Сейчас я пытаюсь использовать NSObject вместо NSProxy для достижения желаемого поведения, и поскольку NSObject имеет метод + (BOOL)resolveClassMethod:(SEL)sel
, который может быть полезен. Я отредактирую этот пост, как только узнаю, подходит ли NSObject для этого.
//Редактировать
Похоже, проблема в том, что с NSProxy на UIView
вызывается superlayer
вместо CALayer
. Доказательство: http://t2523.codeinpro.us/q/508112244f1eba38a4fb032e
Так что это действительно похоже на проблему с ярлыком UIKit - они не отправляют регулярный вызов сообщения (оптимизация скорости, я бы догадался).
В любом случае, я ищу способ обойти это сейчас.
Ответ 3
Я никогда не пробовал использовать NSProxy с представлениями, но я сделал что-то подобное, используя специальный класс представления для отображения другого представления. Возможно, система требует фактического представления, а не прокси-объекта. Существует два способа использования представления "прокси":
-
Сделать прокси-представление подведомственным представлением прокси. Прокси-сервер возьмет фрейм, авторезистирующую маску и т.д. Из проксированного представления, а затем добавит прокси-представление в качестве своего поднабора и установит его фрейм в качестве границ представления прокси и его маску авторезистирования, чтобы она всегда заполняла прокси-представление. Когда прокси-представление будет удалено, все настройки будут скопированы в него из прокси-представления. Любые свойства, не скопированные в прокси, передаются в прокси-представление с помощью пересылки.
-
Прокси-представление передает почти каждое сообщение в прокси-представление. Прокси-представление не отменяет функции блокировки/разблокировки, отображения и т.д. Он переопределяет drawRect: для вызова drawRect: в прокси-представлении.
Ответ 4
Попробовав ту же самую вещь и искал ошибку (которая меня достала), я попытался обойти проблемы... Это было некрасиво.
Идентификация проблемы с корнем была простой. Где-то в рамках Apple использует прямой указатель доступа к переменным в подклассах UIView
. Если вы проверяете заголовки, переменные объявляются с идентификатором доступа @package
.
В основном я пытался:
-
Создайте прокси-класс во время выполнения с ivars, скопированным из определения класса UIView
, а затем установите значения этих указателей для объектов в UIView
. Не удалось зайти далеко.
-
Объявите только CALayer *
в подклассе прокси и только скопируйте этот указатель из защищенного экземпляра UIView
. Работал, но я думаю, что это было плохо. Однако он не работал с автоматической компоновкой, поэтому я решил отойти от этого решения.
Код, который я пробовал, можно найти в RTLSegmentedControl
repo в ветке прокси-шаблона
Я также написал сообщение в блоге о деталях.