Traçage des classs de l'objective C

J'essaie d'attraper / replace certaines methods qui sont appelées dans des objects auxquels je n'ai pas access. Je voudrais être capable de sous-classr les objects qui sont instanciés dans les objects système.

Par exemple, lorsque j'instancie un UIWebView, il instancie en interne un UIScrollView. J'aimerais pouvoir sous-classr UIScrollView pour pouvoir modifier certains aspects de son comportement en le sous-classant. Est-ce possible?

J'ai essayé de faire cela en utilisant une catégorie, @implementation UIScrollView (MyScrollView), puis en remplaçant + alloc, et en returnnant mon propre object qui est une sous-class de UIScrollView. Cependant, je rencontre des problèmes. Fondamentalement, je n'arrive pas à atteindre la méthode originale [UIScrollView alloc]. Peut-être pourrais-je implémenter ma propre méthode + alloc qui répliquerait le comportement [NSObject alloc]?

Y a-t-il un moyen plus facile de faire cela? Quel est le problème avec mon approche actuelle?

Enfin, cela ferait-il passer le process de révision d'Apple? Je n'appelle rien qui ne soit pas documenté.

Le problème des catégories est, elles ne permettent pas de passer outre car vous ne pourrez plus atteindre les methods originales.

Pour réaliser de telles astuces (pardonnez mon français), vous pouvez essayer MethodSwizzling . En dernier recours, cela est parfois d'une grande valeur. Je l'ai utilisé dans le passé pour le debugging principalement et aussi pour l'ingénierie inverse.

Pour votre question sur le process d'approbation d'Apple, je suppose que vos chances d'avoir des problèmes sont assez élevées lorsque vous modifiez l'implémentation par défaut d'un UIWebView d'une manière aussi radicale.

N'utilisez pas de catégorie pour modifier les methods existantes. De cette façon, il y a la folie.

Ce que je ferais dans votre situation est de replace le scrollview par une instance de votre sous-class scrollview personnalisée en manipulant la hiérarchie de vue, pas le mécanisme d'allocation.

@ L'approche de Till de Method Swizzling est de savoir comment vous y parvenez. Mike Ash a l'un des meilleurs commentaires sur la façon de le faire. Je suis actuellement en faveur de son approche Direct Override, même si je l'ai retravaillé un peu comme suit:

#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 

Compte tenu de son exemple, vous utiliseriez ma méthode de cette façon:

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

Ce sont tous des appels documentés et n'utilisent pas les API privées Apple. Apple rejette parfois les applications qui modifient trop radicalement les comportements attendus de l'interface user (quelle que soit la façon dont elles le font), mais j'ai expédié des choses avec des modifications assez draconiennes de l'interface user sans problème, et cela ne dépend pas du privé. Apis.

Cela ne signifie pas que Method Swizzling est stable. C'est une technique fragile et qui vous rend plus dépendant des internes non documentés. Cela peut signifier que vous devez brouiller plus si Apple change quelque chose.

(Profonde respiration)

Il y a une autre approche que vous pouvez utiliser ici, tant que vous n'avez pas besoin de stockage ivar. Vous pouvez saisir la vue de défilement et la class la balayer dans votre propre sous-class. Ai-je mentionné que vous ne devez pas avoir votre propre stockage? Les bugs que vous allez créer si vous allouer un ivar dans ce domaine et l'esprit-flexion. Deusty écrit bien dans son blog . (C'est son code que j'ai ajouté un ivar à et rencontré les bogues incroyablement stupides mentionnés ci-dessus.)

Encore une fois, c'est une technique fragile, folle et dangereuse. Il peut aussi parfois être la façon la plus élégante et la plus facile de faire ce dont vous avez besoin.

Je ne l'ai pas réellement fait, donc je ne suis pas sûr à 100%, mais je crois que vous pouvez faire ce que vous essayez de faire, mais je ne pense pas que vous puissiez le faire avec votre approche actuelle. Je crois que vous devrez utiliser le runtime Objective-C pour intercepter les methods. Si vous ne l'avez pas déjà fait, lisez la reference d'exécution Objective-C .

OK, c'est la canette maintenant et vous devriez vous en sortir?

Tout ce que vous accomplirez en faisant cela sera probablement très fragile. Tout changement futur apporté par Apple aux frameworks peut casser votre code d'interception. Donc personnellement, je classrais cela sous "très douteux" quant à savoir si vous devriez envisager de sortir un produit qui repose sur cela.

Quant à vous allez-vous sortir avec? Évidemment, je ne peux pas parler pour Apple, mais mon pari est non. Vous avez raison de dire que vous n'appelez pas de code non documenté, mais que vous interférez avec le fonctionnement de methods privées, donc vous êtes certainement en train de voler dans l' esprit des termes App Store.

Bien sûr, tout ceci n'est que mon opinion, prenez-en ce que vous voulez.

J'ai trouvé une solution relativement facile et relativement sûre. Ce n'est pas parfait et ne fonctionnera pas dans tous les cas, mais pourrait être utile dans certains cas.

Fondamentalement, ce que je fais utilise une catégorie pour attraper les appels d'alloc + de ma class désirée. Ensuite, j'implémente ma propre méthode + alloc, qui alloue et returnne simplement un object de mon type sous- classé. Je peux alors replace n'importe quelle méthode dans mon object sous-classé et ils sont appelés correctement depuis UIWebView. C'est un mécanisme de sous-classment automatisé: Chaque instance de la class cible (dans ce cas, UIScrollView) est automatiquement sous-classée, même si quelqu'un d'autre la crée.

Voici à quoi ressemble ma méthode alloc +:

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

J'implémente ensuite une class MyUIScrollView simple qui hérite de UIScrollView et me permet de replace toutes les methods dont j'ai besoin, ce qui me donne beaucoup plus de contrôle sur le comportement de l'UIWebView parent. Cool!

Un couple de captures:

  • Si votre application a plus d'un UIScrollViews, vous devez faire attention car chacun d'entre eux deviendra essentiellement MyUIScrollView! La solution est d'avoir un drapeau dans MyUIScrollView qui s'assure qu'il est dormant (essentiellement simplement tout transférer à la superclass) dans les cas où l'instance n'appartient pas à votre UIWebView spécifique. Cela se fait facilement en sous-classant l'UIWebView, et en surchargeant ses methods de hiérarchie de vue (addSubview :, etc.) de sorte qu'une fois qu'UIWebView instancie et ajoute son UIScrollView interne à la hiérarchie de vue, vous pouvez définir un indicateur dans votre instance sous-classée pour lui dire de faire tout ce que vous cherchez à faire.

  • Cela ne fonctionne que pour les applications qui ne sous-classifient pas la class cible (UIScrollView, etc.) n'importe où dans l'application! Par exemple, je suis capable de me glisser dans le UIScrollView de UIWebView en utilisant cette technique et de replace toutes les methods que je voudrais utiliser, mais si un autre composant de mon application sous-class UIScrollView (y compris mon propre code), je vais rencontrer problème parce que je suis essentiellement désactivant tous les sous-classs pour cette class. Tous les UIScrollViews dans l'application vont être de type MyUIScrollView!

Bottom line: Ce n'est pas une approche recommandée, mais il semble que la façon la plus propre de résoudre ce problème.