(Sorry for the question, I am not an iOS, Mac dev.)
(Sorry for the question, I am not an iOS, Mac dev.)
Objective-C is a superset of C. If you think about what it means to have a garbage-collected version of C for just a few minutes it should become immediately clear why abandoning GC for Objective-C is necessary and appropriate. The GC can't always reliably detect a reference (pointer) so it has to be conservative. It also can't compact the heap, which means you don't get the nice "allocation is just advancing a pointer" behavior that makes allocations nearly instantaneous in managed languages.
ARC is essentially just the old rules for manual reference counting (MRC), but the compiler analyzes your code and inserts the retain/release/autorelease calls automatically. This gives you similar deterministic behavior as MRC but while writing the code it feels like a garbage collected language because you mostly ignore management of memory and everything "just works".
The downside to ARC is the inability to detect retain cycles and a slight performance hit compared to MRC in some scenarios. The performance is actually pretty good... What ARC loses compared to hand-coding it gains by using special runtime functions (not slower ObjC message sends) that elide unnecessary retain/release calls in many situations.
objc_retainAutoreleaseReturnValue is inserted in a caller and callee (typically a property getter). It examines the return address on the stack to detect if the caller is going to invoke objc_retainAutoreleaseReturnValue on the returned value; if so it just does a retain and sets a flag the caller uses to skip doing retain+autorelease, leaving the caller's release only. Instead of the object staying alive until the next run loop iteration and getting autoreleased it can safely be released immediately, plus at least one extra pair of function calls can be eliminated.
Microsoft's C++/CX introduced in 2011 is also very similar; the hat syntax[1] is to COM's AddRef/Release as ARC is to -retain/-release. In both schemes, what used to be manually refcounted objects are now managed by the compiler. I don't recommend C++/CX though, it is confusing to C++ devs. Smart pointers are a better recognized idiom.
[1] Unrelated to the hat syntax from MS's previous attempts, Managed C++ and C++/CLI, which are GC and not refcounting as in C++/CX.
In the general case, it is estimated that GC uses on average twice as much memory (high water mark), but direct comparisons are of course very application specific. GC also has the dreaded collection pauses which can make performance unreliable. (Eg stuttering animations)
Biggest drawback of ARC is that it doesn't detect cycles, so the developer needs to have a global understanding of an app to avoid hard to debug leaks.
(Edit: Perl is garbage collected since version 6, and Visual Basic is garbage collected since version 7. I am not aware of any language besides Objective C moving in the opposite direction.)
It is hard to get right with existing code bases.
Additionally C++11 got a GC API, but requires explicit programmer control.
http://www.stroustrup.com/C++11FAQ.html#gc-abi
C++'s memory model is tainted by C's compatibility, as such it is quite limited in the GC algorithms that can be implemented.
How can you guarantee that the component library that your company just bought, available only in binary form isn't doing something like this?
void cool_stuff_with_widget(const std::shared_ptr<UI> ptr)
{
UI* evilPtr = const_cast<UI*>(ptr->get());
// now store evilPtr somewhere else and try to use it any time the library feels like it
}
No one is going through disassembly to check such behaviors.C++ RC is a very welcoming addition to the language, but it only works if everyone plays by the rules, 100% of the time.
It's just better documented and easier to spot the bugs if you're doing RC.
It is impossible to prove that all memory access to a given address are done via the RC wrapper object, specially in the presence of third party libraries in binary form.
Sadly due to its C compatibility, there is no way around this in C++.
This is mostly a problem in large projects with various skill levels across team members, having high attrition levels.
There is no Visual Basic 7. Visual Basic .NET is garbage collected as it runs on the CLR, but is quite a different language to VB6.
Similarly, VB vs VB.NET.
All solutions are not optimal, but unless you have really many allocations and dealloctions going on, have tight resource constraints or hard timing constraints, current garbage collectors are really good enough. Not that you can not shoot your feet by accidentally keeping large object graphs alive, but that is easy to detect, though sometimes hard to fix.
Some developers seem to have some kind of resentments against garbage collectors because real developers of course manage their resources on their own, but many of their arguments are not well founded. Garbage collectors nondeterministically pausing program execution to free unused resources is a common point, but they usually don't realize that automatic reference counting has the same problem - you never know when a reference count drops to zero and triggers the deallocation of the object possibly with a large object graph attached.
Also, its not really fair to sweep "hard timing constraints" under the rug as a minority use case when these are essential for smooth modern GUIs.
Sure, GC is pretty much ideal in terms of absolving developers of resource management responsibilities, but it comes with some significant downsides which can easily effect the final product and should be understated. Rust is a great example of a new language which avoids GC for this reason.
If Java had a placement syntax like
new (HeapReference) SomeClass(); => allocate in custom Heap
Then you could just turn off GC for that heap, and then blow the whole thing away when it got full. Or, you could have a more fine grained API to allow GC on these custom heaps. Perhaps you could even allow copying objects back to the main heap. You'd have to ban cross heap references or have a smart API that lets users pin an object in the main heap while it is known to be referenced by an object in custom heap.
Most of the time in client applications, you only care about GC pauses on the main UI rendering thread, but GC pauses in other threads are less obvious because there's no jank, just that latency may go up for some operations.
You could have a kind of best of both worlds with languages that support non-GC allocation for your UI thread, but GC everywhere else.
The thing is that most discussions tend to be reduced to RC vs GC, as if all GCs or RCs where alike, whereas the reality is more complex than that.
RC, usually boils down to dumb RC, deferred RC or RC with couting elision via the compiler. Additionally it can have weak references or rely on a cycle collector.
Whereas GC, can be simple mark-and-sweep, conservative, incremental, generational, concurrent, parallel, real time, with phantom and weak references, constrainted pause times, coloured....
As for frame drops during animations, I think pauses in a missile control system is not something one wishes for:
http://www.militaryaerospace.com/articles/2009/03/thales-cho...
http://www.spacewar.com/reports/Lockheed_Martin_Selects_Aoni...
Heap compaction is another part of GC which results in long non-deterministic pauses. But the real problem is having fragmentation in the first place, this is something which manual allocation strategies can go a long way to mitigate.
For any sufficiently complex real world application you will not in general know when a deallocation occurs and that is one of the reasons you decided to use automatic reference counting in the first place. Some short lived objects within a function are non-issues to begin with and are easily handled manually if you want to, but the interesting bits and pieces are objects with unpredictable life time and usage pattern because they are heavily influenced by inputs.
I think you've confused determinism with regards to a program and the inputs to that program. Any retain/release program is deterministic because it always behaves in the same manner. Given any possible input I can tell you exactly when and on which lines of code the deallocations will occur. You can't know that with GC, especially when other libraries and threads enter the equation.
Which deallocations occur and in which order depend on the input to the program, but we still know in advance all possible lines of code where specific deallocations can occur and we have complete control over that. With GC we wouldn't know that and we couldn't control it.
Knowing when deallocations can occur and having control over that is the reason to choose ARC over GC in the first place. Not the other way around. With respect to manual retain/release, ARC doesn't take anything away: I can still retain an extra count of a large object to prevent its untimely release and share that object via ARC at the same time.
It's also worth noting that GCs rarely does anything because it feels like it, but because you are allocating past a certain limit. Knowing the size of objects, and given that the allocator can provide you with the current size of allocated objects, you should be able to determine if a GC is likely to incur in the next lines of code. This determination becomes harder when threads enter the picture, unless you're using a GC scheme like Erlang, which have one GC per process (lightweight thread). Some GCs also allow you to pause and resume them, for when you really want to avoid GC cycles, this can of course lead your program to crash if you allocate past your limit.
As always in computer science, 'it depends'.
After a while working with refcounting, I found myself writing code whose natural flow meant I was actually pretty sure, at least for deallocations large enough to care about. I don't even think about it any more, really, I just continue to enjoy my objects going away when I expect them to.