Sagit.Framework Solution: IOS Memory Leakage Caused by Bidirectional Reference (Part 2) - self-willful Use in block

Keywords: iOS Attribute REST

Preface:

After dealing with the problem of internal leakage in the framework, see the previous part: Sagit.Framework Solution: IOS Memory Leakage Caused by Bidirectional Reference (C) - IOS Unknown Bug

It was found that there was a place in the business code where memory was not released for a very simple reason:

self is used in block s, creating bi-directional references, and then starting to think about how to deal with this problem.

The conventional way of thinking is to change the code. Blocks do not use self, or use only weak references to self.

It's just special here in the framework. There's a very useful series, STLastXXX series, which is defined by macros, and the macro points to self.

So useful STLastXXXX macro definition series, I hope it can be used everywhere without restriction.

So began the tortuous road:

Torture One: Redefining macros in code?

The above code, to put it plainly, uses self. Let's look at the macro definition:

Usually, when you encounter a block, you have WeakSelf, like this:

Default definition of STWeakSelf:

//block References in Blocks
#define STWeakSelf __weak typeof(self) selfWeak = self;__weak typeof(selfWeak) this = selfWeak;
#define STWeakObj(o) __weak typeof(o) o##Weak = o;
#define STStrongObj(o) __strong typeof(o) o = o##Weak;

To put it bluntly, macro definition is a deductive text replacement game. What if I redefined the macro sagit in the first line of code in the block?  

For example, it defines:

Then use this:

It seems that I think too much and I can't make it through.

Twist 2: Point macro definition to a function

For example, it defines:

#define sagit [Sagit share].Layout

Then run to the Sagit class and define a UIView* Layout attribute, such as:

Then, when each base class (STController or STView) is initialized, it assigns its own self.baseView to it.

PS: baseView is an extension of UIView and UIViewController, both pointing to View.

For example:

It seems to have some effect, but in this way, we have to think more comprehensively:

1: Each STView in the frame is a baseView.

2: STView can be nested indefinitely.

3: Therefore, when STViewiew is initialized, set it to baseView. After loading, if STView has a parent's STViewiew, return control. (This involves nested control flow, if each child View is loaded asynchronously, it will be a tragedy.)

4: Without inheriting from STView, how do you intercept the beginning and end of View? (Sagit has slowly weakened the functionality of base classes, most of which are native extensions, so many functions can be used without STView.)

Okay, so much writing means that this method is possible, but it must be considered more carefully!!!

And this method just avoids self, self is still not allowed to exist.

So, this method, let's put it on the shelf and see if there's any way to break the self's circular reference first.

Torture Three: Think about how to break the bi-directional quotation of block and self?

block and self, strong quotation from each other, to break it, one side must show weakness.

Since self is allowed to exist in blocks, it means that blocks must be strong references to self, so hot can only think, if the self can only be weak references to blocks, or no references to blocks! uuuuuuuuuu

Let's start with a piece of code for the framework:

Circled code implements the functions circled in the following diagrams:

For Sagit, do you think the code to implement the table is very simple, but the tutorial has not been written yet, and it leaked ahead of time?

Again, the emphasis is UITableView, which extends the Block attribute of AddCell as follows:

@interface UITableView(ST)

#pragma mark Core expansion
typedef void(^AddTableCell)(UITableViewCell *cell,NSIndexPath *indexPath);
typedef BOOL(^DelTableCell)(UITableViewCell *cell,NSIndexPath *indexPath);
typedef void(^AfterTableReloadData)(UITableView *tableView);
//!Used for Table Additional to each line Cell
@property (nonatomic,copy) AddTableCell addCell;
//!Used for Table Remove rows Cell
@property (nonatomic,copy) DelTableCell delCell;
//!Used for Table reloadData Triggered after loading data
@property (nonatomic,copy) AfterTableReloadData afterReload;
//!Obtain Table Data source
@property (nonatomic,strong) NSMutableArray<id> *source;
//!Set up Table Data source
-(UITableView*)source:(NSMutableArray<id> *)dataSource;

Although an addCell attribute is defined, how to hold it depends on how getter and setter are implemented:

@implementation UITableView(ST)

#pragma mark Core expansion

-(AddTableCell)addCell
{
    //Return from third party Holdings
}
-(void)setAddCell:(AddTableCell)addCell
{
    if(addCell!=nil)
    {
         //Third party holds addCell
    }
    else
    {
        
    }
}

If so, save the archive addCell block somewhere unrelated to UITableView?

With a third party holding a block, as long as the third party does not have a direct relationship with self, then there should be no problem.

The reference relationship becomes:

Third Party: Strong Reference= block

