Skip to content

Commit 51098b1

Browse files
authored
NSAutoreleasePool improvements (#31)
1 parent 87af4dc commit 51098b1

File tree

3 files changed

+107
-13
lines changed

3 files changed

+107
-13
lines changed

codecov.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
coverage:
2+
status:
3+
patch: false
4+
project: false
5+
changes: false

src/foundation.jl

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ export NSObject, retain, release, autorelease, is_kind_of
2929
@autoproperty hash::NSUInteger
3030
@autoproperty description::id{NSString}
3131
@autoproperty debugDescription::id{NSString}
32+
33+
@autoproperty retainCount::NSUInteger
3234
end
3335

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

410412
@objcwrapper NSAutoreleasePool <: NSObject
411413

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

423435
# high-level interface to wrap Julia code in an autorelease pool
424-
const NSAutoreleaseLock = ReentrantLock()
436+
437+
"""
438+
NSAutoreleasePool() do
439+
# ...
440+
end
441+
442+
High-level interface to wrap Julia code in an autorelease pool. This is equivalent to
443+
`@autoreleasepool` in Objective-C, and ensures that the pool is drained after the
444+
enclosed code block has finished.
445+
446+
Note that due to technical limitations, this API prevents the current task from migrating
447+
to another thread. In addition, only one autorelease do-block can be active at a time.
448+
To disable these limitations, use the unsafe [`NSUnsafeAutoreleasePool`](@ref) instead.
449+
450+
See also: [`@autoreleasepool`](@ref)
451+
"""
425452
function NSAutoreleasePool(f::Base.Callable)
426453
# we cannot switch between multiple autorelease pools, so ensure only one is ever active.
427454
# XXX: support multiple pools, as long as they run on separate threads?
@@ -442,12 +469,68 @@ function NSAutoreleasePool(f::Base.Callable)
442469
end
443470
end
444471
end
472+
const NSAutoreleaseLock = ReentrantLock()
473+
474+
function NSUnsafeAutoreleasePool(f::Base.Callable)
475+
pool = NSAutoreleasePool()
476+
try
477+
f()
478+
finally
479+
drain(pool)
480+
end
481+
end
482+
445483

446-
# for compatibility with Objective-C code
447-
macro autoreleasepool(ex)
448-
quote
449-
$NSAutoreleasePool() do
450-
$(esc(ex))
484+
"""
485+
@autoreleasepool [kwargs...] code...
486+
@autoreleasepool [kwargs...] function ... end
487+
488+
High-level interface to wrap Julia code in an autorelease pool. This macro can be used
489+
within a function, or as a function decorator. In both cases, the macro ensures that the
490+
contained code is wrapped in an autorelease pool, and that the pool is drained after the
491+
enclosed code block has finished.
492+
493+
See also: [`NSAutoreleasePool`](@ref)
494+
"""
495+
macro autoreleasepool(ex...)
496+
code = ex[end]
497+
kwargs = ex[1:end-1]
498+
499+
# extract keyword arguments that are handled by this macro
500+
unsafe = false
501+
for kwarg in kwargs
502+
if Meta.isexpr(kwarg, :(=))
503+
key, value = kwarg.args
504+
if key == :unsafe
505+
isa(value, Bool) || throw(ArgumentError("Invalid value for keyword argument `unsafe`: got `$value`, expected literal boolean value"))
506+
unsafe = value
507+
else
508+
error("Invalid keyword argument to @autoreleasepool: $kwarg")
509+
end
510+
else
511+
throw(ArgumentError("Invalid keyword argument to @autoreleasepool: $kwarg"))
512+
end
513+
end
514+
f = unsafe ? NSUnsafeAutoreleasePool : NSAutoreleasePool
515+
516+
if Meta.isexpr(code, :function)
517+
# function definition
518+
sig = code.args[1]
519+
@assert Meta.isexpr(sig, :call)
520+
body = code.args[2]
521+
@assert Meta.isexpr(body, :block)
522+
managed_body = quote
523+
$f() do
524+
$body
525+
end
526+
end
527+
esc(Expr(:function, sig, managed_body))
528+
else
529+
# code block
530+
quote
531+
$f() do
532+
$(esc(code))
533+
end
451534
end
452535
end
453536
end

test/runtests.jl

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,22 +108,28 @@ using .Foundation
108108
@testset "foundation" begin
109109

110110
@testset "NSAutoReleasePool" begin
111-
# MRC
112-
obj = NSString(@objc [NSString new]::id{NSString})
113-
@objc [obj::id{NSString} release]::id{NSString}
111+
# a function that creates an `autorelease`d object (by calling `arrayWithObjects`)
112+
function trigger_autorelease()
113+
str1 = NSString("Hello")
114+
str2 = NSString("World")
115+
arr1 = [str1, str2]
116+
NSArray([str1, str2])
117+
end
114118

115119
# low-level API
116120
let pool=NSAutoreleasePool()
117-
obj = NSString(@objc [NSString new]::id{NSString})
118-
autorelease(obj)
121+
trigger_autorelease()
119122
drain(pool)
120123
end
121124

122125
# high-level API
123126
@autoreleasepool begin
124-
# calling the `string` constructor means we don't need `autorelease`
125-
@objc [NSString string]::id{NSString}
127+
trigger_autorelease()
128+
end
129+
@autoreleasepool function foo()
130+
trigger_autorelease()
126131
end
132+
foo()
127133
end
128134

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

0 commit comments

Comments
 (0)