Skip to content

Commit

Permalink
NSAutoreleasePool improvements (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
maleadt authored Mar 4, 2024
1 parent 87af4dc commit 51098b1
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 13 deletions.
5 changes: 5 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
coverage:
status:
patch: false
project: false
changes: false
95 changes: 89 additions & 6 deletions src/foundation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export NSObject, retain, release, autorelease, is_kind_of
@autoproperty hash::NSUInteger
@autoproperty description::id{NSString}
@autoproperty debugDescription::id{NSString}

@autoproperty retainCount::NSUInteger
end

function Base.show(io::IO, ::MIME"text/plain", obj::NSObject)
Expand Down Expand Up @@ -409,6 +411,16 @@ export NSAutoreleasePool, @autoreleasepool, drain

@objcwrapper NSAutoreleasePool <: NSObject

"""
NSAutoreleasePool()
Create a new autorelease pool. This is a low-level wrapper around the Objective-C
`NSAutoreleasePool` class, and should be used with care. For example, it does not
automatically get released, or drain the pool on finalization.
For high-level usage, consider using the do-block syntax, or [`@autoreleasepool`](@ref)
instead.
"""
function NSAutoreleasePool()
obj = NSAutoreleasePool(@objc [NSAutoreleasePool alloc]::id{NSAutoreleasePool})
# XXX: this init call itself requires an autoreleasepool to be active...
Expand All @@ -421,7 +433,22 @@ end
drain(pool::NSAutoreleasePool) = @objc [pool::id{NSAutoreleasePool} drain]::Cvoid

# high-level interface to wrap Julia code in an autorelease pool
const NSAutoreleaseLock = ReentrantLock()

"""
NSAutoreleasePool() do
# ...
end
High-level interface to wrap Julia code in an autorelease pool. This is equivalent to
`@autoreleasepool` in Objective-C, and ensures that the pool is drained after the
enclosed code block has finished.
Note that due to technical limitations, this API prevents the current task from migrating
to another thread. In addition, only one autorelease do-block can be active at a time.
To disable these limitations, use the unsafe [`NSUnsafeAutoreleasePool`](@ref) instead.
See also: [`@autoreleasepool`](@ref)
"""
function NSAutoreleasePool(f::Base.Callable)
# we cannot switch between multiple autorelease pools, so ensure only one is ever active.
# XXX: support multiple pools, as long as they run on separate threads?
Expand All @@ -442,12 +469,68 @@ function NSAutoreleasePool(f::Base.Callable)
end
end
end
const NSAutoreleaseLock = ReentrantLock()

function NSUnsafeAutoreleasePool(f::Base.Callable)
pool = NSAutoreleasePool()
try
f()
finally
drain(pool)
end
end


# for compatibility with Objective-C code
macro autoreleasepool(ex)
quote
$NSAutoreleasePool() do
$(esc(ex))
"""
@autoreleasepool [kwargs...] code...
@autoreleasepool [kwargs...] function ... end
High-level interface to wrap Julia code in an autorelease pool. This macro can be used
within a function, or as a function decorator. In both cases, the macro ensures that the
contained code is wrapped in an autorelease pool, and that the pool is drained after the
enclosed code block has finished.
See also: [`NSAutoreleasePool`](@ref)
"""
macro autoreleasepool(ex...)
code = ex[end]
kwargs = ex[1:end-1]

# extract keyword arguments that are handled by this macro
unsafe = false
for kwarg in kwargs
if Meta.isexpr(kwarg, :(=))
key, value = kwarg.args
if key == :unsafe
isa(value, Bool) || throw(ArgumentError("Invalid value for keyword argument `unsafe`: got `$value`, expected literal boolean value"))
unsafe = value
else
error("Invalid keyword argument to @autoreleasepool: $kwarg")
end
else
throw(ArgumentError("Invalid keyword argument to @autoreleasepool: $kwarg"))
end
end
f = unsafe ? NSUnsafeAutoreleasePool : NSAutoreleasePool

if Meta.isexpr(code, :function)
# function definition
sig = code.args[1]
@assert Meta.isexpr(sig, :call)
body = code.args[2]
@assert Meta.isexpr(body, :block)
managed_body = quote
$f() do
$body
end
end
esc(Expr(:function, sig, managed_body))
else
# code block
quote
$f() do
$(esc(code))
end
end
end
end
Expand Down
20 changes: 13 additions & 7 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -108,22 +108,28 @@ using .Foundation
@testset "foundation" begin

@testset "NSAutoReleasePool" begin
# MRC
obj = NSString(@objc [NSString new]::id{NSString})
@objc [obj::id{NSString} release]::id{NSString}
# a function that creates an `autorelease`d object (by calling `arrayWithObjects`)
function trigger_autorelease()
str1 = NSString("Hello")
str2 = NSString("World")
arr1 = [str1, str2]
NSArray([str1, str2])
end

# low-level API
let pool=NSAutoreleasePool()
obj = NSString(@objc [NSString new]::id{NSString})
autorelease(obj)
trigger_autorelease()
drain(pool)
end

# high-level API
@autoreleasepool begin
# calling the `string` constructor means we don't need `autorelease`
@objc [NSString string]::id{NSString}
trigger_autorelease()
end
@autoreleasepool function foo()
trigger_autorelease()
end
foo()
end

# run the remainder of the tests in an autorelease pool to avoid leaking objects
Expand Down

2 comments on commit 51098b1

@maleadt
Copy link
Member Author

@maleadt maleadt commented on 51098b1 Mar 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/102201

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v2.1.0 -m "<description of version>" 51098b13c1d62f3bcc37b25cd9f0a0029956328e
git push origin v2.1.0

Please sign in to comment.