Захват классов Objective C

Я пытаюсь поймать/переопределить определенные методы, которые вызываются внутри объектов, к которым у меня нет доступа. Я хотел бы иметь возможность создавать подклассы объектов, которые создаются внутри системных объектов.

Например, когда я создаю экземпляр UIWebView, он внутри создает экземпляр UIScrollView. Я хотел бы иметь возможность создать подкласс этого UIScrollView, чтобы я мог изменить определенные аспекты его поведения, создав подкласс. Это возможно?

Я пытался сделать это, используя категорию @implementation UIScrollView (MyScrollView), затем переопределяя + alloc и возвращая свой собственный объект, который является подклассом UIScrollView. Тем не менее, я сталкиваюсь с некоторыми проблемами. По сути, я не могу найти исходный метод [UIScrollView alloc]. Возможно, я мог бы реализовать свой собственный метод + alloc, который воспроизвел бы поведение [NSObject alloc]?

Есть ли более простой способ сделать это? Что не так с моим нынешним подходом?

Наконец, пройдет ли это процесс проверки Apple? Я не вызываю ничего, что недокументировано.


person ldoogy    schedule 17.05.2011    source источник
comment
Методы, которые вы добавляете в категорию, заменят существующие методы с тем же именем; таким образом, вы не сможете получить доступ к исходному методу.   -  person Richard    schedule 17.05.2011
comment
Да, я собрал это. Как раздражает. Есть ли простой способ реализовать метод + alloc для моего подкласса? На самом деле это просто вызов управления памятью, верно? Опять же, как мне точно указать среде выполнения, какой это тип объекта?   -  person ldoogy    schedule 17.05.2011
comment
Если вы возитесь с внутренностями частных классов, это, как правило, довольно сильно осуждается (и может привести к отказу - вам придется попросить ADC дать окончательный ответ). Но на самом деле вы не хотите этого делать. Гарантированно приведет к таинственным сбоям, головным болям с обслуживанием и броску костей в отношении того, работает ли ваше приложение после любого данного обновления программного обеспечения.   -  person bbum    schedule 17.05.2011


Ответы (5)



Не используйте категорию для уничтожения существующих методов. На этом пути лежит безумие.

В вашей ситуации я бы заменил вид прокрутки экземпляром вашего пользовательского подкласса прокрутки, манипулируя иерархией представлений, а не механизмом распределения.

person NSResponder    schedule 17.05.2011
comment
Это гораздо менее хрупко, чем пьянство, но, тем не менее, так или иначе может привести к проблемам. - person bbum; 17.05.2011
comment
Хм, я бы сказал, что это очень рискованно и вряд ли сработает, потому что родитель (UIWebView) практически гарантированно сохранит указатель экземпляра на свой UIScrollView и будет взаимодействовать с ним через этот указатель. Избавившись от иерархии представлений (но не сообщив об этом UIWebView), моя замена UIScrollView, вероятно, не получит той любви, которая ему нужна, от UIWebView. - person ldoogy; 17.05.2011
comment
Любое представление имеет указатель на суперпредставление, которое будет установлено, когда вы поместите его в иерархию представлений. - person NSResponder; 17.05.2011
comment
Верно, но немногие полагаются на него для общения со своими подпредставлениями. Поскольку они вручную создали этот объект, у них, вероятно, есть указатель экземпляра, хранящийся в объекте для облегчения доступа - myScrollView или что-то в этом роде... С этим решением этот указатель экземпляра будет указывать на неправильный UIScrollView. - person ldoogy; 18.05.2011

Подход @Till к Method Swizzling - это то, как вы этого добиваетесь. У Майка Эша есть один из лучшие описания того, как это сделать. В настоящее время я одобряю его подход Direct Override, хотя я немного переработал его следующим образом:

#import <objc/runtime.h>

@interface NSObject (RNSwizzle)
+ (IMP)swizzleSelector:(SEL)origSelector withIMP:(IMP)newIMP;
@end

@implementation NSObject (RNSwizzle)

+ (IMP)swizzleSelector:(SEL)origSelector withIMP:(IMP)newIMP {
  Class class = [self class];
  Method origMethod = class_getInstanceMethod(class, origSelector);
  IMP origIMP = method_getImplementation(origMethod);

  if(!class_addMethod(self, origSelector, newIMP,
                      method_getTypeEncoding(origMethod)))
  {
    method_setImplementation(origMethod, newIMP);
  }

  return origIMP;
}

@end

Учитывая его пример, вы бы использовали мой метод следующим образом:

gOrigDrawRect = [UIView swizzleSelector:@selector(drawRect:)
                                withIMP:OverrideDrawRect];

