iOS Memory Management
When talking about constructors, it is a generic object type being a pointer to any type. Every object is stored in a particular place in memory. For instance, when creating the person class, we create an object of the person type which we access with the use a pointer. A pointer informs us about the object type and about the type of the methods we can refer to. The pointer itself is a certain concept. In other programming languages, for instance in C or C++, there is the keyword ‘void’, which is a pointer in a chosen place in memory. It doesn’t identify any type, only the item that a given pointer points to. Its equivalent is, in a sense, the ID keyword which designates a pointer to any object we may have in our program.
We can assign such a pointer to any object. The ID type cooperates with the casting operation. It means that an ID object which doesn’t have any known type may be cast on an object we’re interested in. For instance, if we have a person type object, we can assign it to an object of the ID type. We don’t have to cast anything, because it may point to any object. If the system is able to perform in such an operation, we could cast an object of the ID type to any type, so that the ID object points to an object of the student type. We could cast it and gain access to the methods of this object. However, if the system is not able to do that, we cast on a class which is not higher in the inheritance hierarchy. Instead of returning an error, the system will assign a nil value, which is an equivalent of the null value from other programming languages.
The pointer won’t point to any object, which means that the object hasn’t been cast properly. We create an object of the person type by calling the methods alloc and init. Next, we create an ID object named ‘A.’ It should be noted that when creating a pointer of the person type, we use an asterisk; whereas, in the case of the ID variable, we don’t have to do so because the ID keyword itself informs the compiler that it’ll be a pointer. In the next step, we assign a value person to the variable ‘A.’ From now on, ‘A’ points to a class of the person type. Next, we could check whether we could cast an object of type ‘A’ to another variable of the person type. The casting operation proceeds exactly as we remember it from the other programming languages. In parenthesis, we enter the type of a given variable that we’ll be casting. In such a case, the variable ‘A’ points to an object of the person type. Now we can perform a proper object casting. In the last line, we see that we can’t cast an object type of an object of the NS string type. It results from the fact that the class of the person type is not higher than the inheritance hierarchy than the NS string type. So, in case of the casting of the object to the type of variable ‘S,’ a nil value will be assigned. There are unknown and non-compatible types, and instead of informing us about an error, the compiler will inform us about it by assigning the nil value.
The importance of memory management
Memory management is a vital issue when developing iOS applications. Some of you may have heard that the memory management in Objective-C is difficult, particularly when creating applications for the iOS system. Fortunately, this is no longer the case. The recent implemented technologies facilitate the memory management process a great deal. Let’s start from the beginning. What is memory management? It’s very important because it’s one of the most important issues in programming. Memory management means that when creating objects, we use memory which is limited. Current devices have so much memory that we may be under an impression that it will last us forever. However, it’s not true and with a couple of code lines, we can write a program that will fill all of it. That’s why we should release memory, if we don’t, we may simply run out of it. Incorrect memory management, the situation when we allocate memory that doesn’t free up later, leads to the so-called memory leak. It’s a bad practice, which may even cause the device to hang. In such cases, users have to restart the device. Certainly, we don’t want our application to cause it.
There are two memory management techniques in iOS. The first one is reference counting and auto-release pull blocks. The second technique is called ARC, which is an acronym for Automatic Reference Counting. The latter of the techniques has been introduced fairly recently. It’s gaining popularity due to its simplicity. In order to understand ARC , first, we need to understand what reference counting and auto-release pull blocks are. Both of these techniques differ from the ones used in high level programming languages. There is no so-called garbage collection. There is no background manager or domain that is occupied in searching for unused and unneeded memory. For instance, in the form of objects which exceed their own scope so that the programmer is no longer able to use them. None of the techniques mentioned include such a mechanism. It’s available only for users programming in Objective-C from MAC OSX platform.
In order to understand ARC, we must learn something about reference counting and auto-release pull blocks, which are older techniques. Unfortunately, they are relatively hard to understand and use. The general rules of their usage may seem simple, but using them requires more concentration, because it’s extremely easy to make a mistake. That’s why we recommend using the ARC method. We’ll discuss this technique in a moment. It will help us understand the source code and adjust it with the use of the relevant memory management technique. Reference counting is a more manual technique, analogical to working with objects in C++ language, where the memory for the object has to be allocated by the programmer. And once the work with the object is finished, it has to be released manually. We must remember that the object life cycle begins the moment the necessary memory is allocated for it, and ends when it’s released.
When finishing working with an object, we should close all files, release the memory and perform all the operations that clean up after the object. In some languages, such as C++, we use two statements for memory management; one that allocates memory and the other that deallocates it. If an object is no longer needed, we call the method that removes it, closes the files that are used, and removes the memory leaks. This technique was expanded in reference counting.
Every object has a current reference count assigned to it. The number of pointers that point to a given object is stored. It means that if any object is interested in another object, it receives a pointer to it and the count of the current references increases by one. If any program object is not interested in specific object, it lowers the reference count. Thanks to it, we know how many pointers point to a given object. If the object reference count reaches zero, it means that the object is no longer needed and can be removed. When we want to use a specific object, we should inform it that we create a pointer to it. If we stop using the object, we decrease the reference count. Every newly created object has a reference count equal to one, which is tantamount to the fact that this object is not needed at the moment. If its count drops to zero, the object is released and the memory used by it can be used to store other objects.
There are two methods of operating on the reference count which are inherited from the NS Object class. These methods are retained and released. Calling a retain method on an object increases the reference count by one. It means that a given part of the program is interested in the chosen object. If we’re no longer interested in the object, we should send a release method to it, which will lower the reference count by one. We can refer to the current count value by calling a method ‘retain count’. This method returns an integer that specifies how many pointers point to a given object. It may come in handy during the project debugging, searching for errors, or checking whether we have an added any unnecessary hookups.
First, we create a person object named ‘someone’. In the beginning, using the alloc and init method, we create a new object which the ‘someone’ pointer will point to. The object we created that way will reference a count equal to one. Next, somewhere in the source code, we call the retain method on this object, which increases the reference count by one. It has a value now of two. Then we use the release method which makes the retain count equal one. In the next two steps, we repeat the previous operations. In the end, we call release method to an object to retain count equals one. The count drops to zero and we inform the system that the object may be destroyed.
We should watch out how we use the methods “retain” and “release.” If we call the release method twice, the retain count equals -1, which is unacceptable and causes an error. We should pay particular attention to the reference count, because if the retain count of all objects does not drop to zero during the program and termination, it will cause memory leaks. If the reference count drops to zero, the object should be destroyed, deallocated, and all the files should be closed.
In C++, there’s a notion of destructor, a function that has to clean up after an object. It’s a function that is called after the method ‘free’, which releases memory in C++, is called on an object. It’s equivalent in Objective-C is a method called ‘dealloc’. Its task is to clean up memory. As the environment itself cannot control which files have been opened, we’re responsible for deallocating all the objects included in our class, as well as for cleaning up after our objects. We shouldn’t call the dealloc method in the source code. We can’t call the equivalent of a destructor. This method will be called by the system itself the moment the reference count drops to zero.
It’s a different situation from the one in C++, where we first allocate memory for a given object and subsequently call a destructor. In Objective-C, we allocate the memory as well, but instead of using a destructor, we bring a reference count of a given object. The environment detects such a state and calls the dealloc method. It also calls a method inherited from NS Object. Every class inheriting from NS Object we create will include this method.
The task of the dealloc method is to release memory, as we already know, is called by the environment the moment when a reference count of a given object drops to zero. If we added any variables in our class, we’re obliged to clean up after every object. If there’s an object in a class – in this case, my object – once the release method has been called on it, the dealloc method is called on it. Finally, we call dealloc method in the base class which we can refer to by using the appropriate keyword, ‘super’. Thanks to that, we make sure that all the elements in the inheritance hierarchy of this object are released.
Manual memory management is tedious, so it’s good to automate this process. It would be best not to worry about memory management at all. This is when the auto release pool comes in to action. It’s a technique which supports reference counting mechanisms. It makes it possible to automate part of this process. Every object that inherits from NS Object has a method called ‘auto release’, which can be called on any object. This method informs the system that a release message will be sent to the object in the future. Sending this message to the object causes it to be sent to the auto release pool. It means all the objects gathered in the pool will have a release method sent to them which will decrease their reference count. We may add to this pool all the objects we use, and send the release method to the entire pool using solely one function.
The pool is handled by an instance of the NSAutorelease pool class. In the slide, we see how to create the object of this type. We do the same as we did in the case of other objects using alloc and init methods. Next, in the program code, we send the auto release method to the objects we created. When creating a variable, we send the release method to it. If we send a release message to the created pool, it will be sent to every object present in that pool. This way, we call the release method only once in an object of the type NSAutorelease pool, which facilitates memory management a great deal. In case automatic reference counting is used for most projects created in Xcode, the entire program is included in one of the auto release pool instances. This method is called during every application startup cycle. Thanks to that, all the automatically released objects get removed from the memory every single cycle.
We should pay attention to the fact that NSAutorelease pool is something different from garbage collection. Thanks to this technique, the release method needs to be released only once. If we call the auto release method on an object, it doesn’t mean that we can forget about it and then it is magically removed from memory. Next, we get an object of ID type from the object array. Let’s assume that the object we get from the array is automatically released. That is, the moment we get it from the array, the auto release method is called. Every cycle run creates a new object, which is automatically released. However, we have to notice that the release message is only sent after we leave the loop. It means that for every loop cycle, we create an object we get from the array and next, the auto release method is sent to it. So, it gets added to the pool.
Moving onto the next loop cycle
After the operations we performed on this object, we exceed the range and return to the next loop cycle. It means that the object is still stored in the memory and that there’s a point or two at any auto release pool, every new loop cycle, a similar object is created. In this example, 10 million such objects were planned so 10 million such objects stored in memory will be created. The pool subsequently forwards the message to all the objects stored within it. In the case of memory shortage, after getting an object of the ID type, we should release it every loop cycle using the release method instead of using auto release pool. At first glance, it seems obvious, but during programming, it’s easy to make this mistake which may take long to correct. This is why we should watch out when we use manual memory management, and when we use it by a pool.
Ordinary manual memory management is used for all variables in a class and the pool is only used for local variables used in a given method, because we can’t know exactly when the memory pool will be released, particularly when the software is later used or expanded by third parties. Now that we’ve gotten to know how to use these methods, let’s get down to work. Each of the objects we created using the methods alloc, copy, and new should be manually released with a release method. Other objects need to be released by the auto release pool. It means that most standard objects we use in Objective-C have constructors that return objects, in which we have to use methods of manual memory management, or share methods which automatically return released objects.
Handling manually vs. a pool
So how do we recognize which objects should be handled manually and which ones should be using a pool? Objects of this type should be handled manually. They’re available for the programmer until the release method is called as many times as the retain method has been previously called.
The other variables set are constructors, which don’t include keywords such as alloc, copy, or new. In other words, the ones that don’t inform us about their memory usage and their name. The auto release method has already been sent to such objects, so they’ll be released soon. The majority of popular classes in Objective-C have methods of creating objects, which will be released with the use of the pool. Usually, they take the form of constructors which are no longer two-stage ones, but the entire object with a single call.
The relevant example here is stringWithFormat of the NS String class. Based on that, we can assume that this object will be released automatically. When assigning a variable value to a variable of this class, we have to be aware that if we want to refer to it after some time, it will be removed by the environment. That’s why you have to pay particular attention when developing software with the use of these memory management techniques.
Most frequent errors regardless of whether memory was released manually or the auto release pool was activated are connected with referring to a pointer that points to an empty place in the memory. The area that the debugger displays is ambiguous, which may hinder the search for the error. The two memory management techniques presented in iOs are reference counting as well as the method based on auto release pool. These two techniques are very popular in the previous iOS versions, namely 2, 3 and 4. They were used in all applications created for this type of system. However, when used improperly, they may cause errors which are hard to find. These errors may appear in situations when we want to refer to the memory that has already been released.
The ARC technique
The cause of such a situation is an incorrect implementation of memory management. The iOS 5 environment introduced a new technique named ARC. This technique is based on the two previous methods. Having learned about these methods will help you better understand how the ARC technique works. The ARC technique is fairly new, so many books and examples discuss only the two earlier methods. ARC stands for automatic reference counting. This technique was introduced with the 5th version of iOS system. It simplifies memory management a great deal. ARC places functions such as release, retain, and auto release in the source code, taking the burden off the programmer in this regard.
These functions are injected to the executable during the compilation. It means that the functions we’ve mentioned can’t be used in the source code. Calling them on an object will cause an error to appear as early as the compilation start. When using ARC, these functions are prohibited. We say ARC operates during compilation. It means that this technique is not tantamount to memory garbage collection.
Garbage collection known from other programming languages such as Java or C# works like this. In the environment, there is a manager that seeks objects which are not pointed to by any pointers and deletes them. ARC doesn’t do that. This technique only inserts functions retain, release, and auto release into the executable. The executable acts exactly the same as it would in the case of two previous techniques were used. The difference being that we don’t have to manually introduce the above mentioned functions. ARC detects places where the given functions should be used and inserts them automatically. Thanks to that, the programmers’ work is done by the compiler and Xcode. ARC is based on the concept that all pointers are divided into strong and weak ones. Actually, there are more pointer types. They are, in particular, pointers used when using frameworks or Objective-C functionalities written in pure C language.
Moving between an object part of the language and the fragments written in the low level frameworks requires a large number of pointers provided by ARC, but these are features intended for advanced programmers. That’s why when talking about basic use of ARC, we divide pointers only into strong and weak ones. If a pointer is strong, it means that it helps keep the object alive. As long as a pointer points to an object, that is, as long as the nova is not assigned to it or it doesn’t exceed the range, the object is kept alive.
Let’s see this in an example. For instance, we’d like to have a text field that enables the user to enter text from a keyboard and next, makes it possible for the text to be retrieved and processed in a specific manner. Let’s assume that the implemented class has a text field variable, which is responsible for a text field. One of the variables of this field is a variable named ‘text’, which is an object of the NS String type that includes data introduced by users using a keyboard. We’d like to assign the value of this field to an NS String variable named ‘firstname’. Let’s assume now that the field in question changed after a specific value has been assigned to it. It means that the value ‘firstname’ points to would be removed.
It’s analogical to the way string works in the languages such as C# or Java. A variable of string type can’t be modified after having been introduced. Changing the text accounts for creating a new NS String object, which will be located in the variable of the text type. The previous value of this variable, as well as the previous object is removed, because selftextfield text points to another object. What would happen if we auto release the memory? Textfield text would probably be an element of the auto release type so it would be released in the next application of cycle run. At that moment, it would turn out that the first name variables points to the memory that has already been released. Thanks to the ARC and an assumption that the firstname pointer is a strong pointer keeping the object alive, even if the field of the selftextfield text variable has changed, since firstname points to the previous text, the object wouldn’t be removed from the memory until no string pointer points to this object. So if we introduced a new text to text field and assigned the previous text to the firstname variable, this value would not change.
As a rule, when using ARC and objective-C, all pointers are strong. It means that the pointer we created in our example is strong as well. We can also explicitly declare the pointer’s strength by using the keyword ‘strong’ preceded by two underscores, the way it was presented at the bottom of the slide below. Thanks to that, we could be assured that the firstname pointer is a strong pointer. But actually, even without it, every pointer is regarded as strong. There are also weak points that don’t keep objects alive. If such a pointer points to a memory that gets released, then, unlike strong type pointer, the value is not only no longer kept alive, but it could also be removed by the system.
If a pointer points to a deallocated memory, it will be assigned nil value and calling it will cause a memory access error. In order to declare such a variable, we use a similar technique. As we can see in the slide, when declaring a pointer, before entering its type, we have to use the keyword ‘weak’ preceded by the two underscores. We create an object of type NS String which will be a weak pointer. A question arises: what will happen in the example in the slide? We said that weak pointers don’t keep objects alive. When comparing it to previous methods, they don’t increase the reference count of a given object. In our example, we assign an object created using functions alloc and init reformat to a weak pointer of the NS String type. Next, in a different place in the code, we’d like to print the value of the object of the standard output using the NSLog function. However, instead of printing what we expected, there’s a null value.
Let’s follow the code once again. In the first line on the right, we create an NS String type object. We assign a pointer to this object, a weak one. It means that no strong pointer points to an object. For the system, it means that such an object may be released, because its reference count equals zero. In the NSLog function, a null value appears, though we expected the NS String to appear here. Why then should we use weak pointers at all? Wouldn’t it better to use only strong pointers? In most cases, the programmer will only use strong pointers. They’re required to operate on variables, as well as to prevent the newly created objects from being released. However, there are situations when weak pointers are very helpful. Let’s assume that we have a GUI element. For instance, an element list. We can compare it to the track list from iTunes program on iOS devices. Using this program, we can browse a list of tracks. It works as follows: an object list is stored in the container, and each object presents a specific track. The container object mentioned above must have strong pointers to all objects in the list. However, it makes no sense for every object to have a strong pointer to the container type object. It’s better for the object on the list to have a weak pointer to the container object.
In a situation like this, if a pointer to the container part kept in the upper layer of the application that manages the entire GUI is released, the pointer to the list will be released and the list similarly to the container part can be released as well. If we add strong pointers to the container part from the side of the list, we’d have a bidirectional relation, which would make it impossible to release variables. So how do we start ARC? When creating the project, there’s a “use ARC” check box, which should be chosen. Once we check it, we’re able to use the solution in the project.
We begin by declaring a variable of the ID type which can point to an object of any type. We assign the object from the array located under the index zero to this variable. Next, we remove the object from the array. It’s an object which we just assigned to the OBJ pointer. After performing these operations using the NSLog function, we’d like to pass the description regarding the contents of the OBJ object to the NS String format to the standard output.
What will happen in the case we use ARC, and what will happen if we use the memory management methods mentioned earlier? If we used reference count and auto release pool, NSLog would print a null. In the first line, we get a printer that points to an object with a reference counter equals one. In the next line, the object assigned to the previous line is removed from the array. The function remove object index decreases the reference counter by one, which releases the object. Next, in the NSLog function, we try to print the contents of the description method for the OBJ object, which has already been released. In the case of ARC, the ID pointer, just as any other pointer that doesn’t have any specifier entered is regarded as a strong pointer. It means that it keeps alive the object we get from the array. Even if we remove the index from the array in the second line, the OBJ pointer still keeps alive the object removed from the array. It means that in the NSLog function, we’re able to print its contents because the object hasn’t been removed from the memory. At first glance, it’s a simple code fragment which may, however, cause problems when using older techniques. ARC has a great advantage over other solutions and facilitates writing code.
Memory Management students also learn
Empower your team. Lead the industry.
Get a subscription to a library of online courses and digital learning tools for your organization with Udemy for Business.