Invoking Objective-C block from Ruby?

443 views
Skip to first unread message

Jim Rankin

unread,
Nov 17, 2012, 12:28:33 AM11/17/12
to rubym...@googlegroups.com
It's clear how we can pass a Proc for an argument expecting an Objective-C block.

But how about the other way around?  If you're handed an Objective-C block, is there any way to invoke it from RubyMotion?

I have a library (TouchDB) that passes a block as an argument, but when I go to invoke it, like this (the block is the "emit" argument)

lambda do |doc, emit|
    emit(1, doc)
end

I get

"undefined method `emit' for #<AppDelegate:0x957fb20 ...> (NoMethodError)"

in other words, RubyMotion assumes emit is a method call in the current scope.

Is there any way I can invoke emit after it's passed to me?

Thanks,
 -jim rankin

Colin Thomas-Arnold

unread,
Nov 17, 2012, 4:07:59 AM11/17/12
to rubym...@googlegroups.com
If emit is a ruby block, it should be invoked as `emit.call(1, doc)`
--
 
 
 

Jim Rankin

unread,
Nov 17, 2012, 9:51:22 AM11/17/12
to rubym...@googlegroups.com
That gives me:

undefined method `call' for #<__NSMallocBlock__:0xdfcd8f0> (NoMethodError)

So it's a __NSMallocBlock__, not a Proc.  A little Googling shows me that's one of the classes used to implement blocks in Objective-C.

Re-reading the RubyMotion Runtime Guide, I'm thinking now this case is not covered in the RubyMotion implementation.  Passing Proc's to methods expecting a block is handled, but there's nothing suggesting you can call an Objective-C block from RubyMotion.

Laurent (if you're reading this), is this something that might be addressed in a future release?

Clay Allsopp

unread,
Nov 17, 2012, 8:26:18 PM11/17/12
to rubym...@googlegroups.com
This is a pretty common issue where Objective-C types aren't auto-boxed to Ruby types. Similar problems occur if you get an NSString, NSArray, NSDictionary, etc returned from some Objective-C functions.

File a support ticket with `motion support` =\

Colin Thomas Arnold Gray

unread,
Nov 19, 2012, 3:49:42 PM11/19/12
to rubym...@googlegroups.com
I was really curious about this one, and I asked around a little bit, but it turns out this is just currently not supported in RubyMotion.

But, that doesn't mean it's not possible in the future!  Submit a support ticket, and that will put it on Laurent's radar.



--
 
 
 

Naoya Makino

unread,
Nov 19, 2012, 4:10:46 PM11/19/12
to rubym...@googlegroups.com
I am not sure if I am on topic, but I am also working to get touchdb running with rubymotion. and I am struggling to define views and queries properly.
this is WIP code that is trying to exercise views and queries.

here is how I call defineViewNamed:mapBlock:version
@design.defineViewNamed("by_first_name", mapBlock:lambda do |doc, emit|
      first_name = doc.objectForKey("first_name")
      emit(first_name, doc) unless first_name.nil?
    end, version:"1.0")

which returns CouchDesignDocument. I am on motion version 1.29. is this issue of invoking a block in ruby addressed? or am I on different topic?

(main)> delegate = UIApplication.sharedApplication.delegate
=> #<AppDelegate:0x73ea1b0 @server=#<CouchTouchDBServer:0x93ab5a0> @database=#<CouchTouchDBDatabase:0x93acf20> @design=#<CouchDesignDocument:0xb08f430>>
(main)> view = delegate.defineViewByFirstName
=> #<CouchDesignDocument:0xb08f430>
(main)> delegate.query_by_first_name
=> #<CouchQueryEnumerator:0x93ca890>
(main)> delegate.query_by_first_name.count
=> 0 #this is not what I want, but I know why it is returning 0 as I am passing an untitledDocument in mapBlock.

Jim, how's things going with touchdb? if you have sample codes to share, please do. (particularly interested in defining views and queries and working with map/reduce) 

Naoya Makino

unread,
Nov 19, 2012, 7:28:52 PM11/19/12
to rubym...@googlegroups.com
seems like we cannot define mapBlock in rubymotion. 
here are few approaches we tried:

  def defineViewByFirstName
    @design.defineViewNamed("first_name", mapBlock:mapBlock, version:"1.0")
  end

#1
def mapBlock
   lambda {|doc, emit| emit(1, doc)}
end


when I check a delegate.design.viewNames, it did not list a view named "first_name".
but if we try to define a view by defineViewNamed:map:, it will properly save the view.

#2
 def mapBlock
   "^(NSDictionary* doc, void (^emit)(id key, id value)) {
    emit(1, doc);
}" 
  end

  def defineView
    @design.defineViewNamed("another_view", map:mapBlock)
  end

(main)> delegate = UIApplication.sharedApplication.delegate
=> #<AppDelegate:0x9550030 @server=#<CouchTouchDBServer:0xd155f30> @database=#<CouchTouchDBDatabase:0x964d240> @design=#<CouchDesignDocument:0x9654720>>
(main)> delegate.defineView
=> #<CouchDesignDocument:0x9654720>
(main)> delegate.design.queryViewNamed("another_view").rows
16:20:01.977| WARNING*** : View my-design/another_view has unknown map function: ^(NSDictionary* doc, void (^emit)(id key, id value)) {
    emit(1, doc);
}
2012-11-19 16:20:01.982 touchdb_demo[8581:11303] WARNING: CouchQuery[_view/another_view] failed with Error Domain=CouchDB Code=500 "The operation couldn’t be completed. Database error!" UserInfo=0xd172760 {NSUnderlyingError=0xd173200 "internal server error", NSLocalizedFailureReason=Database error!}
=> nil

However, when we try to access views, it raises the above error.
although I was able to add a document and getAllDocuments, so it seems like database is functioning in some cases.

is there anything wrong with how I define a view or a way to query views?
any help would be welcome.

here are WIP code.

Oleksandr Dodatko

unread,
Jan 17, 2013, 11:51:32 AM1/17/13
to rubym...@googlegroups.com
In order to invoke Objective-C block I had to write a custom category in native code.

Suppose, we need to execute the block that produces a @"Hello world" string for me
typedef NSString*(^BBStringProducer)(void);

In order to achieve this I have implemented a category for NSObject
@implementation NSObject (BlockForRuby)

-(id)objc_BlockSend0
{
    typedef id(^Block0)();
    
    Block0 block_ = (Block0)self;
    return block_();
}
@end


You only have to make the objc_BlockSend implementation support more arguments. You will also have to wrap your ints and floats to NSNumber and make your blocks API conform to this convention.
It is a common practice in the world of "C". Still, It is ugly. I wish I had any better solution.
In my applications I do not expose any blocks that take more than 3 parameters. And I know nobody who does.


aceofspades

unread,
Oct 11, 2013, 10:06:28 AM10/11/13
to rubym...@googlegroups.com
Has this issue gotten any official attention?

Colin T.A. Gray

unread,
Oct 11, 2013, 10:27:35 AM10/11/13
to rubym...@googlegroups.com
Seems like I've used obj-c blocks in rubymotion code; does block.call not work as expected?


Colin T.A. Gray
Community Manager
HipByte




--
You received this message because you are subscribed to the Google Groups "RubyMotion - Ruby for iOS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rubymotion+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/rubymotion/070a8065-b14b-48e6-9b13-53e2a541e781%40googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Eloy Durán

unread,
Oct 11, 2013, 10:41:01 AM10/11/13
to rubym...@googlegroups.com
An Objective-C block actually has a private `invoke` method you can use, but there's no way to pass arguments. This should be fixed in the upcoming 2.11 release where Objective-C blocks are treated as Ruby Proc objects.

Doug Puchalski

unread,
Oct 11, 2013, 11:17:08 AM10/11/13
to rubym...@googlegroups.com
Awesome, any ETA?

You received this message because you are subscribed to a topic in the Google Groups "RubyMotion - Ruby for iOS" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/rubymotion/-95TxLK_GDE/unsubscribe.
To unsubscribe from this group and all its topics, send an email to rubymotion+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/rubymotion/D6B56358-B18D-44C7-9C1A-A7A145DA359F%40hipbyte.com.

Eloy Durán

unread,
Oct 11, 2013, 11:48:50 AM10/11/13
to rubym...@googlegroups.com
No, sorry. Soon enough, though!

Stephen

unread,
Oct 15, 2013, 10:52:06 AM10/15/13
to rubym...@googlegroups.com
According to the change log, this was fixed in today's 2.11 update.
Message has been deleted

aceofspades

unread,
Oct 15, 2013, 7:36:28 PM10/15/13
to rubym...@googlegroups.com
Thought I had confirmed but was mistaken. CouchbaseLite is passing in blocks that get copied, I assume, and are instances of __NSMallocBlock__. Cannot send :call to them. 

Laurent Sansonetti

unread,
Oct 16, 2013, 5:32:40 AM10/16/13
to rubym...@googlegroups.com
Hi,

Very interesting. Would it be possible to share a simple project that reproduces the problem in a support ticket? In our tests and also based on feedback we received it should really be working fine.

(The fact that you are using a 3rd-party library might reveal a problem in the gen_bridge_metadata tool.)

Best regards.
Laurent

aceofspades

unread,
Oct 16, 2013, 11:04:05 AM10/16/13
to rubym...@googlegroups.com
Issue #1276 submitted.
Reply all
Reply to author
Forward
0 new messages