Все эти вызовы задокументированы и не используют частные API Apple. Apple иногда отклоняет приложения, которые вносят слишком серьезные изменения в ожидаемое поведение пользовательского интерфейса (независимо от того, как они это делают), но я без проблем отправил приложения с довольно значительными скрытыми модификациями пользовательского интерфейса, и это не зависит от приватности. API.

Это не означает, что метод Swizzling стабилен. Это хрупкий метод, который заставляет вас больше полагаться на недокументированные внутренние компоненты. Это может означать, что вам придется больше карабкаться, если Apple что-то изменит.

(Глубокий вдох)

Здесь вы можете использовать другой подход, если вам не требуется хранилище ivar. Вы можете захватить вид прокрутки и класс переместить его в свой собственный подкласс. Я упоминал, что у вас не должно быть собственного хранилища иваров? Ошибки, которые вы создадите, если выделите ивар в этом и умопомрачительном. Деусти хорошо описывает это в своем блоге. (Это был его код, к которому я добавил ивар и столкнулся с невероятно умопомрачительными ошибками, упомянутыми выше.)

Опять же, это хрупкая, сумасшедшая, опасная техника. Это также иногда может быть самым элегантным и удобным способом сделать то, что вам нужно.

person Rob Napier    schedule 17.05.2011

На самом деле я этого не делал, поэтому я не уверен на 100%, но я верю, что вы можете сделать то, что пытаетесь сделать, но я не думаю, что вы можете сделать это с вашим нынешним подходом. Я считаю, что вам придется использовать среду выполнения Objective-C для перехвата методов. Если вы еще этого не сделали, прочтите Цель -C Справочник по среде выполнения.

Хорошо, это можно теперь, что насчет следует вам и сойдет ли вам это с рук?

Все, чего вы добьетесь, делая это, вероятно, будет очень хрупким. Любые будущие изменения, которые Apple внесет в фреймворки, могут нарушить ваш код перехвата. Так что лично я бы назвал это «очень сомнительным» относительно того, следует ли рассмотреть вопрос о выпуске продукта, основанного на этом.

Что касается сойдет ли вам это с рук? Очевидно, я не могу говорить за Apple, но я уверен, что нет. Вы правы в том, что, строго говоря, вы не вызываете недокументированный код, а вмешиваетесь в работу приватных методов, поэтому вы, безусловно, идете вразрез с духом терминов App Store.

Конечно, все это только мое мнение, берите из него что хотите.

person idz    schedule 17.05.2011

Я нашел относительно простое и довольно безопасное решение. Он не идеален и не будет работать во всех случаях, но в некоторых случаях может быть полезен.

По сути, я использую категорию, чтобы поймать вызовы моего желаемого класса + alloc. Затем я реализую свой собственный метод + alloc, который просто выделяет и возвращает объект моего типа subclassed. Затем я могу переопределить любой метод в моем объекте подкласса, и они будут правильно вызываться из UIWebView. Это автоматический механизм создания подклассов: каждый экземпляр целевого класса (в данном случае UIScrollView) автоматически становится подклассом, даже если его создает кто-то другой.

Вот как выглядит мой метод + alloc:

+ (id) alloc
{
    return class_createInstance([MyUIScrollView class], 0);
}

Затем я реализую простой класс MyUIScrollView, который наследуется от UIScrollView и позволяет мне переопределять любые методы, которые мне могут понадобиться, что дает мне гораздо больший контроль над поведением родительского UIWebView. Прохладный!

Пара уловов:

  • Если в вашем приложении есть более одного UIScrollView, вам нужно быть осторожным, потому что каждый из них теперь по существу станет MyUIScrollView! Решение состоит в том, чтобы иметь флаг в MyUIScrollView, который гарантирует, что он бездействует (в основном просто перенаправляет все в суперкласс) в тех случаях, когда экземпляр не принадлежит вашему конкретному UIWebView. Это легко сделать путем создания подкласса UIWebView и переопределения его методов иерархии представлений (addSubview: и т. д.), чтобы после создания экземпляра UIWebView и добавления его внутреннего UIScrollView в иерархию представлений вы могли установить флаг в своем экземпляре подкласса. чтобы сказать ему делать все, что вы хотите сделать.

  • Это работает только для приложений, которые не являются подклассами целевого класса (UIScrollView и т. д.) в любом месте приложения! Например, я успешно могу втиснуться в UIScrollView UIWebView, используя эту технику, и переопределить любые методы, которые я хотел бы использовать внутри него, но если какой-либо другой компонент в моем приложении подклассы UIScrollView (включая мой собственный код), я столкнусь с проблема, потому что я по существу отключаю все подклассы для этого класса. Все UIScrollViews в приложении будут иметь тип MyUIScrollView!

Итог: это не рекомендуемый подход, но он выглядит как самый простой способ решить эту проблему.

person ldoogy    schedule 18.05.2011