Delegate dangling pointer problem on iOS

Source: Internet
Author: User

The article is a bit long, the process of writing is very rewarding, but the process of reading is not necessarily a harvest, cautious into

The crash problem caused by the "abstract" dangling pointer (dangling pointer) is often encountered during the iOS development process. This type of problem, which is caused by delegate, is more common. This paper starts with a uiactionsheet-induced delegate dangling pointer problem, and gradually thinks and tries to solve several kinds of problems and compare them. The "Body" uiactionsheet is a common iOS system control, the usage is very simple, implements the Uiactiondelegate protocol method, then through the Showinview: And so on method pops up. Let's look at a piece of code: (without special instructions, the code in this article is written under arc conditions)
-(void) popupactionsheet{    uiactionsheet* sheet = [[Uiactionsheet alloc] Initwithtitle:nil                                                        delegate:self                                               Cancelbuttontitle:nslocalizedstring (@ "Str_cancel", @ "")                                         destructivebuttontitle:nslocalizedstring (@ "Str_ Delete ", @" ")                                               Otherbuttontitles:nil];    [Sheet ShowInView:self.view];}

  like this with a local variable pop-up Actionsheet code loved.   So is there a problem with that?   To see a bug in a project: page x, press and hold area y click Button B, then click the button C (Close), release after the page X exit, but actionsheet a pop-up, click on the button, the program crash.   From the description it is not difficult to see the problem: This is a dangling pointer problem. Click button B, this should pop up Actionsheet A, but due to some special operations (specific reasons are different, here is the holding area y), this actionsheet pop is delayed, when it pops up, Its delegate (usually a uiviewcontroller or a uiview, here is the page's Viewcontroller X) has been destroyed, so delegate became a dangling pointer, click the button, When you send a message to delegate, crash appears.   In order to prevent most of the delegate in retain Cycle,ios is not to increase the reference count (weak reference) of the object (X), it is easy to dangling pointer problem. For this type of problem, there are usually two resolution directions: one is to determine whether delegate is still valid before sending a message to delegate, and the other, to make the object x Dealloc, to set all delegate that point to X to nil.   for direction One, it looks beautiful, if you can tell before sending a message whether a pointer is dangling pointer, then we have the last line of defense, never again this kind of crash problem. However, when dangling pointer really appears, we should also reflect on the design of the code to see if there is an unreasonable place, rather than simply capture and discard in this way.   In contrast, the direction of the second "destroy the empty" this scheme appears to be more fundamental, but also a good programming habit. By far, not limited to delegate, all weak reference pointers can be handled this way.   This is the concept of the weak pointer introduced in Arc, which automatically resets to nil when the object is Dealloc. That is, as long as all delegate are weak types, such dangling pointer problems will cease to exist. This article can also end here.   But the reality is always brutal. First, weak pointers are only available under arc conditions in iOS 5.0 and later, and many projects still need support iOS4.3. Of course, with the release of IOS7, this situation will improve. But even if all the users are 5.0+, the problem is still unresolved. Why?   Our custom delegate can be used in all weak types. But what about the system controls? Like Uiactionsheet, look at the uiactionsheet.h:  under the iOS7.0 version SDK.
@property (nonatomic,assign) ID delegate;    Weak reference

Even at 7.0, the delegate of these system controls is still not weak, but assign, which is about the reason for compatibility with non-ARC environments. In other words, the weak pointer does not resolve the dangling pointer problem with the system control delegate. Is this swelling? Flowers bloom Two, one for each table. Let's go back and look at another question: Why does Actionsheet have this dangling pointer problem? The direct reason is that Viewcontroller X as delegate is destroyed, and actionsheet a itself is still displayed. But this a is clearly show on the Self.view, why Self.view are not, it will exist? Let's take a look at the following code:
-(void) Viewdidappear: (BOOL) animated{    uiactionsheet *sheet = [[Uiactionsheet alloc] initwithtitle:@ "ABCDE" Delegate:self cancelbuttontitle:@ "Cancel" Destructivebuttontitle:nil Otherbuttontitles:nil];    NSLog (@ "Application windows:%@", [uiapplication sharedapplication].windows);    [Sheet ShowInView:self.view];    NSLog (@ "self.window:%@", Self.view.window);    NSLog (@ "sheet.window:%@", Sheet.window);    NSLog (@ "Application windows:%@", [UIApplication sharedapplication].windows);}


The main operating results (for iphone, the situation on the ipad slightly different) are as follows:

Application windows: (    UIWindow ...)//actionsheet before pop-up, only 1 UIWindowself.window:UIWindow ... Sheet.window: _ Uialertoverlaywindow ... application windows: (UIWindow ...    Uitexteffectswindow ... _uialertoverlaywindow ...) After the actionsheet pops up, there are 3 UIWindow

The original iOS application is not the only one window,actionsheet is on the other inside the window (the case is different on the ipad, there is only one window, Actionsheet is a subview on Showinview's superview called Uidimmingview), and Showinview: The view specified in the method does not hold a relationship, So it is possible to exist without relying entirely on the latter's life cycle, so it is not surprising that dangling pointer delegate appear. Now that we know the ins and outs, we can begin to solve the bug at the beginning of the article. According to "in an object x Dealloc, set all points to the X delegate is empty" the central idea of the "direction two", weak pointer is not useful, we can only find another way.  Through analysis, we know that the key to implementing this "central idea" is: how to get all the Actionsheet a delegate point x when x dealloc? Since the article at the beginning of the popular code, Actionsheet is a local variable popup, in Viewcontroller X dealloc, we have not access to the local variable, how to do? Idea 1:

Use one instance variable v to save Actionsheet. The Dealloc method in X is V.delegate = nil. This is undoubtedly the easiest way to think, without repeating it. Just pay attention to one problem: Actionsheet is able to pop multiple (or successive) POPs (we'll see the black mask of the background superimposed with the number of pop-up actionsheet, getting deeper. In this way, we either use an array to hold the actionsheet pointers, or we will have to dispose of the old (or delegate empty, or simply dismiss) each time a new one pops up. This idea, the advantage has 2:1, the idea is simple, the code adds the quantity to be few; second, if you are writing an ipad app, that anyway to cope with the change of screen re-layout actionsheet also need this instance variable, more than one swoop.

There are 2:1 shortcomings, this way of universality is poor, we need to write this code for each of these x, if this is an existing project, and this project almost all of the actionsheet is the local variable pop-up, how to do?          How much code do we need to change? Second, actionsheet as a system control, viewcontroller in most cases just control pop-up and implement delegate method, do nothing else, this is why the above-mentioned code, Because there is no need to cite this actionsheet elsewhere. Add an instance variable in the class to save the pointer only to solve the problem of dangling pointer, and even to save a pointer array, and this part of the code is also coupled with the code of the class itself logic, a neat person seems to always feel dazzling.  The ideal solution to the problem of dangling pointer should be a common basic method, independent of the business logic of the class, and the code is relatively independent. Idea 2:

Without instance variables, try to get actionsheet pointers when delegate dealloc. The system's view tree must have saved the Actionsheet pointer, the first reaction is to play tag on the Actionsheet, and then use the Viewwithtag: method to obtain. Or, when dealloc, traverse the entire view tree to find the current actionsheet, both of which are essentially the same. We will not discuss the cost of traversing the view tree for the moment, only discussing the feasibility of the method. As we said just now, the Actionsheet on the iphone belongs to an internal window, not in the window where our program is controllable, so the selection of root nodes is the key.
Uiactionsheet *sheet = [[Uiactionsheet alloc] initwithtitle:@ "sheet" delegate:self cancelbuttontitle:@ "Cancel" Destructivebuttontitle:nil Otherbuttontitles:nil];    [Sheet ShowInView:self.view];    [Sheet Settag:ksheettag];    NSLog (@ "root (Self.view.window):%@", [Self.view.window Viewwithtag:ksheettag]);  Null    NSLog (@ "root (internal window):%@", [[UIApplication sharedapplication].windows[2] Viewwithtag:ksheettag] ); Actionsheet found!


As a result, we do not traverse this actionsheet on the current window, and we need to traverse the _uialertoverlaywindow on the previous call. So we can first in the actionsheet create a tag, and then in the X Dealloc method to write this: (Can not cope with multiple actionsheet pop-up situation)
[[UIApplication sharedapplication].windows enumerateobjectsusingblock:^ (id obj, Nsuinteger idx, BOOL *stop) {        if (strcmp (Class_getname ([obj class]), "_uialertoverlaywindow") = = 0)        {            Uiactionsheet *thesheet = ( Uiactionsheet *) [obj Viewwithtag:ksheettag];            [Thesheet Setdelegate:nil];        }    }];

You can also take a walk through the view tree without playing the tag. (if it's on the ipad, don't use the internal window, just traverse your self.view.superview's subviews, you can experiment on your own)
[[UIApplication sharedapplication].windows enumerateobjectsusingblock:^ (id obj, Nsuinteger idx, BOOL *stop) {         [self traverseview:obj];    }]; Traverse View Tree-(void) Traverseview: (UIView *) root{    [[Root subviews] enumerateobjectsusingblock:^ (id obj, Nsuinteger IDX, BOOL *stop) {        if ([obj Iskindofclass:[uiactionsheet class]])        {            if (((Uiactionsheet *) obj). Delegate = = Self)            {                ((Uiactionsheet *) obj). Delegate = nil;                NSLog (@ "enemy spotted!");            }        }        else        {            [self traverseview:obj];}}    ];}


This also solves the problem, its advantages are 1:1, do not change the class X business logic part of the code, modify the scope to the X Dealloc method, relatively easy to transplant to other classes, but also through some runtime means to achieve automation. Second, the method of traversal, can easily deal with the situation of popping multiple actionsheet. Its disadvantage is 2:1, lift traversal view tree, the cost is definitely to consider. Take a project as an example, a VC in Dealloc time, the view tree has a total of 320 view, traverse looking for Uiacionsheet delegate empty needs about 0.002s, and the normal Dealloc method only need to 0.0001s, the time to increase 20 times times.          To tell you the truth, the cost is not too big to be tolerated, and whether it is cost-effective depends on the situation. Second, if you want to save this overhead, then you need to use some "unspoken rules", such as the Viewwithtag method code above, the use of _uialertoverlaywindow this class name to narrow the traversal scope. Unspoken rules this thing, if used, please use with caution. They are completely document-protected, possibly the next-generation iOS, and this implementation is changed, and our code will be out of the problem. For example, through the hack system imagepicker way to achieve the multi-chart selection box, is a more common and "reasonable" use of "unspoken rules" example (because the general use of assetslibrary implementation of the multi-chart selection box on the iOS5 will have the location permission of the problem, This is a lot of products are not willing to accept things, but iOS7, Imagepicker's internal Viewcontroller name is changed, the original use this way to achieve multi-chart selection box code, you need to follow up the changes. Idea 3:
From ideas 1 and 2 we can get such an inspiration, if there is a set that contains all delegate points to x Actionsheet A (or even other object instances), then we can traverse this set at dealloc to place a.delegate = nil. The above-mentioned set of S has the following characteristics: 1, S can be implemented with an object x 1 to 1 binding or corresponding, and can be accessed when x dealloc. 2, at the right time (such as setting delegate), can add or remove elements to s we first press 1 and 2 to abstract a generic containing set S class structure, named parasite:
@interface delegatebaseparasite:nsobject{    nsmutableset *sanctuaryset_;//Set S}//Create and bind yourself (parasite) to Hostobj X + (Delegatebaseparasite *) Parasitizein: (ID) hostobj; Returns the Parasite object (or nil if unbound) that has been bound (corresponding) to Hostobj x + (Delegatebaseparasite *) Getparasitefromhost: (ID) hostobj;// Add an object to the collection of this parasite s, when object.delegate = Hostobj x-(void) Addtosanctuary: (ID) object; Remove object from this parasite collection s, when Object.delegate is no longer =x-(void) Removefromsanctuary: (ID) object;// nil-(void) Redemptionall The delegate of all objects in the sanctuary (which point to hostobj at this point); @end

The main idea is that if each x is bound to a delegatebaseparasite p (corresponding), when setting A.delegate = x, Call Addtosanctuary to add a to the set of P s (while the Removefromsanctuary method removes a from the old delegate binding parasite's collection s), and the X The problem is solved when Dealloc executes the Redemptionall method to empty the delegate property of all objects in the collection S. There is nothing complicated about the method of collection S operation. The focus is on how to implement object X's one-to-one binding with parasite p. We found that this parasite object has the following characteristics: 1, completely unrelated to the type and implementation of the host, there is no method to call the host or access any instance variables. 2, only need to call their own method when the host Dealloc, and they are also destroyed. This makes us think of a thing called Associate object (associated objects document)! You might want to bind Delegatebaseparasite as a associate object to X. To derive a Delegateassociativeparasite class from this idea, implement the binding related methods:
#define Kdelegateassociativeparasitesanctuarykey "Kdelegateassociativeparasitesanctuarykey" @implementation Delegateassociativeparasite#pragma mark-#pragma public interface+ (Delegateassociativeparasite *) ParasitizeIn: (ID) hostobj{    Delegateassociativeparasite *parasite = [[Delegateassociativeparasite alloc] init];        Objc_setassociatedobject (hostobj, &kdelegateassociativeparasitesanctuarykey, parasite, OBJC_ASSOCIATION_ retain_nonatomic);    return parasite;} + (Delegateassociativeparasite *) Getparasitefromhost: (ID) hostobj{    return Objc_getassociatedobject (HOSTOBJ, &kdelegateassociativeparasitesanctuarykey);} -(void) dealloc{    [self redemptionall];} @end

Unconsciously, we have succeeded in half. That is, there is another half of the problem that we need to solve: we need to remove element A from the collection s that is bound to the old delegate in Actionsheet A's setdelegate: method, and add a to the set S bound to the new delegate X. Also call [self setdelegate:nil] in the Dealloc method of a To add code to manually modify each time you call setdelegate? This is obviously not a good way, and, actionsheet dealloc timing, not controlled by us, want to manually add code can not be done. So there is no way to modify the implementation of these two methods, and this modification can also be called to its original method implementation? Inheriting a delegateautounregistereduiactionsheet out of course can do, but to replace all the Uiactionsheet, still have to do a lot of works, and, function is only on Uiactionsheet on the automatic logout delegate patch, no need and should not adopt the way of inheritance. Can you use category? Category replication main class The same name method produces warning, which is strongly deprecated by Apple, and cannot invoke the original implementation even if it forcibly duplicates the method of the same name as the main class. So what do we do? There are some methods that can be provided using the OBJC runtime. First, we use Class_addmethod to add a new method to the class, call the original implementation in the new method, then use Method_exchangeimplementation to exchange it with the original implementation. (OBJC runtime documentation) According to this idea we can write an auxiliary class Delegateautounregisterhelper class (code see attachment Example Project).

In this way, the other half of the problem is solved, now simply call in MAIN.M:
[Delegateautounregisterhelper Registerdelegateautounregisterto:[uiactionsheet class]];

Can realize the Actionsheet delegate automatic empty function.   This solution, which uses associate object and runtime, has its advantages and disadvantages. The advantages are two:      one, Delegateassociativeparasite and Delegateautounregisterhelper added to the project two classes are completely independent of other classes of code, Not related to business logic, clear logic.       Second, simple to use, only need to call a method in MAIN.M to register the target class (Uiactionsheet). "Loved" code before the project does not have to make any changes at all. Its disadvantage has a:      A, generalized dangling pointer delegate most of the scene is actually multithreading. One thread frees the delegate object, while another thread is using it exactly. Looking at the code we just wrote, we didn't consider any thread-safety issues at all.       We can't help but ask two questions:     1. How can i solve the delegate problem of Uiactionsheet without regard to thread safety?      2. This idea of using associate object can solve the problem of thread safety by means of lock/semaphore etc.   1 is determined by Uiactionsheet's usage scenario, as a system UI control, in most cases its setdelegate, Dealloc, Showinview, and so on, are called in the UI thread. And its delegate is generally a uiview or uiviewcontroller, the destruction of these two objects is usually also occurred in the UI line thread (in fact, If we find out that some of our views or Viewcontroller have been released for the last time and destroyed to a non-UI thread, we should stop and think about the design problem, because the release of view and VC is likely to involve some actions that can be performed on the UI thread. Of course, I am talking about most cases, not absolute. Therefore, the usual use of actionsheet does not involve thread-safety issues.   So come to question 2, this binding method with associate object as the core, is it possible to solve the thread safety problem?   A scrutiny, natural defects are exposed.   Before weHas deliberately blurred a concept, that is, "when X dealloc". When was the Dealloc? Dealloc before or after Dealloc?   for associate object, its Dealloc method is called after the Dealloc method invocation of its host x is complete, that is, after the host X has been destroyed. That is, delegate is empty after delegate is destroyed. No matter how short the interval, there is always a moment, X has been destroyed, delegate has not been empty, dangling pointer appear, if it is in a multi-threaded scenario, there may be another thread at this time access to this dangling pointer, The program will still be crash.   So, the solution based on the associate object, in the final analysis, does not solve the problem of thread safety.   So how do you make a thread-safe solution to the dangling pointer delegate problem?   Idea 4:

Since the problem is on the Associate object, then we don't need it to think of any other way of implementing X-to-p binding (correspondence). Then we think of the weak pointer again. How does the system bind (correspond to) the object with the weak pointer set that points to it? With regard to the implementation of the weak pointer, we can see the relevant document content (http://clang.llvm.org/docs/AutomaticReferenceCounting.html) on the llvm.org, but not in detail. The more direct way is to read the http://www.opensource.apple.com/source/objc4/inside the NSObject and runtime implementation of the source code. In short, the compiler's implementation of the weak pointer is consistent with our central idea of binding object X and a set of pointers to the X that need to be monitored, and automatically emptying the elements in the collection at x dealloc. But compared with the Associate object method, there are two different points: 1. The binding object is a global hash table (sidetable), not associate object. The key of the hash table corresponds to an object x,value as a pointer collection. 2. At the time of Dealloc, refers to the X Dealloc method call process, not the final destruction, so there is no natural defect, its thread safety problem can be solved by locking on the hash table. In this way, let's derive a new Delegatedictparasite class and implement another method that leverages Cfdictionary's binding (corresponding):
 @implementation delegatedictparasite+ (Delegatedictparasite *) Parasitizein: (ID) hostobj{if (!class_ Getinstancemethod ([Hostobj class], @selector (myhostobjdealloc))) {[Delegatedictparasite Addnewmethodtohost:[hos        Tobj class]]; [Delegateautounregisterhelper mergeoldsel:[delegateautounregisterhelper Deallocselector] NewSEL: @selector (        MYHOSTOBJDEALLOC) Forclass:[hostobj class]]; [Delegateautounregisterhelper mergeoldsel:[delegateautounregisterhelper Releaseselector] NewSEL: @selector (    Myhostobjrelease) Forclass:[hostobj class]];        } Delegatedictparasite *parasite; @synchronized (Kdelegateassociativeparasitelock) {if (!delegatehostparasitehashtable) {Delega tehostparasitehashtable = cfdictionarycreatemutable (kcfallocatordefault, 0, NULL, &        Kcftypedictionaryvaluecallbacks);        } parasite = [[Delegatedictparasite alloc] init]; Cfdictionarysetvalue (delegatehostparasitehashtable, (__bridge const void *) (Hostobj), (__bridge const void *) (parasite)); } return parasite;}        + (Delegatedictparasite *) Getparasitefromhost: (ID) hostobj{delegatedictparasite *parasite;  @synchronized (Kdelegateassociativeparasitelock) {if (!delegatehostparasitehashtable) {return        Nil    } parasite = Cfdictionarygetvalue (delegatehostparasitehashtable, (__bridge const void *) (hostobj)); } return parasite;} @end

Here, because without the help of associate object, X dealloc and parasite dealloc linkage need ourselves to trigger, the same use runtime, we can rewrite each X Dealloc method to complete this linkage, to remove the hash The binding of X in the table, which causes automatic emptying. In addition, through the lock, we can solve the thread safety problem. So as to solve the dangling pointer problem of delegate under multi-thread.     (See appendix for complete code) This kind of idea, its merit has 2:1, has not had the congenital flaw, solves the thread security question, thus may generalize to the generalized dangling pointer delegate question. Second, the usage and the way of thinking are like three, relatively simple.

Its disadvantage has two:      One, with a global hash table. People who are usually neat and tidy see global variables as uncomfortable.       Two, for each class that becomes delegate object X, will modify its Dealloc method, not like associate object linkage so natural, a bit unclean.   Idea 5: github has a Mikeash write open source project Mazeroingweakref, the purpose is to provide a weak pointer implementation without supporting weak, the implementation of the idea is similar to the system weak pointer, That is, the use of global hash table to do. Unlike the idea 4, it modifies the Dealloc method of X by dynamically inheriting a subclass of X and then addmethod on the subclass, rather than using method_exchangeimplementation.   This project takes into account more situations, such as support for KVO and the processing of toll-free CF objects (but with the use of private APIs), and so on, everyone has the interest and time to study, not to repeat.   Its advantages are two:      One, consider the KVO/CF and other conditions of support, more rigorous.       II. dynamic inheritance reduces the scope of the Dealloc method modification to just using an instance of weak instead of all instances of this class, solves the disadvantage of the idea 4 two. Its disadvantage has two:      One, dynamic inheritance of the way to modify the name of the class.       Two, just used to implement weak pointer in the condition that weak cannot be used, can solve the dangling pointer problem of custom delegate, Does not resolve the issue of system control delegate that has been specified as assign type in the article.   NOTE: Due to space limitations, some pits and interesting places in the implementation process are not mentioned in one by one. For example, when modifying the implementation of a method, it is important to note whether the parent or subclass method is modified, and some method implementations can only be placed in a non-arc (add-FNO-OBJC-ARC flag) file;    "Summary"   this paper gradually think and summarize several ideas to solve dangling pointer problems have advantages and disadvantages, there is no certain best, to the specific situation of specific analysis. In contrast, the idea of 4 is to solve the multi-threaded delegate dangling pointer a more complete solution. ThinkThere are many immature places in the examination and implementation process, welcome to discuss together, the wrong place also welcome the criticism.

Delegate dangling pointer problem on iOS

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.