On 07/02/2022 20:21, Fred Kiefer wrote:
Yes, ARC is a real improvement for application code. Not so much for libraries,
as GNUstep is. Getting reference counting correct in procedural programming is
not too hard in most case, but doing so declaratively just seems easier. You
need to be very careful and provide the correct hints for the compiler to sort
out things for you. I would expect that a switch here will cost us about two
years until we got most of the declarations correct. Still the change would be
worthwhile.
I think you're making up an artificial difference here. The rules for reference
counting are the same for both application and libraries: If you own/use an
object retain it, once you no longer own/use it (auto)release it. The tricky
point just is avoiding cyclic references because they will never be released.
But the real problem (usually) starts when programmers start trying to outsmart those
simple rules by thinking that some object is already retained by some other
container/instance variable and they can save some extra CPU cycles (plus the textual
clutter) by omitting the retain/release calls. And I know what I'm talking about because
I often enough fall prey to this sort of "optimization". But, as the issue with
NSPopUpButtonCell shows, I'm not the only one. Note that there is no semantic reason to
use an unretained reference for the _selectedItem instance variable.
So for -gui the main issue is managing references between objects and their
delegates and avoiding cyclic reference between parents and children in the
view hierarchy. And otherwise not compromising the code by the attempting to
omit retain/release otherwise. For -base, the difficulty is not so much cyclic
references, but multithreading. You have to be even more careful about
retaining objects because -- at least without serious locking, which comes with
its own set of problems -- you cannot be sure that assumptions that hold at
entry of a method are still valid at the end. So, in a way I feel its even the
other way around: In libraries ypu should be very sure about proper management
of ownership, which is better done automatically, whereas in applications you
can eventually be more sloppy, for instance when you know that the application
is single threaded.
What you are describing is about what I wanted to say, but obviously failed to
do. Things are a lot easier in an application as you know what you are aiming
at. In a library you need to be correct for whatever use case people come up
with. Doing that is hard. ARC may be able to help you in most cases. Still you
will have to be very careful and you will need a lot more understanding of the
implicit semantics. I agree that we should be going that route, we just need to
be aware that it is a steep road and we need to be careful. Just as you said.
If anything, ARC is more important for libraries than application code,
for precisely this reason. ARC explicitly defines ownership (strong,
weak, unsafe_unretained) for consumers of a library interface. Things
like delegates become weak references and so this is both explicit in
the API (the AppKit component does not own the delegate) and less error
prone (the reference becomes nil if the object is deallocated).
When Apple moved their Foundation and AppKit implementations to ARC,
they reported that they had less source code, smaller binaries, faster
code, and fewer bugs. The compiler does a lot of the
reference-count-elision things for you and it's easier to define APIs
that return strong references and don't leak, so you can significantly
reduce autorelease pool pressure.
Many years ago, I ran the -base and -gui code through the ARC Migration
Tool (built into clang). I think about half of the code worked with ARC
already, a lot of the files needed some small tweaks. The migration
tool pointed out all of the places where it couldn't do the automatic
translation.
ARC also makes it a lot easier to interop with C++. C++ containers can
store Objective-C object pointers in ARC mode and will treat the retain
/ release calls as copy constructors and destructors. WinObjC made
heavy use of this and the last time I tried it replacing
`NSMutableDictionary` with a thin wrapper around
`std::unordered_map<id>` (with a hash function defined to call `-hash`
and the equality function defined to call `-isEqual:`) it was faster
than GNUstep's implementation in my tests. I didn't try the same thing
with `NSMutableArray` and `std::vector<id>`. I'm told that Apple's
implementations do this: implement the containers in C++, wrap them in
Objective-C (and there are a lot of high-performance C++ containers with
friendly licenses that we could use if the standard library ones aren't
adequate).
This was a big part of the reason that I gave up contributing to
GNUstep. I could write significantly simpler code that ran faster using
Objective-C++ and ARC than I could without either and GNUstep would not
accept the less-buggy and faster option. Refactoring code to make it
less maintainable so that it could compile with an old compiler and
spending time finding bugs that are impossible by construction with ARC
felt like a complete waste of my time.
Given that Apple is now focused on SwiftUI, GNUstep is so far behind
things like GTK and Qt in completeness, and the license is not one that
I'd choose to use given any alternative, I don't imagine that I'd start
contributing again even if GNUstep adopted Objective-C++ and ARC, but at
least the remaining contributors would be able to achieve more with the
same amount of effort.
David