Recently read two books, "Objective-c advanced Programming, iOS and OS X multithreading and Memory Management", "effective object-c2.0". iOS development must read two books, very recommended. But look at memory management when there is a word very do not understand: you build and hold the object, the reference count is 1. There is another sentence: When the object alloc, the default reference count is 1. When you create your own, if the default is 1, then you hold him again, to add 1, this time is 2. But the actual result is 1. See a lot of information, I understand that: when creating an object by default is 0, reference count, as the name implies, refers to the count of the object. Strong reference once, reference count +1. The object is no longer referenced, and the reference count-1. Out of scope, reference count-1. The above is a simple understanding of memory management, detailed content to read. There is a Tang Qiao's post, written very good, turn to show you:
Understanding Memory management for IOS
The story of the ancient times
People who have experienced manual management of the Memory (MRC) era must remember memory management in IOS development. At that time about 2010, the domestic IOS development has just emerged, Tinyfool uncle's name has been thunderclap piercing, and I am still a unknown just graduated kid. The IOS development process at that time was this:
We write a code for IOS, then hold our breath and start running it, as expected, it crashes. In the MRC era, even the most awesome IOS developers are not guaranteed to write the perfect memory management code at once. So, we started the step-by-step debugging, trying to print out a reference count for each suspect object (Retain count), and then we carefully inserted the reasonable retain
and the release
code. After time and again the application crashes and debugs, finally one time, the application can run normally! So we have a sigh of relief, a long-lost smile.
Yes, this is the IOS developer of that era, and normally, after we've developed a feature, it takes a few hours to manage the reference count.
Apple presented an automatic reference count (ARC) at the WWDC conference in 2011. The principle behind ARC is to rely on the compiler's static analysis capabilities to completely liberate the programmer by finding a reasonable insert reference count management code at compile time.
When the arc came out, the industry was full of doubts and wait-and-see, and the existing MRC code to do the migration would have required additional costs, so ARC is not quickly accepted. Until around 2013, Apple thought the arc technology was mature enough to simply discard the garbage collection mechanism on MacOS (then called OS X), which made the arc quickly accepted.
At the WWDC conference in 2014, Apple introduced the Swift language, which still uses ARC technology as its memory management approach.
Why do I have to mention this period of history? It's because the IOS developers are so comfortable, most of the time they don't even care about the memory management behavior of the program. But while ARC helped us solve most of the problem with reference counting, some young IOS developers would still do poorly in memory management . They do not even understand common circular reference problems, which can lead to memory leaks, eventually making the application run slowly or being terminated by the system.
So, each of our IOS developers needs to understand the memory management approach to reference counting, so that we can handle memory management related issues.
What is a reference count
The reference count (Reference count) is a simple and effective way to manage the object life cycle. When we create a new object and hold it, it has a reference count of 1, and when a new pointer points to the object, we add its reference count to 1, and when a pointer no longer points to the object, we subtract its reference count by 1, and when the object's reference count becomes 0 o'clock, the object is no longer pointed to by any pointers. At this point we can destroy the object and reclaim the memory. Because the reference count is simple and effective, Microsoft's COM (Component Object Model), c++11 (c++11 provides a smart pointer based on reference counting, in addition to the Objective-c and Swift languages share_ PRT) also provides a memory management approach based on reference counting.
To be more graphic, let's look at a objective-c code. Create a new project, because the default project now has an automatic reference count ARC (Automatic Reference count), we first modify the project settings, give APPDELEGATE.M -fno-objc-arc
the compilation parameters (as shown), This parameter enables manual management of reference count patterns.
Then, we enter the following code in, you can see the corresponding reference count changes through the Log.
-(BOOL) application: (uiapplication *) application didfinishlaunchingwithoptions: ( Nsdictionary *) launchoptions{NSObject *object = [[nsobject alloc] init]; NSLog (@ "Reference Count =%u", [object Retaincount]); nsobject *another = [object retain]; NSLog (@ "Reference Count =%u", [object Retaincount]); [Another release]; NSLog (@ "Reference Count =%u", [object Retaincount]); [Object release]; //Here, the memory of object is freed. return YES;} |
Operation Result:
Reference count = 1Reference Count = 2Reference count = 1 |
Students who are familiar with the Linux file system may find that the reference count is managed in a similar way to hard links inside the file system. In the Linux file system, we use the ln
command to create a hard link (equivalent to our retain here), when deleting a file (equivalent to our release), the system call will check the file's link count value, if it is greater than 1, the disk area occupied by the file is not recycled. Until the last deletion, the system finds that the link count value is 1, and the system does not perform a straight delete operation, marking the disk area occupied by the file as unused.
Why do we need reference counting
From the simple example above, we don't see the real usefulness of reference counting. Because the object's lifetime is only within a function, in a real-world scenario, we use a temporary object within the function, usually without modifying its reference count, simply destroying the object before the function returns.
A really useful scenario for reference counting is in object-oriented programming architecture, where data is passed and shared between objects. Let's give a concrete example:
If object a generates an object m, you need to call a method of object B and pass the object m as a parameter. In the absence of a reference count, the principle of general memory management is "who applies for release", then object A needs to destroy object M when object B no longer needs the object m. But object B may be just a temporary use of the object m, it may also feel that object m is important, set it to its own member variable, in this case, when to destroy the object m becomes a problem.
In this case, there is a violent practice, that is, object A after the end of the call to object B, immediately destroy the Parameter object M, then object B needs to copy the parameters of another, generate another object M2, and then manage the lifetime of the object M2. But there is a big problem with this approach, which is that it brings in more memory applications, duplication, and release of work. Originally a reusable object, because it is inconvenient to manage its life, it is simple to destroy it, and reconstruct a copy of the same, it really affects performance. As shown in the following:
We also have another way to do this is Object A after the object m is constructed, the object m is never destroyed, and object B is done to destroy the object M. If object B takes a long time to use object M, it does not destroy it, and if it is used only temporarily, it can be destroyed immediately after it is exhausted. This approach seems to solve the problem of object duplication, but it relies heavily on the mates of the AB two objects, which the code maintainer needs to remember explicitly in the programming conventions. Furthermore, because the application of object M is in Object A, it is freed in object B, so that its memory management code is scattered among different objects, and it is very laborious to manage. If the situation is more complicated at this time, such as Object B needs to pass object M to object C, then this object cannot be managed by object C in Object C. So the complexity of this approach is greater and more inaccessible.
So the reference count is a good solution to this problem, in the transfer process of the parameter M, which objects need to use this object for a long time, the reference count is added 1, after the use of the reference count minus 1. All objects are subject to this rule, and the lifetime management of the object can be completely given to the reference count. We can also easily enjoy the benefits of sharing objects.
Do not send messages to objects that have already been disposed
Some students want to test that when the object is released, it retainCount
becomes 0, and their test code is as follows:
-(BOOL) application: (uiapplication *) application didfinishlaunchingwithoptions: (nsdictionary *) Launchoptions{NSObject *object = [[nsobject alloc] init]; NSLog (@ "Reference Count =%u", [object Retaincount]); [Object release]; NSLog (@ "Reference Count =%u", [object Retaincount]); return YES;} |
But if you're really experimenting with this, you get the output that might be the following:
Reference count = 1Reference count = 1 |
We noticed that the last output, the reference count did not become 0. What is this for? Because the object's memory has been reclaimed, and we send a RETAINCOUNT message to an object that has been reclaimed, its output should be indeterminate, and if the memory used by the object is reused, it could cause the program to crash unexpectedly.
So why is this indeterminate value 1 instead of 0 after the object is recycled? This is because when the last release is executed, the system knows that the memory is being reclaimed immediately, there is no need to reduce the retaincount by 1, because the object will certainly be recycled, regardless of minus 1, and after the object is recycled, all of its memory areas, including Retaincount Values also become meaningless. Not to change this value from 1 to 0, you can reduce the memory write operation, accelerate the object's recycling.
Take the Linux file system we mentioned earlier, delete a file under the Linux file system, and do not actually erase the disk area of the file, but just delete the index node number of the file. This is similar to how memory is recycled in reference counting, where only tokens are used for recycling, and the associated data is not erased.
Memory management issues under ARC
ARC is able to solve 90% of the memory management problems in IOS development, but there are also 10% memory management that need to be handled by the developer themselves, primarily the part that interacts with the underlying core foundation object, because the underlying core foundation object is not in the ARC Management, so you need to maintain the reference count of these objects yourself.
For new IOS people who blindly rely on ARC, their problems are mainly reflected in the fact that they do not know the reference count:
- The circular reference problem cannot be resolved after the block is over-used.
- When encountering the underlying Core Foundation objects, it is helpless to manage their reference counts manually.
Circular reference (Reference cycle) issues
Reference counting this way of managing memory is simple, but there is a big flaw, which is that it does not solve circular reference problems very well. As shown in: Object A and Object B, referencing each other as their own member variables, the reference count of the member variable is reduced by 1 only if you destroy it yourself. Because the destruction of object a relies on object B destruction, and object B's destruction is dependent on object A's destruction, this creates the problem of what we call circular references (Reference cycle), which cannot be freed even if no pointers are available to them in the outside world.
More than two objects have circular reference problems, multiple objects hold each other sequentially, form a ring, can also cause circular reference problems, and in the real programming environment, the larger the ring is more difficult to find. is a circular reference problem formed by 4 objects.
Actively disconnecting circular references
There are two main ways to solve the circular reference problem, the first way is that I know that there will be a circular reference, in a reasonable position to actively disconnect a reference in the ring, so that the object can be recycled. As shown in the following:
Active disconnection of circular references is common in various block-related code logic. For example, in my open source Ytknetwork network library, the callback block for the network request is held, but if there is a reference to the View Controller in this block, it is easy to generate a circular reference because:
- Controller holds the network request object
- The network Request object holds the block of the callback
- The callback block is used
self
, so it holds the Controller
The solution is that after the network request ends, the network Request object executes the block and voluntarily releases the hold on the block in order to break the circular reference. See the relevant code:
https://github.com/yuantiku/YTKNetwork/blob/master/YTKNetwork/YTKBaseRequest.m//line 147th:-(void) Clearcompletionblock {//Active release of reference to block Self.successcompletionblock = Nil;self.failurecompletionblock = nil;} |
However, the active disconnection of the circular reference is dependent on the programmer's own manual explicit control, which is equivalent to returning to the previous memory management age of "who applied for release", which relies on the programmer's own ability to discover circular references and to know at what time to break circular references to reclaim memory (which is usually related to specific business logic). So this workaround is not commonly used, and the more common approach is to use the weak reference (weak reference) method.
Using weak references
Weak references, while holding objects, do not increase the reference count, thus avoiding the generation of circular references. In IOS development, weak references are typically used in delegate mode. For example, two Viewcontroller A and b,viewcontroller a need to eject Viewcontroller B, let the user enter some content, when the user input is completed, Viewcontroller B needs to return the content to Viewco Ntroller A. At this point, the delegate member variable of the View Controller is usually a weak reference to avoid two viewcontroller referencing each other causing circular reference problems, as follows:
How weak references are implemented
The implementation of weak references is so that the system maintains a table for each object with weak references to record all of its weak reference pointer addresses. Thus, when the reference count of an object is 0 o'clock, the system passes through the table, finds all the weak reference pointers, and then resets them to nil.
From this principle, we can see that the use of weak references has additional overhead. Although this overhead is small, it should not blindly use weak references if there is a place where we are sure that it does not require weak references. For example, someone likes to set all the interface elements to weak in the handwriting interface, which is somewhat consistent with the new variables that Xcode generates by Storyboard. But I personally do not think it is appropriate to do so. Because:
- When we create this object, we need to be careful to use a strong reference to hold it temporarily, otherwise, because the weak variable does not hold the object, it causes an object to be destroyed just after it is created.
- The life cycle of most viewcontroller view objects is consistent with the viewcontroller itself, and there is no need to do this extra.
- The previous design of Apple was a historical reason. In the early years, when the system received Memory Warning, Viewcontroller's View will be unLoad off. At this point, it is useful to use the weak view variable to keep the memory from being recycled. However, this design has been deprecated, and the alternative is to calayer the corresponding Cabackingstore type of memory area of the relevant view into a volatile type, as described in "Goodbye, Viewdidunload method".
Using Xcode to detect circular references
Xcode's Instruments toolset makes it easy to detect circular references. To test the effect, we fill in the following code in a test Viewcontroller, which firstArray
references each other in the code and secondArray
makes a circular reference.
-(void) viewdidload{[super viewdidload]; Nsmutablearray *firstarray = [Nsmutablearray array]; Nsmutablearray *secondarray = [Nsmutablearray array]; [Firstarray Addobject:secondarray]; [Secondarray Addobject:firstarray];} |
In the Xcode menu bar, select: Product--profile, then select "Leaks", then click on the "Profile" button in the lower right corner to start detection. Such as
This time the IOS simulator will run, and we'll do some switching of the interface in the simulator. Wait a few seconds and you can see that Instruments has detected our circular reference. A red bar is used in the Instruments to indicate the generation of a memory leak. As shown in the following:
We can switch to the Leaks column and click "Cycles & Roots" To see the circular references displayed graphically. This makes it very easy to find the object that the loop refers to.
Memory management for Core Foundation objects
Let's take a brief look at the memory management of the underlying Core Foundation object. The underlying Core Foundation objects are created mostly in the form of xxxcreatewithxxx, such as:
Create a Cfstringref object Cfstringref str= cfstringcreatewithcstring (kcfallocatordefault, "Hello World", KCFSTRINGENCODINGUTF8); Create a Ctfontref object ctfontref fontref = Ctfontcreatewithname ((cfstringref) @ "ARIALMT", FontSize, NULL); |
For the modification of reference counts for these objects, use CFRetain
and methods are appropriate CFRelease
. As shown below:
Create a Ctfontref object ctfontref fontref = Ctfontcreatewithname ((cfstringref) @ "ARIALMT", FontSize, NULL); Reference count plus 1CFRetain (FONTREF);//reference count minus 1CFRelease (FONTREF); |
For CFRetain
and CFRelease
two methods, the reader can intuitively think that this is equivalent to the Objective-c object retain
and release
method.
So for the underlying Core Foundation object, we only need to continue to manually manage the reference count in the previous way.
In addition, there is another problem to be solved. Under ARC, we sometimes need to convert a Core Foundation object into a Objective-c object, and at this point we need to tell the compiler how the reference count in the conversion process needs to be adjusted. This introduces the bridge
relevant keywords, the following are descriptions of these keywords:
__bridge
: Do only type conversions, do not modify the reference count of related objects, and the original Core Foundation object needs to call the Cfrelease method when not in use.
__bridge_retained
: After type conversion, the reference count of the related object is added to 1, and the original Core Foundation object needs to call the Cfrelease method when it is not used.
__bridge_transfer
: After the type conversion, the reference count of the object is given to ARC management, and the Core Foundation object no longer needs to call the Cfrelease method when it is not in use.
We can solve the problem of relative conversion between Core Foundation objects and Objective-c objects by using the above 3 conversion keywords rationally according to the specific business logic.
Summarize
With the help of ARC, the memory management of IOS developers has been greatly reduced, but we still need to understand the advantages and common problems of the memory management method of reference counting, especially to solve the circular reference problem. There are two main solutions to circular reference problems, one is to actively break the circular reference, and the other is to avoid circular references by using weak references. For Core Foundation objects, because they are not under ARC management, we still need to continue the method of manually managing reference counts in the past.
When debugging memory problems, the Instruments tool can assist us very well, and the use of Instruments can save us a lot of debugging time.
Every iOS developer is willing to learn about iOS memory management skills.
Memory Management for iOS