Подтвердить что ты не робот

Кто-нибудь успешный с 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 Я только пробовал это в Симуляторе, а не в устройстве. Будет ли это поведение другим?

4b9b3361

Ответ 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 в ветке прокси-шаблона

Я также написал сообщение в блоге о деталях.