block: Strong Reference="self"

Here the release relationship, the third party alive, will not release the block, block alive, will not release self.

So the conclusion is: still not released.

That is to say, a piece of code is written down, although the relationship is broken, but it is still not released.

What if third parties are weak references to block s?

For this experiment, I built a new project, and then on this project, I tried for 24 hours:

The experimental process did not get the desired results, but understand the principle of block, and identify their own logical misunderstandings.

The knowledge gained from the experiment will be shared later. Here we go on:

Because addCell is here, it must always survive, because you don't know when you might want to reload Data from UITableView.

Therefore, the addCell block must be strongly referenced, and the lifecycle must be consistent with UITableView.

So, to break this core, there must be a third-party action event to trigger the breakdown of the relationship, and the story turns into: remove the third party by self.

If a definition is to be triggered by an event to dissolve the relationship, there is no need for a third party to exist.

Because normal A and B refer to each other without understanding, that is, they have no understanding of each other, but as long as a third party exists, one of them will be solved by nil.

Finally, the code for the framework is as follows:

-(AddTableCell)addCell
{
    return [self key:@"addCell"];
}
-(void)setAddCell:(AddTableCell)addCell
{
    if(addCell!=nil)
    {
        addCell=[addCell copy];
        [self key:@"addCell" value:addCell];
    }
    else
    {
        [self.keyValue remove:@"addCell"];
    }
}

Still archiving block s with strong references, here's a note:

If you want to persist a block, copy it first, or you will die miserably.

Well, let them quote each other strongly, the rest is who will be the third party, and how to dissolve this relationship!!!

So, in the case of the navigation bar retreat, intercept and destroy:

@implementation UINavigationController (ST)

#pragma mark NavigationBar Protocol, triggered here
// fuck shouldPopItem When a method exists, it only triggers the navigation bar to retreat, but the interface view does not.
//- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item  // same as push methods
//{
////    //Reset the navigation of the previous Controller (otherwise the Pop will Crash after the second Push)
////    NSInteger count=self.viewControllers.count;
////    if(count>0)//It is found that the viewControllers returned here are the ones left after the current Controller has been removed.
////    {
////        UIViewController *preController=self.viewControllers[count-1];//Get the last controller
////        if([preController needNavBar])
////        {
////            [preController reSetNav:self];
////        }
////    }
////
//    return YES;
//}
//Return to the current page
- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item
{
//    if(navigationBar!=nil && [navigationBar.lastSubView isKindOfClass:[UIButton class]])
//    {
//       // [navigationBar.lastSubView height:0];//Cancel custom overlay UIButton
//    }
    NSInteger count=self.viewControllers.count;
    if(count>0)
    {
        UIViewController *current=self.viewControllers[count-1];
        self.navigationBar.hidden=![current needNavBar];
        if(self.tabBarController!=nil)
        {
            self.tabBarController.tabBar.hidden=![current needTabBar];
        }
        //Check whether the last controller is released
        UIViewController *nextController=current.nextController;
        if(nextController!=nil)
        {
            [nextController dispose];
            nextController=nil;
        }
    }
}
-(void)dealloc
{
    NSLog(@"UINavigationController relase -> %@", [self class]);
}

The Sagit framework extends the dispose method for each view and controller, which removes the key-value pairs, which is equivalent to setting the block to nil and dissolving the relationship.

In addition to navigating backwards, you need to intercept one more event, that is, the event jump of presentViewController also needs to be detected and destroyed.

After completing these two steps, you can easily write self in the block, love quotation will be quoted, anyway, the end of the story, there is a third party to end.

And strong citation has one advantage:

1: You can't use WeakSelf any more.

2: Because it's a strong reference, you don't have to deal with it: there's also a StrongSelf in it to avoid the flip-flop problem that self may cause when it's removed from multithreading.

Finally: Tucao another IOS pit is another mysterious dealloc method:

In the last article, it was mentioned that if dealloc was extended to UIView, the murder was caused by:

Navigation Bar: The second step backwards and then flies back.

In this article, I found that if I extended the dealloc method to UIViewController, it would cause murder:

UIAlert View: Flash when alertView Style is set to text box.

Give everyone a picture. Open dealloc first.

Then the effect of running:

Well, thanks to my previous experience, I quickly built a new project, then used code exclusion method, and finally came to dealloc.

When you read this article, you can fool your colleagues again.

Conclusion:

The Sagit framework is also much more efficient after the overall memory release problem has been tossed around, which may be an illusion.

IT company's business is also continuing. Welcome everyone's continuous attention. Thank you!

Posted by Mad Mick on Sun, 16 Dec 2018 03:33:03 -0800