Jump to content
Sign in to follow this  
KTachyon

Objective-C Runtime - Blocks como IMP(lementation)

Recommended Posts

KTachyon

Este tópico vem no seguimento de um outro tópico que coloquei nesta secção há alguns meses e que podem encontrar em:

https://www.portugal-a-programar.pt/topic/57080-objective-c-runtime-exchange-method-implementation/

Ambos os tópicos não são exclusivos do iOS, também podem ser aplicados no desenvolvimento para OS X (e, muito provavelmente para qualquer outra plataforma onde possam programar Objective-C).

O tema deste tópico vem da possibilidade de se criarem classes no Objective-C em runtime:

Class mySpecialObjectClass = objc_allocateClassPair([NSObject class], "MySpecialObject", 0);
objc_registerClassPair(mySpecialObjectClass);

Portanto, neste exemplo, estou a criar uma nova classe, subclasse do NSObject, que depois poderá ser instanciada em qualquer ponto do programa, após a execução da linha anterior, da seguinte forma:

// Class mySpecialObjectClass = NSClassFromString(@"MySpecialObject");
id anObject = [mySpecialObjectClass new];

Obviamente que esta subclasse é inútil se não fizer nada mais que aquilo que a superclass já faz. Portanto temos que poder adicionar alguma funcionalidade à classe. E é aqui que entramos realmente no tema do tópico.

Para exemplificar melhor o que pretendo dizer, vou apresentar a implementação de uma classe, da qual vou depois criar uma subclasse em runtime e adicionar um método:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface Cat : NSObject

@property (nonatomic) NSString *name;
- (void) meow;

@end

@implementation Cat
@synthesize name;

- (void) meow {
   NSLog(@"%s %@ meows", object_getClassName(self), self.name);
}

@end

int main(int argc, const char * argv[])
{
   @autoreleasepool {

       id garfield = [Cat new];
       [garfield setName:@"Garfield"];

       [garfield meow]; // Output: "Cat Garfield meows"
   }
   return 0;
}

Portanto, temos uma classe gato com o método meow que imprime o nome da classe e uma propriedade da instância. O problema é que o Garfield é um gato especial que faz coisas que mais nenhum gato faz e portanto dizer que ele é um simples gato não parece correcto. Vou começar por criar uma subclasse da classe Cat e tornar o Garfield numa instância dessa classe, tudo em runtime:

int main(int argc, const char * argv[])
{
   @autoreleasepool {

       id garfield = [Cat new];
       [garfield setName:@"Garfield"];

       [garfield meow]; // Output: "Cat Garfield meows"

       Class specialCat = objc_allocateClassPair([Cat class], "SpecialCat", 0);
       objc_registerClassPair(specialCat);

       object_setClass(garfield, specialCat);

       [garfield meow]; // Output: "SpecialCat Garfield meows"
   }
   return 0;
}

Agora o Garfield é um SpecialCat, como mostra o output da função meow quando é chamada da segunda vez. Mas continua a não fazer mais que um Cat. Apesar de ser uma instância da classe SpecialCat, não tem nenhuma funcionalidade adicional à classe mãe. Mas eu quero que o Garfield voe, portanto vou adicionar um método novo à classe SpecialCat:

int main(int argc, const char * argv[])
{
   @autoreleasepool {

       id garfield = [Cat new];
       [garfield setName:@"Garfield"];

       [garfield meow]; // Output: "Cat Garfield meows"

       Class specialCat = objc_allocateClassPair([Cat class], "SpecialCat", 0);

       class_addMethod(specialCat, @selector(fly), imp_implementationWithBlock(^(){
           NSLog(@"flies");
       }), "v@:");

       objc_registerClassPair(specialCat);

       object_setClass(garfield, specialCat);

       [garfield meow]; // Output: "SpecialCat Garfield meows"

       [garfield fly]; // Output: "flies"
       // Se estiverem a usar ARC: [garfield performSelector:@selector(fly)];
   }
   return 0;
}

Agora perdemos o resto do contexto. Como o block apanha o contexto do scope em que está, chamadas ao self não resultam, porque a adição ao método não está no contexto da instância, para além de podermos querer criar mais objectos da classe que respondam com as suas próprias propriedades e não com as propriedades da instância Garfield:

class_addMethod(specialCat, @selector(fly), imp_implementationWithBlock(^(){
   NSLog(@"%s %@ meows", object_getClassName(self), self.name);
   // Erro: o self não existe fora do contexto de um objecto / o self é o objecto em que se está a adicionar o método
}), "v");

class_addMethod(specialCat, @selector(fly), imp_implementationWithBlock(^(){
   NSLog(@"%s %@ meows", object_getClassName(garfield), garfield.name);
   // Problema: Todas as instâncias da classe SpecialCat e subclasses vão responder com a propriedade do garfield
}), "v");

Todas as funções que trabalham com a troca e criação de métodos em runtime recebem um argumento do tipo IMP, cuja declaração é a seguinte:

typedef id (*IMP)(id, SEL, ...);

Portanto é um ponteiro para uma função que tem um id (que é efectivamente o self), o selector que resultou na chamada da função seguido dos parametros que a função poderá ter.

Ou seja, o nosso problema pode ser simplesmente resolvido da seguinte forma:

void fly(id self, SEL _cmd) {
   NSLog(@"%s %@ flies", object_getClassName(self), [self name]);
}

int main(int argc, const char * argv[])
{
   @autoreleasepool {

       id garfield = [Cat new];
       [garfield setName:@"Garfield"];

       [garfield meow]; // Output: "Cat Garfield meows"

       Class specialCat = objc_allocateClassPair([Cat class], "SpecialCat", 0);

       class_addMethod(specialCat, @selector(fly), (IMP)&fly, "v@:");

       objc_registerClassPair(specialCat);

       object_setClass(garfield, specialCat);

       [garfield meow]; // Output: "SpecialCat Garfield meows"
       [garfield fly]; // Output: "SpecialCat Garfield flies"
   }
   return 0;
}

A questão agora é: Será que funciona com blocks? E a resposta:

int main(int argc, const char * argv[])
{
   @autoreleasepool {

       id garfield = [Cat new];
       [garfield setName:@"Garfield"];

       [garfield meow]; // Output: "Cat Garfield meows"

       Class specialCat = objc_allocateClassPair([Cat class], "SpecialCat", 0);

       class_addMethod(specialCat, @selector(fly), imp_implementationWithBlock(^(id _self, SEL _cmd){
           NSLog(@"%s %@ flies", object_getClassName(_self), [_self name]);
       }), "v@:");

       objc_registerClassPair(specialCat);

       object_setClass(garfield, specialCat);

       [garfield meow]; // Output: "SpecialCat Garfield meows"
       [garfield fly]; // Output: "SpecialCat Garfield flies"
   }
   return 0;
}

Isto permite adicionar e alterar as funções de objectos em runtime, mantendo a possibilidade de passar parametros do scope directamente para dentro da função devido à utilização de um block, mantendo a capacidade de aceder às propriedades do objecto.

EDIT: O tópico encontrava-se incompleto, por razões que desconheço. Fica agora o tópico original.

Edited by KTachyon

“There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult.”

-- Tony Hoare

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×
×
  • Create New...

Important Information

By using this site you accept our Terms of Use and Privacy Policy. We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.