Skip to content

Commit

Permalink
Initial support for blocks.
Browse files Browse the repository at this point in the history
  • Loading branch information
maleadt committed Mar 7, 2023
1 parent 2d24d6b commit 18ea404
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 1 deletion.
92 changes: 92 additions & 0 deletions src/foundation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -366,4 +366,96 @@ function Base.:(==)(a::NSURL, b::NSURL)
end


export NSBlock

# @cenum Block_flags::Cint begin
# BLOCK_DEALLOCATING = 0x0001
# BLOCK_REFCOUNT_MASK = 0xfffe

# BLOCK_IS_NOESCAPE = 1 << 23
# BLOCK_NEEDS_FREE = 1 << 24
# BLOCK_HAS_COPY_DISPOSE = 1 << 25
# BLOCK_HAS_CTOR = 1 << 26
# BLOCK_IS_GLOBAL = 1 << 28
# BLOCK_HAS_STRET = 1 << 29
# BLOCK_HAS_SIGNATURE = 1 << 30
# end

const _NSConcreteGlobalBlock = cglobal(:_NSConcreteGlobalBlock)
const _NSConcreteStackBlock = cglobal(:_NSConcreteStackBlock)

@objcwrapper NSBlock <: NSObject

function Base.copy(block::NSBlock)
@objc [block::id{NSBlock} copy]::id{NSBlock}
end


struct JuliaBlockDescriptor
reserved::Culong
size::Culong
copy_helper::Ptr{Cvoid}
dispose_helper::Ptr{Cvoid}
end

struct JuliaBlock
isa::Ptr{Cvoid}
flags::Cint
reserved::Cint
invoke::Ptr{Cvoid}
descriptor::Ptr{JuliaBlockDescriptor}

# custom fields
lambda::Function
end

# JuliaBlock is the concrete version of NSBlock, so make it possible to derive a regular
# Objective-C object by temporarily boxing the structure and copying it to the heap.
function NSBlock(block::JuliaBlock)
block_box = Ref(block)
GC.@preserve block_box begin
block_ptr = Base.unsafe_convert(Ptr{Cvoid}, block_box)
nsblock_ptr = reinterpret(id{NSBlock}, block_ptr)
copy(NSBlock(nsblock_ptr))
end
end

# static descriptor for a simple Julia-based block
const julia_block_descriptor = Ref(JuliaBlockDescriptor(0, sizeof(JuliaBlock), 0, C_NULL))

function julia_block_trampoline(_block, _self, args...)
block = unsafe_load(_block)
nsblock = NSBlock(reinterpret(id{NSBlock}, _block))

# call the user lambda
block.lambda(args...)
end

macro objcblock(callable, rettyp, argtyps)
quote
cb = @cfunction($julia_block_trampoline, $(esc(rettyp)),
(Ptr{JuliaBlock}, id{Object}, $(esc(argtyps))...))
GC.@preserve cb begin
# set-up the block data structures
trampoline_ptr = Base.unsafe_convert(Ptr{Cvoid}, cb)
desc_ptr = Base.unsafe_convert(Ptr{Cvoid}, $julia_block_descriptor)
block = JuliaBlock(_NSConcreteStackBlock, 0, 0, trampoline_ptr, desc_ptr, $(esc(callable)))

# copy the block to the heap and have Objective-C use that object.
# our original block was a plain Julia struct so doesn't need to be released.
# we also don't need to worry about the lifetime of the block object, as the
# only references to Julia data it contains are the descriptor, which is
# statically allocated, and the lambda (Julia code currently isn't GC'd).
#
# XXX: does nobody need to release the NSBlock copy we created?
# also, we will need to keep track of the cfunction lifetime
# if we want to support closures (or make the user responsible
# by requiring the @cfunction output to be passed to the macro,
# or returning an object that links the NSBlock with the CFunction).
NSBlock(block)
end
end
end


end
2 changes: 1 addition & 1 deletion src/syntax.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export @objc, @objcwrapper, @objcproperties
export @objc, @objcwrapper, @objcproperties, @objcblock


# Method Calling
Expand Down
24 changes: 24 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,30 @@ end

using .Foundation

@testset "NSBlock" begin
function addone(x::T) where T
return x + one(T)
end

block = Foundation.@objcblock(addone, Cint, (Cint,))

# for validation, register our block with a class method
# (no need to use @objcwrapper as we're not constructing an id{BlockWrapper})
wrapper_class = ObjectiveC.allocclass(:BlockWrapper, Class(:NSObject))
imp = ccall(:imp_implementationWithBlock, Ptr{Cvoid}, (id{Foundation.NSBlock},), block)
types = (Cint, Object, Selector, Cint)
typestr = ObjectiveC.encodetype(types...)
@assert ccall(:class_addMethod, Bool,
(Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cchar}),
wrapper_class, sel"invoke:", imp, typestr)
ObjectiveC.register(wrapper_class)

# create a wrapper instance and call our block
ptr = @objc [BlockWrapper alloc]::id{Object}
ret = @objc [ptr::id{Object} invoke:41::Cint]::Cint
@test ret == 42
end

@testset "NSString" begin
str = NSString()
@test is_kind_of(str, Class("NSString"))
Expand Down

0 comments on commit 18ea404

Please sign in to comment.