[fpc-devel] Blocks support
Jonas Maebe
jonas.maebe at elis.ugent.be
Wed Jul 16 14:12:42 CEST 2014
Hi,
I've (locally, not yet in svn) implemented support for blocks in FPC (
http://en.wikipedia.org/wiki/Blocks_(C_language_extension) ). Blocks are
basically procedural variables with support for capturing local context
in various ways. As implemented in C/C++/Objective-C, they also
introduce the syntactic sugar of declaring them as inline code fragments
(anonymous procdures), but I have not implemented this in FPC. Such
functionality is completely orthogonal to the support for blocks, and
can be added later if/when support for anonymous methods is added to FPC
in general.
The current blocks support is very limited and only supports converting
regular procedures/functions to blocks (i.e., not nested
procedures/functions, not methods of either Pascal or Objective-Pascal
classes/objects, not other procedural variables). It is also only
enabled on Darwin ((Mac) OS X, iOS and iphonesim) for now, but since the
blocks runtime is available on other platforms too, it can be enabled
elsewhere over time.
The way I've implemented it right now is that a block-type is declared
like a regular procedure variable, followed by "is block". E.g.:
type
NSArrayEnumeratorBlock = procedure (obj: id; idx: NSUInteger; var
stop: boolean) is block;
Unlike other procedure variable types, you cannot specify the calling
convention of these "is block" types, as it is determined by the blocks
runtime. Also unlike other procedure variable types, the compiler will
automatically generate wrappers when assigning a procedure/function to
such an "is block" type, so that the calling convention of the original
procedure/function doesn't matter.
A complete example:
***
{$mode objfpc}
{$modeswitch objectivec2}
{$modeswitch blocks}
uses
CocoaAll;
type
NSArrayEnumeratorBlock = procedure (obj: id; idx: NSUInteger; var
stop: boolean) is block;
{ normally part of the NSArray declaration, but not yet in
our units since the released compilers don't support blocks
yet }
enumcategory = objccategory external (NSArray)
procedure enumerateObjectsUsingBlock(block:
NSArrayEnumeratorBlock); message 'enumerateObjectsUsingBlock:';
end;
procedure myenumerate(obj: id; idx: NSUInteger; var stop: boolean);
begin
NSLog(NSString.alloc.initWithUTF8String('Object at index %lu is %@'),
idx, obj);
end;
var
arr: NSMutableArray;
str: NSString;
begin
arr := NSMutableArray.alloc.init;
str:=NSString.alloc.initWithUTF8String('abc');
arr.addObject(str);
str:=NSString.alloc.initWithUTF8String('123');
arr.addObject(str);
str:=NSString.alloc.initWithUTF8String('XYZ');
arr.addObject(str);
arr.enumerateObjectsUsingBlock(@myenumerate);
end.
Output:
2014-07-16 09:58:53.886 blockenumerate[17438:507] Object at index 0 is abc
2014-07-16 09:58:53.913 blockenumerate[17438:507] Object at index 1 is 123
2014-07-16 09:58:53.914 blockenumerate[17438:507] Object at index 2 is XYZ
Now, to the hard part. In case of global routines, the block does not
capture any local state and everything works perfectly as it is. There
is however not much point to using them this way, other than with
library APIs that accept blocks rather than regular procedure variables
(which forms an increasing part of the OS X/iOS system interface, so it
is already useful by itself).
"Capturing local state" means that the block gets a copy or a reference
to a variable that is local to the context where it is instantiated
(instantiated in our case means "the place where we assign the address
of a procedure/function to an "is block" variable). For example, when
support is added for assigning an instance method to an "is block"
variable, the instance pointer must become part of the block because
otherwise we don't know what self parameter to use when the block is
actually invoked.
However:
* Methods of Pascal classes
As explained above, the "self" parameter has to be captured by the block
in this case. If nothing special is done, then this self pointer will be
treated like a regular pointer and you will be responsible for freeing
it. However, in case a block is passed to an asynchronous routine, the
question will most likely be "when is it safe to free that instance",
and I'm not sure how to do that. The blocks runtime supports adding code
that will be executed when a block is copied and when it is freed, but
since Pascal classes don't have an internal reference count, there's not
much we can do automatically here.
There is a way to make a Pascal class reference counted, by making it a
descendent of TInterfacedObject (or more generally, by implementing the
IUnknown interface) and only handling it via interface variables, so
maybe the compiler could generate special code in that case to make sure
that the block does update the reference counts in that case. That way,
you can have a choice between manual and automatic reference counting. I
still have to think this through to determine whether this reference
counted approach is in fact feasible to implement.
* Methods of Objective-C classes
Objective-C doesn't support function pointers to Objective-C methods (as
far as I know), so right now Objective-Pascal doesn't support procedure
variables that reference Objective-C methods either. If this is changed
because of blocks, they should be supported in general for orthogonality
reasons.
This would also require capturing the "self" parameter, but since
Objective-C classes to come with reference counting (in various ways),
the reference counting would be handled automatically behind the scenes.
On the other hand, such a capture could result in cyclic references
(https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html#//apple_ref/doc/uid/TP40011210-CH8-SW16
)
* Local/nested procedures
These most closely resemble blocks as they exist in C, since they can
directly access all local variables of their enclosing scope (rather
than only a specific value such as the self pointer).
The default in C when accessing a local variable from the enclosing
scope within a block depends on the type and declaration of the variable:
1) an Objective-C class or a variable marked to behave as such via
__attribute__((NSObject)) (e.g. a CFString), or another block-typed
variable: reference counts are automatically updated by the
compiler/runtime -- except if that variable is declared as __weak, then
no reference counting is performed
2) the variable is marked as "__block". In this case, the compiler and
runtime use indirection (and if needed, copying onto the heap and
updating all redirections) to make sure that the local variable can
survive the scope in which it was declared, so that the block (and
copies thereof) can keep using it even after the function in which the
local variable was declared, returns
3) all other variables (non-block/object and not marked as __block)
accessed by a block are simply copied into local storage of the block
when the block is instantiated (in our case that would be: when the
procedure pointer is assigned to to the "is block" variable).
Modifications to such variables in the original routine after the block
has been instantiated are not visible to the block and vice verse. If a
copy is made of the block, then the current value of those variables
within that first block are copied and afterwards again become private
to the copy.
I currently have no idea how to encapsulate this in Pascal. We could add
__block and __weak modifiers (the __weak modifier would also be useful
for non-block-related Objective-Pascal once we add support for
ARC/Automated Reference Counting), but that's not really nice.
Another option is to only support by-value copies, and basically always
use cases 1) and 3) above depending on the type of the variable. If you
want to change something by reference, then you would have to manually
set up a pointer (or object instance) first.
Or maybe someone has another idea that combines the flexibility with a
Pascalish syntax/way of working?
Jonas
More information about the fpc-devel
mailing list