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

Modernize the package #11

Merged
merged 64 commits into from
Mar 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
91eda41
Replace REQUIRE with Project.toml.
maleadt Feb 28, 2023
c809c43
Fix deprecations until the package is loadable.
maleadt Feb 28, 2023
4618063
Add a smoke test.
maleadt Feb 28, 2023
332b494
Fix hostname function.
maleadt Feb 28, 2023
b8330bc
Remove unused/broken Cocoa wrappers.
maleadt Feb 28, 2023
9b5a456
Add some CI.
maleadt Feb 28, 2023
307733d
Put Foundation wrappers in their own submodule.
maleadt Feb 28, 2023
47c2e6a
Don't import to override methods.
maleadt Feb 28, 2023
d3728a1
Add other useful actions.
maleadt Feb 28, 2023
f3a68dc
Use a generated function, not eval, to call objc_msgSend with varargs.
maleadt Mar 1, 2023
b9431a8
Fix method iteration.
maleadt Mar 1, 2023
b522b39
Test I/O.
maleadt Mar 1, 2023
1ac67dd
Use statically-defined type info instead of relying on reflection.
maleadt Mar 1, 2023
423c3c5
Add back the hacky ccall wrapper so that we can use derived types in …
maleadt Mar 2, 2023
87202d7
Add back framework loading.
maleadt Mar 2, 2023
125e490
Use syntax to represent the (currently unused) type information.
maleadt Mar 2, 2023
86fbb87
Conversion back to String.
maleadt Mar 2, 2023
3a9f0b3
Show the description of NSObject methods during verbose printing.
maleadt Mar 2, 2023
4e385b1
Retain and release are part of Foundation.
maleadt Mar 2, 2023
d3dff01
Set name in selector and support more expressions inside at-objc.
maleadt Mar 2, 2023
eced9ac
Support splitting at-objc expressions over multiple lines.
maleadt Mar 2, 2023
77a0081
Behave similarly to objective C.
maleadt Mar 2, 2023
51c4d9e
String comparisons.
maleadt Mar 2, 2023
c965a25
NSArray.
maleadt Mar 2, 2023
5aa00f5
Rework the at-classes helper for importing classes.
maleadt Mar 3, 2023
520be76
Add better error messages on at-objc failure.
maleadt Mar 3, 2023
a7a2449
Add integer aliases.
maleadt Mar 3, 2023
91f3600
YES/NO/nil are ObjC constructs.
maleadt Mar 3, 2023
aac61bd
Refuse to construct nil objects.
maleadt Mar 4, 2023
a8c859f
Split generated classes into an abstract and concrete instance.
maleadt Mar 4, 2023
9ec8057
Add NSRange.
maleadt Mar 5, 2023
cbcc6df
Add a macro for auto-generating property methods.
maleadt Mar 6, 2023
1a815ad
Wrap NSDictionary.
maleadt Mar 6, 2023
29c0538
Wrap NSError.
maleadt Mar 6, 2023
8f54dea
Improved Dict conversions.
maleadt Mar 6, 2023
0923617
Fix userInfo dict access.
maleadt Mar 6, 2023
78f8d6a
Better display for NSError.
maleadt Mar 6, 2023
2d2665c
Actually make id a parametric pointer type.
maleadt Mar 6, 2023
adfb975
Conversion to typed array.
maleadt Mar 6, 2023
b0ea877
Initial GCD wrappers.
maleadt Mar 6, 2023
01d0223
Fix for 1.7-
maleadt Mar 6, 2023
2a8f041
NSValue.
maleadt Mar 6, 2023
be305b3
NSNumber.
maleadt Mar 6, 2023
fad6dfe
NSURL.
maleadt Mar 6, 2023
39dd3de
Default to emitting comparisons for mutable structs.
maleadt Mar 6, 2023
cf51209
NSRange from UnitRange.
maleadt Mar 7, 2023
d36add2
NSURL comparisons.
maleadt Mar 7, 2023
1c4868d
Add ability to check kind of object.
maleadt Mar 7, 2023
9b94d6c
Bugfixes.
maleadt Mar 7, 2023
a0d0c8a
Add some NSObject functions.
maleadt Mar 7, 2023
6191667
setproperty invoke bugfix.
maleadt Mar 7, 2023
39932a4
Add Protocol type.
maleadt Mar 7, 2023
2d24d6b
Fix a couple of more deprecations
maleadt Mar 7, 2023
4520fbb
Initial support for blocks.
maleadt Mar 7, 2023
ffd8984
Error when using closure blocks.
maleadt Mar 7, 2023
960aa5f
Add support for blocks with closures.
maleadt Mar 7, 2023
ce04137
For Julia 1.8, add a block macro that wakes the libuv event loop.
maleadt Mar 7, 2023
71ec81c
Don't store the AsyncCondition to prevent allocating loads.
maleadt Mar 8, 2023
e31db9f
Change test for 1.6 compatibility.
maleadt Mar 8, 2023
f9e23f1
Reorganize code a little.
maleadt Mar 8, 2023
eb876b1
Remove broken '@class' functionality and its dependencies.
maleadt Mar 8, 2023
b26f340
Rework the README.
maleadt Mar 8, 2023
b6bd3c3
Remove unused entry from .gitignore.
maleadt Mar 8, 2023
d7d683c
Add some references on blocks.
maleadt Mar 8, 2023
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
45 changes: 45 additions & 0 deletions .github/workflows/CompatHelper.yml
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 }}
11 changes: 11 additions & 0 deletions .github/workflows/TagBot.yml
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 }}
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
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
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@

deps/cocoa.dylib
Manifest.toml
12 changes: 12 additions & 0 deletions Project.toml
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"]
212 changes: 149 additions & 63 deletions README.md
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.
2 changes: 0 additions & 2 deletions REQUIRE

This file was deleted.

8 changes: 0 additions & 8 deletions deps/Makefile

This file was deleted.

1 change: 0 additions & 1 deletion deps/build.jl

This file was deleted.

Loading