-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11: Revamp the package
- Loading branch information
Showing
22 changed files
with
1,777 additions
and
512 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
name: CompatHelper | ||
on: | ||
schedule: | ||
- cron: 0 0 * * * | ||
workflow_dispatch: | ||
permissions: | ||
contents: write | ||
pull-requests: write | ||
jobs: | ||
CompatHelper: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Check if Julia is already available in the PATH | ||
id: julia_in_path | ||
run: which julia | ||
continue-on-error: true | ||
- name: Install Julia, but only if it is not already available in the PATH | ||
uses: julia-actions/setup-julia@v1 | ||
with: | ||
version: '1' | ||
# arch: ${{ runner.arch }} | ||
if: steps.julia_in_path.outcome != 'success' | ||
- name: "Add the General registry via Git" | ||
run: | | ||
import Pkg | ||
ENV["JULIA_PKG_SERVER"] = "" | ||
Pkg.Registry.add("General") | ||
shell: julia --color=yes {0} | ||
- name: "Install CompatHelper" | ||
run: | | ||
import Pkg | ||
name = "CompatHelper" | ||
uuid = "aa819f21-2bde-4658-8897-bab36330d9b7" | ||
version = "3" | ||
Pkg.add(; name, uuid, version) | ||
shell: julia --color=yes {0} | ||
- name: "Run CompatHelper" | ||
run: | | ||
import CompatHelper | ||
CompatHelper.main() | ||
shell: julia --color=yes {0} | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} | ||
# COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
name: TagBot | ||
on: | ||
schedule: | ||
- cron: 0 * * * * | ||
jobs: | ||
TagBot: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: JuliaRegistries/TagBot@v1 | ||
with: | ||
token: ${{ secrets.GITHUB_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
name: CI | ||
on: | ||
push: | ||
branches: [main, master] | ||
tags: ["*"] | ||
pull_request: | ||
jobs: | ||
test: | ||
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} | ||
runs-on: ${{ matrix.os }} | ||
strategy: | ||
fail-fast: false | ||
matrix: | ||
version: | ||
- '1.6' | ||
- '1' | ||
os: | ||
- macOS-latest | ||
arch: | ||
- x64 | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- uses: julia-actions/setup-julia@v1 | ||
with: | ||
version: ${{ matrix.version }} | ||
arch: ${{ matrix.arch }} | ||
- uses: actions/cache@v1 | ||
env: | ||
cache-name: cache-artifacts | ||
with: | ||
path: ~/.julia/artifacts | ||
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} | ||
restore-keys: | | ||
${{ runner.os }}-test-${{ env.cache-name }}- | ||
${{ runner.os }}-test- | ||
${{ runner.os }}- | ||
- uses: julia-actions/julia-runtest@v1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1 @@ | ||
|
||
deps/cocoa.dylib | ||
Manifest.toml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
name = "ObjectiveC" | ||
uuid = "e86c9b32-1129-44ac-8ea0-90d5bb39ded9" | ||
version = "0.1.0" | ||
|
||
[deps] | ||
CEnum = "fa961155-64e5-5f13-b03f-caf6b980ea82" | ||
|
||
[extras] | ||
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" | ||
|
||
[targets] | ||
test = ["Test"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,96 +1,182 @@ | ||
# ObjectiveC.jl | ||
|
||
*Objective-C bridge for Julia* | ||
|
||
|
||
## Quick start | ||
|
||
ObjectiveC.jl is a registered package, so you can install it using the package manager: | ||
|
||
```julia | ||
Pkg.clone("ObjectiveC") | ||
Pkg.add("ObjectiveC") | ||
``` | ||
|
||
ObjectiveC.jl is an Objective-C bridge for Julia. The library allows you to call Objective-C methods using native syntax: | ||
The library allows you to call Objective-C methods using almost-native syntax: | ||
|
||
```julia | ||
using ObjectiveC | ||
julia> using ObjectiveC | ||
|
||
@objc [NSString new] | ||
julia> @objc [NSString new]::id{Object} | ||
id{Object}(0x00006000008a4760) | ||
``` | ||
|
||
This makes it easy to wrap Objective-C APIs from Julia. | ||
For performance reasons, ObjectiveC.jl requires you to specify the type of the call and | ||
any arguments using Julia type-assertion syntax (`::id{Object}` in the example above). | ||
|
||
Although it is possible to build Julia APIs around this functionality, manually keeping | ||
track of `id` pointers, it is possible to have ObjectiveC.jl do this for you: | ||
|
||
```julia | ||
using ObjectiveC | ||
julia> @objcwrapper NSValue | ||
|
||
@classes NSSound | ||
julia> obj_ptr = @objc [NSValue valueWithPointer:C_NULL::Ptr{Cvoid}]::id{NSValue} | ||
id{NSValue}(0x00006000023cfca0) | ||
|
||
function play(name::String) | ||
@objc begin | ||
sound = [NSSound soundNamed:name] | ||
if [sound isPlaying] |> bool | ||
[sound stop] | ||
end | ||
[sound play] | ||
end | ||
end | ||
julia> obj = NSValue(obj_ptr) | ||
NSValueInstance (object of type NSConcreteValue) | ||
``` | ||
|
||
The generated `NSValue` class is an abstract type that implements the type hierarchy, while | ||
the `NSValueInstance` object is a concrete structure that houses the `id` pointer. This | ||
split makes it possible to implement multi-level inheritance and attach functionality at | ||
each level of the hierarchy, and should be entirely transparent to the user (i.e., you | ||
should never need to use the `*Instance` types in code or signatures). | ||
|
||
The `@objcwrapper` macro also generates conversion routines and accessors that makes it | ||
possible to use these objects directly with `@objc` calls that require `id` pointers: | ||
|
||
play("Purr") | ||
```julia | ||
julia> get_pointer(val::NSValue) = @objc [val::id{NSValue} pointerValue]::Ptr{Cvoid} | ||
|
||
julia> get_pointer(obj) | ||
Ptr{Nothing} @0x0000000000000000 | ||
``` | ||
|
||
|
||
## Properties | ||
|
||
A common pattern in Objective-C is to use properties to acces instance variables. Although | ||
it is possible to access these directly using `@objc`, ObjectiveC.jl provides a macro to | ||
automatically generate the appropriate `getproperty`, `setproperty!` and `propertynames` | ||
definitions: | ||
|
||
```julia | ||
julia> @objcproperties NSValue begin | ||
@autoproperty pointerValue::Ptr{Cvoid} | ||
end | ||
|
||
julia> obj.pointerValue | ||
Ptr{Nothing} @0x0000000000000000 | ||
``` | ||
|
||
ObjectiveC.jl also supports defining classes, using a variant of Objective-C | ||
syntax (which eschews the interface/implementation distinction): | ||
The behavior of `@objcproperties` can be customized by passing keyword arguments to the | ||
property macros: | ||
|
||
```julia | ||
@class type Foo | ||
@- (Cdouble) multiply:(Cdouble)x by:(Cdouble)y begin | ||
x*y # Note that this is Julia code | ||
@objcproperties SomeObject begin | ||
# simplest definition: just generate a getter, | ||
# and convert the property value to `DstTyp` | ||
@autoproperty someProperty::DstTyp | ||
|
||
# also generate a setter | ||
@autoproperty someProperty::DstTyp setter=setSomeProperty | ||
|
||
# if the property is an ObjC object, use an object pointer type. | ||
# this will make sure to do a nil check and return nothing, | ||
# or convert the pointer to an instance of the specified type | ||
@autoproperty someProperty::id{DstTyp} | ||
|
||
# sometimes you may want to convert to a different type | ||
@autoproperty someStringProperty::id{NSString} typ=String | ||
|
||
# and finally, if more control is needed, just do it yourselv: | ||
@getproperty someComplexProperty function(obj) | ||
# do something with obj | ||
# return a value | ||
end | ||
@setproperty! someComplexProperty function(obj, val) | ||
# do something with obj and val | ||
# return nothing | ||
end | ||
end | ||
|
||
@objc [[Foo new] multiply:5 by:3] | ||
#> 15 | ||
``` | ||
|
||
You can leave out the type to default to `Object`. So long as you don't change | ||
the type of the method, you're able to redefine it on the fly – even if you've | ||
already created instances of the class and used them as delegates. | ||
|
||
## using Cocoa | ||
## Blocks | ||
|
||
The library provides some basic wrappers for the Cocoa framework for creating | ||
GUIs. Despite having generally nice APIs Objective-C is ridiculously verbose, so | ||
it's handy to have Julia wrappers for most functionality. | ||
Julia callables can be converted to Objective-C blocks using the `@objcblock` macro: | ||
|
||
```julia | ||
using ObjectiveC, Cocoa | ||
Cocoa.init() | ||
win = window() | ||
julia> function hello(x) | ||
println("Hello, $x!") | ||
x+1 | ||
end | ||
julia> block = @objcblock(hello, Cint, (Cint,)) | ||
``` | ||
|
||
This will pop up a window with the title "Julia". Now let's try something more | ||
interesting: | ||
This object can now be passed to Objective-C methods that take blocks as arguments. Note | ||
that before Julia 1.9, blocks should only ever be called from Julia-managed threads, or else | ||
your application will crash. | ||
|
||
If you need to use blocks that may be called from unrelated threads on Julia 1.8 or earlier, | ||
you can use the `@objasyncblock` macro instead. This variant takes an `AsyncCondition` that | ||
will be executed on the libuv event loop after the block has been called. Note that there | ||
may be some time between the block being called and the condition being executed, and libuv | ||
may decide to coalesce multiple conditions into a single execution, so it is preferred to | ||
use `@objcblock` whenever possible. It is also not possible to pass any arguments to the | ||
condition, but you can use a closure to capture any state you need: | ||
|
||
```julia | ||
for α = linspace(0,π,50) | ||
@objc [win setAlphaValue:cos(α)^2] | ||
sleep(1/100) | ||
end | ||
julia> counter = 0 | ||
julia> cond = Base.AsyncCondition() do async_cond | ||
counter += 1 | ||
end | ||
julia> block = @objcasyncblock(cond) | ||
``` | ||
|
||
|
||
## API wrappers | ||
|
||
ObjectiveC.jl also provides ready-made wrappers for essential frameworks like Foundation: | ||
|
||
```julia | ||
julia> using .Foundation | ||
|
||
|
||
julia> str = NSString("test") | ||
NSString("test") | ||
|
||
|
||
julia> NSArray([str, str]) | ||
( | ||
test, | ||
test | ||
) | ||
|
||
|
||
julia> d = NSDictionary(Dict(str=>str)) | ||
{ | ||
test = test; | ||
} | ||
|
||
julia> d[str] | ||
id{Object}(0x836f2afbc3a7b349) | ||
|
||
julia> Dict{NSString,NSString}(d) | ||
Dict{NSString, NSString} with 1 entry: | ||
"test" => "test" | ||
``` | ||
|
||
You should see the window fade in and out again. | ||
|
||
If you're using [Juno](http://junolab.org), I encourage you to try uncommenting | ||
[this line](https://github.com/one-more-minute/ObjectiveC.jl/blob/65f8605657a9a5c7bf5eab6cea89c6c431ff332d/src/cocoa/cocoa.jl#L48) | ||
and pressing `C-Enter` to evaluate the class definition (after opening a window | ||
as above). You'll notice that the class is actually redefined on-the-fly, and | ||
you'll hear a popping sound as the `tick` method is called (and you can do the | ||
reverse to stop the sound, of course). | ||
|
||
## Current Limitations | ||
|
||
* Julia's FFI doesn't have great support for structs yet, so neither does | ||
ObjectiveC.jl. Luckily structs aren't too common in Objective-C APIs, and | ||
where they are used it's not too difficult to add wrappers (see | ||
[cocoa.m](deps/cocoa.m)) | ||
* Objective-C calls made from Julia are not as fast as they could be. This | ||
is fine for most GUI-related purposes, since most calls will be callbacks | ||
made by the Objective-C runtime, but may not be suitable for use with | ||
high-performance scientific computing libraries written in Objective-C. | ||
* Instance variables are not yet supported on classes. | ||
* Probably other things I haven't thought of; ObjectiveC.jl has not been used | ||
for any remotely large projects yet so proceed with caution. | ||
|
||
## Current status | ||
|
||
ObjectiveC.jl has recently been revamped, and is still under heavy development. Do not | ||
assume its APIs are stable until version 1.0 is released. That said, it is being used | ||
as the main FFI for [Metal.jl](https://github.com/JuliaGPU/Metal.jl), so you can expect | ||
the existing functionality to be fairly solid. | ||
|
||
In the process of revamping the package, some functionality was lost, including the ability | ||
to define Objective-C classes using native-like syntax. If you are interested, please take a | ||
look at the repository [before the | ||
revamp](https://github.com/JuliaInterop/ObjectiveC.jl/tree/22118319da1fb7601d2a3ecefb671ffbb5e57012) | ||
and consider contributing a PR to bring it back. |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.
64628ec
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JuliaRegistrator register
64628ec
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error while trying to register: Register Failed
@maleadt, it looks like you are not a publicly listed member/owner in the parent organization (JuliaInterop).
If you are a member/owner, you will need to change your membership to public. See GitHub Help
64628ec
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JuliaRegistrator register
64628ec
There was a problem hiding this comment.
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/79159
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: