iOS circular referencing common scenarios and workarounds

Source: Internet
Author: User

Many scenarios cause circular references, such as using blocks, threads, delegates, notifications, and observers, which can cause circular references.

1. Commission

Following a rule, the principal holds a strong reference from the agent, and the agent holds the weak reference of the principal.

In a real-world scenario, a delegate is a controller object, which may be an object that encapsulates a network request and obtains data.

For example: Viewcontroller need to get data from the network, so that after the display to the list, the class obtained from the network is DATAUPDATEOP

//VIEWCONTROLLER.M-(Ibaction) onrefreshclicked: (ID) Sender {//Action object for the scene to obtain dataSelf.updateop = [DataupdateopNew]; [Self.updateop startusingdelegate:self Withselector: @selector (ondataavailable:)];}- (void) ondataavailable: (Nsarray *) Records {//when the task is complete, set the Action object to nilSelf.updateop =Nil;}//Cancel the operation if the controller Delloc- (void) Delloc {//Cancel    if(Slef.updateop! =Nil)        {[Self.updateop cancel]; }}//DataUpdateOp.h@protocoldataupdateopdeleate<nsobject>-(void) ondataavailable: (Nsarray *) records;@end@interfaceDataupdateop@property (nonatomic, weak)ID<DataUpdateOpDeleate>Delegate;- (void) startupdate;- (void) Cancel;@end//dataupdateop.m@implementationDataupdateop- (void) startupdate {dispatch_async{dispatch_get_global_queue (Dispatch_queue_priority_default,0), ^{            //get results after network request executionNsarray *records =... dispatch_async (Dispatch_get_main_queue (),^{                //attempt to get a strong reference to a delegate object                ID<DataUpdateOpDeleate>Delegate= self.Delegate; if(!Delegate){                    return; }Else{//determine if the original object still exists? //Callback Data[DelegateOndataavailable:records];    }            })        }; }); }//required obsolete callback object for display- (void) Cancel {//cancel a network request in executionSelf.Delegate=Nil;} 

Of course, most of the time, many people would like to use block Backhaul network request data, such as the afnetworking to do a simple two times package.

Here is just a look at how to avoid circular references if you use proxies. It also does an action to verify that the Controller object responds when it is not recycled.

In real-world scenarios, the encapsulation of network requests can be more complex because of the different packages.

2. Block

Block capturing an external variable (typically the controller itself or the controller's properties) causes a circular reference

-(void) somemethod {    *VC =
[Self PRESENTVIEWCONTROLLER:VC animated:yes completion:^{ = vc.data; [Self Dismissviewcontrolleranimated:yes completion:nil];} ];}

This time caused a circular reference, present VC, VC is displayed, sub-view consistent existence, in the completion block, there is a reference to self, that is, the parent controller. In this case, the parent controller sub-controller is in memory, if the sub-controller has done the time-consuming operation, consumes the memory operation, may cause the memory insufficiency.

Workaround: Use ' weak strong dance ' technology

-(void*vc =typeof(self) weakself = self ;//weak reference self facilitates capture by completion         [self PRESENTVIEWCONTROLLER:VC animated:yes             Completion:^{            typeof(self) theself =  weakself; Get a strong reference            through a weak reference if(theself! = nil) {//Only continue                when the controller is not nil = vc.data;                 [Theself Dismissviewcontrolleranimated:yes Completion:nil];            }         

3. Threads and Timers

Incorrect use of Nsthread and Nstimer objects may also cause circular references

Typical steps for running asynchronous operations:

1. If you have not written more advanced code to manage your custom queues, use the Dispatch_async method on the global queue.

2, in the time and place needed to open asynchronous execution with Nsthread.

3, using Nstimer periodic execution of a short code

Error Example:

@implementation Someviewcontroller-(void) startpollingtask {    = [Nstimer Scheduledtimerwithtimeinterval:  target:self         selector: @selector (updatetask:) userinfo:nil Repeats:yes];} -(void) Updatetask: (Nstimer *) Timer {    //... }-(void) delloc {    [self.timer invalidated];} @end

The above code: the object holds the timer, while the timer also holds the object, and the run loop also holds the timer until the timer's invalidate method is called.

This creates an additional reference to the Timer object, even if the reference relationship is not displayed in the code. This will still cause a circular reference.

In fact: The Nstimer object results in an indirect reference held by the runtime, which is a strong reference, and the target's reference counter grows at 2 (not 1). You must call the Inivalidatae method on the timer to remove the reference.

If the controller is created multiple times in the above code, then the controller will not be destroyed. Can cause a serious memory leak.

If Nsthread is used, the same problem can occur.

Workaround:

1, the active call invalidated,

2. Separate the code into multiple classes.

First, don't expect the Delloc method to be called, because once a circular reference is taken with the controller, the Delloc method is never called. [Self.timer invalidated] in Delloc (); never be executed.

Because the run loop tracks the active timer object and the thread object, the code to find that set to nil does not destroy the object. To solve this problem, you can create a custom method to perform cleanup operations in a more explicit manner.

In a view controller, the best time to call this cleanup method is when the user leaves the view controller, which can be either a click-back button or other similar behavior (class until this happens), and we can define a cleanup () method.

@implementation Someviewcontroller-(void) startpollingtask {    = [Nstimer Scheduledtimerwithtimeinterval:  target:self         selector: @selector (updatetask:) userinfo:nil Repeats:yes];} -(void) Updatetask: (Nstimer *) Timer {    //... }-(void) delloc {    [Self.timer invalidate];} @end

You can't erase the timer from the above notation.

3.1 The program to clean up the timer two ways:
1, method One, when the user leaves the current view controller to clean up the timer

//This method is called when the view controller enters or leaves the view controller- (void) Didmovetoparentviewcontroller: (Uiviewcontroller *) Parent {//if the parent controller is left, the if is judged as yes to execute the cleanUp if(Parent = =Nil) {[Self cleanUp]; }}- (void) cleanUp {[Self.timer invalidate];}//2, method two by intercepting the return button to perform cleanup- (ID) init {if(self =[Super Init]) {Self.navigationItem.backBarButtonItem.target=Self ; Self.navigationItem.backBarButtonItem.action=@selector (backbuttonpressdetected:); } returnsel;}- (void) Backbuttonpressdetected: (IDsender {[self cleanUp]; [Self.navigationcontroller Popviewcontrolleranimated:yes];}
3.2 Scenario two spread the holding relationship across multiple classes---task class to perform specific actions, the owner class invokes the task

Advantage 1, cleaner has good responsibility holder
Advantage 2, when needed, the task can be reused by multiple holders
Specific: The controller is only responsible for displaying data, creating a new class Newfeedupdatetask, performing periodic tasks, checking the latest data of the Fill view controller

//NewFeedUpdateTask.h@interfaceNewfeedupdatetask@property (nonatomic, weak)IDTarget//Target is a weak reference, and target will instantiate the task here and hold it@property (nonatomic, assign) SEL selector;@end//NEWFEEDUPDATETASK.M@implementationNewfeedupdatetask//Recommended construction method external best not to use which alloc init.- (void) Initwithtimerinterval: (nstimerinterval) interval target: (ID) Target selector: (SEL) selector{if(SELLF =[Super Init]) {Self.target=Target; Self.selector=selector; Self.timer=[Nstimer scheduledtimerwithtimeinterval:interval target:self selector: @selector (fetchandupdate:) userinfo:nil    Repeats:yes]; }    returnSelf ;}//tasks that are performed periodically- (void) Fetchandupdate: (Nstimer *) Timer {//Retrieving FeedsNewsFeed *feed =[self Getfromserverandcreatemodel]; //modified with weak to ensure that the use of asynchronous blocks does not cause circular references__weaktypeof(self) weakself =Self ; Dispatch_async (Dispatch_get_main_queue (),^{__strongtypeof(self) Strongslef =weakself; if(!Strongslef) {                return; }        if(Strongslef.target = =Nil) {            return; }        /** Strongslef.target and Strongslef.selector are controllers, that is, there may be different controllers to create this object, thus initializing target and selector using local            The variable target and selector have one benefit: avoid the case of competition in the following sequence of execution 1 "call [target responstoselector:selector] in a thread A;            2 "Modify target in thread B or selector 3" in thread A calls [target Performselector:selector withobject:feed]; With this code, even if target or selector has changed now, Performselector will still be called by the correct target and Selecctor **/        IDtarget =Strongslef.target; SEL selector=Strongslef.selector; if([target respondstoselector:selector]) {[Target performselector:selector withobject:feed];; }    });}- (void) ShutDown {[Self.timer invalidate]; Self.timer=Nil;}//VIEWCONTROLLER.M@implementationViewcontroller- (void) Viewdidload {//Initializes an object that performs a scheduled task, and the timer is triggered internallySelf.updatetask = [Newfeedupdatetask initwithtimerinterval: -target:self selector: @selector (updateusingfeed:)];}//is a callback method that newfeedupdatetask the object periodically. - (void) Updateusingfeed: (NewsFeed *) Feed {//updating the UI based on the returned data feed}//invokes the shutdown method of the Task object, which internally destroys the timer- (void) Delloc {[Self.updatetask shutDown];} @end


In terms of usage, Viewcontroller holds the Newfeedupdatetask object, and the controller is not held by objects other than the parent controller.
Therefore, when the user leaves the page, that is, when the return button is clicked, the reference counter is lowered to 0 and the view controller is destroyed. This in turn causes the new task to stop.
This in turn causes the timer to be set to invalid, triggering the destructor of the associated object including (timer and Updatetask).

Attention

When using Nstimer and Nsthread, a clear destruction process should always be achieved through an indirect layer. This indirection should use a weak reference to ensure that all objects are able to execute the destroy action after it is stopped.

iOS circular referencing common scenarios and workarounds

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.