Watson1978
10/11/2011 - 2:54 AM

MacRuby with Objective-C associated objects

MacRuby with Objective-C associated objects

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

@interface NSObject (MRAssociatedObjects)
- (id)associatedObjectForSymbol:(NSString *)rubySymbolName;
- (void)setAssociatedObject:(id)value forSymbol:(NSString *)rubySymbolName;
@end

@implementation NSObject (MRAssociatedObjects)

static VALUE
symbolNameToSymbol(id rubySymbolName)
{
  VALUE sym;
  ID id_to_s = rb_intern("to_s");

  if (SYMBOL_P(rubySymbolName)) {
    sym = (VALUE)rubySymbolName;
  } else if ([rubySymbolName isKindOfClass:[NSString class]]) {
    sym = rb_name2sym([rubySymbolName UTF8String]);
  } else {
      if (rb_respond_to((VALUE)rubySymbolName, id_to_s)) {
	  VALUE str = rb_funcall((VALUE)rubySymbolName, id_to_s, 0, Qnil);
	  sym = rb_name2sym(RSTRING_PTR(str));
      }
      else {
	  [NSException raise:@"ArgumentError"
                format:@"Expected a Symbol or NSString, but got `%@'", rubySymbolName];
      }
  }
  return sym;
}

- (id)associatedObjectForSymbol:(id)rubySymbolName
{
  return objc_getAssociatedObject(self, (void *)symbolNameToSymbol(rubySymbolName));
}

- (void)setAssociatedObject:(id)value forSymbol:(id)rubySymbolName
{
  objc_setAssociatedObject(self, (void *)symbolNameToSymbol(rubySymbolName), value, OBJC_ASSOCIATION_RETAIN);
}

@end


// Ruby expects this
void
Init_associated_objects() {}


@interface TestAssociatedObject : NSObject
@end
@implementation TestAssociatedObject

- (void)computeAnswerInObjC
{
  if ([[self associatedObjectForSymbol:@"question"] isEqual:@"the answer to everything"]) {
    [self setAssociatedObject:[NSNumber numberWithInt:42] forSymbol:@"computed_answer"];
  }
}

@end
% clang -fobjc-gc -framework Foundation -framework MacRuby -bundle -o associated_objects.bundle associated_objects.m

% macruby test.rb                                                                                                   
42
/Users/eloy/tmp/associated-objects/test.rb:6:in `[]=:': ArgumentError: Expected a Symbol or NSString, but got `42' (RuntimeError)
	from /Users/eloy/tmp/associated-objects/test.rb:13:in `<main>'
require "associated_objects"

class NSObject
  alias_method :[], :associatedObjectForSymbol
  def []=(symbol, value)
    setAssociatedObject(value, forSymbol:symbol)
  end
end

o = TestAssociatedObject.new
o[:question] = "the answer to everything"
o.computeAnswerInObjC
p o[:computed_answer] # => 42

# anything other than a string/symbol makes it go boom
o["string is ok too"] = "yup"
o[42] = "ohnoes" # => test.rb:6:in `[]=:': ArgumentError: Expected a Symbol or NSString, but got `42' (RuntimeError)
p o[42]