Кто-нибудь успешно с NSProxy UIView (например, UILabel?) - PullRequest
13 голосов
/ 21 февраля 2011

Я экспериментирую с добавлением функциональности в мои 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 ]

4 голосов
/ 27 августа 2013

Я пытался решить ту же проблему - использовать 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.

// Edit

Кажется, проблема в том, что с NSProxy вызывается superlayer1022 * вместо CALayer.Доказательство: http://t2523.codeinpro.us/q/508112244f1eba38a4fb032e

Так что это действительно похоже на проблему с ярлыком UIKit - они не отправляют обычный вызов сообщения (я думаю, оптимизация скорости).

В любом случае, это я ищучтобы обойти это сейчас.

3 голосов
/ 16 апреля 2011

Я бросил пытаться.Я пришел к выводу, что NSProxy является настолько недоиспользуемым объектом, что его потенциал для использования вне примеров Apple не был полностью исследован и отлажен.Короче говоря, я считаю, что NSProxy не готов к использованию в качестве универсального способа расширения функциональности объекта без создания подклассов или добавления категории.

В старые времена я использовал вызов poseAsClass для реализации желаемой функциональности.

Мое решение закончилось примерно так:

  • Я добавил категорию в UIView, которая добавила дополнительные свойства.Эти реализации свойств пересылали свои сообщения set & get в свойство addOn объекта UIView, которое я также поместил в категорию.Значение по умолчанию этого свойства "addOn" в реализации категории UIView, конечно, равно nil.(Я мог бы реализовать статическую хеш-таблицу, чтобы разрешить ассоциирование экземпляра AddOn для любого UIView, но мне показалось рискованным занятием правильно управлять счетами хранения.)

  • The "В классе AddOn был добавлен дополнительный код для непосредственной манипуляции с UIView, и он добавил дополнительный код для рисования.

  • Для каждого типа UIView, в который я хотел добавить эту добавленную функциональность, япришлось подклассифицировать его с помощью кода, который: a) создал метод экземпляра и соответствующий код свойства для класса «AddOn»; б) подклассифицировал все функции, которые я рассмотрел, чтобы дать коду «AddOn» возможность добавить свою функциональность.

  • Каждый из этих подклассов имеет по существу один и тот же код для перенаправления нужной функциональности в экземпляр AddOn.

ТАК, в итоге я минимизировал дублирование кодакак я мог, но каждый из подклассов UIView-потомков, которые позволяют использовать функциональность «AddOn», заканчивает тем, что дублировал код.

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

2 голосов
/ 12 марта 2014

Попробовав ту же самую вещь и отыскав ошибку (которая меня здесь привела), я попытался обойти проблемы ... Это было не красиво.

Выявить корневую проблему было легко. Где-то в рамках Apple использует прямой указатель доступа к переменным в подклассах UIView. Если вы проверяете заголовки, переменные объявляются с @package идентификатором доступа.

То, что я в основном пробовал, было:

  1. Создайте прокси-класс во время выполнения с помощью ivars, скопированного из определения класса UIView, а затем установите значения этих указателей для объектов в UIView. Не смог далеко уйти.

  2. Объявите только CALayer * в подклассе прокси и скопируйте только этот указатель из защищенного экземпляра UIView. Сработало, но думаю глючит? Тем не менее, он не работал с автоматической разметкой, поэтому я решил отойти от этого решения.

Код, который я пробовал, можно найти в RTLSegmentedControl репо в ветке прокси-шаблона

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

2 голосов
/ 21 февраля 2011

Я никогда не пытался использовать NSProxy с представлениями, но я сделал нечто подобное, используя собственный класс представления для отображения другого представления. Может быть, система требует фактического представления, а не прокси-объекта. Существует два способа использования прокси-представления:

  1. Сделать прокси-представление подпредставлением прокси-представления. Прокси-сервер будет брать кадр, маску автоматического изменения размера и т. Д. Из прокси-представления, затем добавлять прокси-представление в качестве своего подпредставления и устанавливать его в качестве границ прокси-представления, а маску авторазмера - так, чтобы он всегда заполнял представление прокси. Когда прокси-представление удаляется, все настройки копируются обратно в него из прокси-представления. Любые свойства, не скопированные в прокси-сервер, передаются в прокси-представление с помощью пересылки.

  2. Прокси-представление передает почти каждое сообщение в прокси-представление. Представление прокси не отменяет методы блокировки / разблокировки фокуса, отображения и т. Д. Он переопределяет drawRect: для вызова drawRect: в прокси-представлении.

...