Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for NSAutoreleasePool. #29

Merged
merged 2 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 66 additions & 8 deletions src/foundation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
const NSUIntegerMax = typemax(NSUInteger)


export NSObject, retain, release, is_kind_of
export NSObject, retain, release, autorelease, is_kind_of

@objcwrapper NSObject <: Object

Expand All @@ -41,6 +41,8 @@

release(obj::NSObject) = @objc [obj::id{NSObject} release]::Cvoid

autorelease(obj::NSObject) = @objc [obj::id{NSObject} autorelease]::Cvoid

retain(obj::NSObject) = @objc [obj::id{NSObject} retain]::Cvoid

ObjectiveC.class(obj::NSObject) = @objc [obj::id{NSObject} class]::Class
Expand Down Expand Up @@ -83,7 +85,8 @@
end

NSValue(x::Ptr) = NSValue(@objc [NSValue valueWithPointer:x::Ptr{Cvoid}]::id{NSValue})
NSValue(x::Union{NSRange,UnitRange}) = NSValue(@objc [NSValue valueWithRange:x::NSRange]::id{NSValue})
NSValue(x::Union{NSRange,UnitRange}) =
NSValue(@objc [NSValue valueWithRange:x::NSRange]::id{NSValue})
# ...


Expand Down Expand Up @@ -150,23 +153,29 @@
Base.cconvert(::Type{id{NSString}}, str::String) = NSString(str)
Base.convert(::Type{NSString}, str::String) = NSString(str)

Base.:(==)(s1::Union{String,NSString}, s2::Union{String,NSString}) = String(s1) == String(s2)
Base.:(==)(s1::NSString, s2::NSString) = @objc [s1::id{NSString} isEqualToString:s2::id{NSString}]::Bool
Base.:(==)(s1::Union{String,NSString}, s2::Union{String,NSString}) =
String(s1) == String(s2)
Base.:(==)(s1::NSString, s2::NSString) =
@objc [s1::id{NSString} isEqualToString:s2::id{NSString}]::Bool

NSString() = NSString(@objc [NSString string]::id{NSString})
NSString(data::String) = NSString(@objc [NSString stringWithUTF8String:data::Ptr{Cchar}]::id{NSString})
NSString(data::String) =
NSString(@objc [NSString stringWithUTF8String:data::Ptr{Cchar}]::id{NSString})
Base.length(s::NSString) = Int(s.length)
Base.String(s::NSString) = unsafe_string(@objc [s::id{NSString} UTF8String]::Ptr{Cchar})

# avoid redundant quotes
Base.string(s::NSString) = String(s)
Base.print(io::IO, s::NSString) = print(io, String(s))

Base.show(io::IO, ::MIME"text/plain", s::NSString) = print(io, "NSString(", repr(String(s)), ")")
Base.show(io::IO, ::MIME"text/plain", s::NSString) =

Check warning on line 171 in src/foundation.jl

View check run for this annotation

Codecov / codecov/patch

src/foundation.jl#L171

Added line #L171 was not covered by tests
print(io, "NSString(", repr(String(s)), ")")
Base.show(io::IO, s::NSString) = show(io, String(s))

Base.contains(s::NSString, t::AbstractString) = @objc [s::id{NSString} containsString:t::id{NSString}]::Bool
Base.contains(s::AbstractString, t::NSString) = @objc [s::id{NSString} containsString:t::id{NSString}]::Bool
Base.contains(s::NSString, t::AbstractString) =
@objc [s::id{NSString} containsString:t::id{NSString}]::Bool
Base.contains(s::AbstractString, t::NSString) =
@objc [s::id{NSString} containsString:t::id{NSString}]::Bool

Check warning on line 178 in src/foundation.jl

View check run for this annotation

Codecov / codecov/patch

src/foundation.jl#L177-L178

Added lines #L177 - L178 were not covered by tests


export NSArray
Expand Down Expand Up @@ -396,4 +405,53 @@
end


export NSAutoreleasePool, @autoreleasepool, drain

@objcwrapper immutable=false NSAutoreleasePool <: NSObject

function NSAutoreleasePool(; autorelease=true)
obj = NSAutoreleasePool(@objc [NSAutoreleasePool alloc]::id{NSAutoreleasePool})
if autorelease
finalizer(release, obj)

Check warning on line 415 in src/foundation.jl

View check run for this annotation

Codecov / codecov/patch

src/foundation.jl#L415

Added line #L415 was not covered by tests
end
# XXX: this init call itself requires an autoreleasepool to be active...
@objc [obj::id{NSAutoreleasePool} init]::id{NSAutoreleasePool}
obj
end

drain(pool::NSAutoreleasePool) = @objc [pool::id{NSAutoreleasePool} drain]::Cvoid

# high-level interface to wrap Julia code in an autorelease pool
const NSAutoreleaseLock = ReentrantLock()
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?
Base.@lock NSAutoreleaseLock begin
# autorelease pools are thread-bound, so ensure we don't migrate to another thread
task = current_task()
sticky = task.sticky
task.sticky = true

# pools cannot be double released, so we need to disable the finalizer
pool = NSAutoreleasePool(; autorelease=false)
try
f()
finally
drain(pool)
#task.sticky = sticky
# XXX: we cannot safely re-enable thread migration, as the called code might have
# disabled it too. instead, Julia should have a notion of "temporary pinning"
end
end
end

# for compatibility with Objective-C code
macro autoreleasepool(ex)
quote
$NSAutoreleasePool() do
$(esc(ex))
end
end
end

end
28 changes: 27 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ using ObjectiveC
@test @objc [obj::id{Object} isEqualTo:obj::id{Object}]::Bool
empty_str = @objc [NSString string]::id{Object}
@objc [obj::id stringByReplacingOccurrencesOfString:empty_str::id{Object} withString:empty_str::id{Object}]::id{Object}

# chained class + install calls
@objc [[NSString alloc]::id{Object} init]::id{Object}
end

@objcwrapper TestNSString <: Object
Expand Down Expand Up @@ -101,9 +104,30 @@ end
end
end

using .Foundation
@testset "foundation" begin

using .Foundation
@testset "NSAutoReleasePool" begin
# MRC
obj = NSString(@objc [NSString new]::id{NSString})
@objc [obj::id{NSString} release]::id{NSString}

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

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

# run the remainder of the tests in an autorelease pool to avoid leaking objects
@autoreleasepool begin

@testset "NSString" begin
str = NSString()
Expand Down Expand Up @@ -225,6 +249,8 @@ end

end

end

@testset "dispatch" begin

using .Dispatch
Expand Down
Loading