3 ms·
- It takes the right approach to memory management. Realizes that garbage collectors are abominations, and that managing memory is really the job of either the
by rudiger 14y ago
- It takes the right approach to memory management. Realizes that garbage collectors are abominations, and that managing memory is really the job of either the developer or the compiler.
Please elaborate why Objective-C's approach to memory management is Right, and why GC is Wrong.
- SeoxyS 14y agoBecause memory management is something that's known at compile time, and doesn't need to be evaluated at runtime. It's the same reason a compiler will essentially substitute the expressions `2 + 2` to simply `4` and save the extra CPU cycle. Most GCs do a barely acceptable job of managing memory, and often lead to hard-to-diagnose bugs. With manually managed memory, you know exactly what's going on and can easily inspect what's actually going on by diving into a debugger. You're also not wasting memory letting the runtime hold onto things you don't need anymore, or wasting CPU cycles trying to figure out what's still needed. Additionally, and importantly, without manual memory management, you cannot safely use pointers and memory directly. The whole idea of playing with memory directly by doing `void buffer = malloc(100)`, and being able to do pointer arithmetic such as `(buffer + 99) = NULL` goes out of the window. Lastly, Automatic Reference Counting is awesome in that it brings you all the benefits of manual memory management (not having to worry about making mistakes, and saving yourself a little typing) without any of the drawbacks.
- flatline3 14y agoI'd just like to touch on GC vs ARC/RefCounting, and leave the buffer mangling for another comment. There are advantages and drawbacks to GC vs reference counting (whether manual or automated), but the disadvantages of GC certainly do not include doing "barely acceptable job of managing memory, and often lead to hard-to-diagnose bugs." I'd provide a more complete rebuttal, but lacking any specific examples, I can only definitively state that GC significantly reduces the likelihood of having to debug memory related issues, especially tracking down leaks due to reference cycles. In comparison to GC, the main advantage of reference counting systems is that they are deterministic. However, they do a lot of book-keeping that is expensive in aggregate and may not even be necessary in a garbage collected system. There are GC designs that mix GC and reference counting to achieve the best of both -- another poster in this thread provided a link to some papers on the subject. The major downside to reference counting -- other than the expensive-in-aggregate book keeping mentioned above -- is, as mentioned above, that they can't handle reference cycles. On iOS it's terrible easy to create cycles, and prior to the introduction of __weak, it was almost impossible to build thread-safe code that also involved a cyclic reference, due to it being impossible to invalid another thread's reference to your object (short of hacking out your own zeroing weak reference implementation, which is what people were forced to do). This ease of creating cycles, especially with blocks and GCD, leads to a proliferation of careful and manual application of __weak designators on self-references. Instead of simply using an instance variable from a block, you must be certain to only access it through a __weak-marked references. Additionally, you now have to worry about NULL-dereferencing! Example: __weak MyObject *weakself = self; _block = ^{ // Whoops! weakself could be nil here. Should have used a property, // which would have just passed a nil argument to runWithTX. Whoops! [opManager runWithTX: weakself->_txid]; // Also, weakself could be deallocated *after* the above call succeeds, // so what we really needed to do as a first step in the block was // capture a -strong- reference to weakself, and return immediately // if we'd been deallocated MyObject *strongSelf = self; if (self == nil) return; // Whoops, accidentally referenced self via _txid below, // causing a retain cycle NSLog(@"Running with _txid=%@", _txid); }); This is awful. If the language used a hybrid ARC refcounting with cycle breaking, that code would simply read: _block = ^{ [opManager runWithTX: _txid]; NSLog(@"Running with _txid=%@", _txid); });
- jim_kaiser 14y agoI think you are a little confused as to how exactly ARC works. ARC is mostly compile-time and adds the memory management code automatically during compile time. So, the runtime overhead compared to a GC is much lower. http://stackoverflow.com/questions/7874342/what-is-the-difference-between-objective-c-automatic-reference-counting-and-garb http://stackoverflow.com/questions/7874342/what-is-the-diffe... Regarding reference cycles, it is not a difficult job at all for any decent coder to work it out. It is a design issue in your application if you think its hard. If you use protocols and delegates correctly, you can manage it quite simply with the rule of storing delegates as weak pointers. If you use blocks, you just have to avoid the case of both storing a block in an object and referencing the object from the block. I had not used a Mac till about 6 months ago and in the last 6 months, I lead the development of an enterprise scale app for iOS dealing with the above issues quite easily by having a good architecture. I have previously worked professionally for around 4 years with Qt/C++ and found it extremely easy to make the switch to iOS IMHO.
- flatline3 14y agoI'm not confused as to how ARC works. Reference counting systems require a high level of bookkeeping for short lived objects as compared to GC systems. Dealing with cycles is not complex, but it is error prone, verbose, and difficult in aggregate as compared to having cycles correctly and automatically handled.
- flatline3 14y agoAfter covering reference counting in my first reply, I want to tackle the pointer arithmetic question here. Pointer arithmetic specifically, or direct memory access more generally, is bug prone and dangerous. Its valuable primarily in terms of performance, and is a terrible idea in terms of correctness and security. In runtime-managed languages, efficient access to memory buffers is achieved by exposing an array type, and then performing basic bounds checking on array access. This is efficient enough for most purposes, is essentially what you achieve by using NSData, and is generally implemented in a way that is vastly more efficient than NSData. Direct memory access in straight C will be faster, but it's also enormously error prone, and something I avoid if at all possible when writing ObjC or even straight C. While there's no discounting the performance value of being able to operate at this level (or write straight assembly), it's rarely a useful thing to do when writing most application-level code, especially when weighed against the propensity for failure. All managed languages allow for extension through C for the cases where low-level performance is required. ObjC has the easiest means of working with C, but that should be balanced against the fact nearly all of ObjC's ugly warts and pain points derive from being a strict C superset.
- pcwalton 14y agoReference counting is a form of garbage collection. It does spend CPU cycles trying to figure out what isn't needed, so your claim that it doesn't "waste CPU cycles" is not true. Its advantages over tracing GC are that memory can be reclaimed promptly, that it does a pretty good job of spreading out the CPU load in an incremental sense, and that it's simple to implement and reason about. The big downside is cycles; lots of large reference-counted codebases constantly struggle with cycles (for example, Firefox). Adding cycle detection to a reference counted system is not easy to do in a performant way and almost nobody that I'm aware of but Firefox even attempts to do it. Without unsafe memory management, you can still do pointer arithmetic. See Cyclone's fat pointers, Go's slices, Rust's slices, etc.