IOS 看懂此文,你的block再也不需要WeakSelf弱引用了!管理

测试代码

前言:

日前都在折腾 Sagit 架框的内存释放的题材,所以对这一块有些心得。

对于新手,学到的著作都在教你用:typeof(self) __weak weakSelf = self。

对此老手,可能早习惯了各地了WeakSelf了。

这一次,就来上学,咋样不用WeakSelf。

  介绍如何接纳Python模块unittest 中的工具来测试代码。

1:从引用计数器起先:

此地先规划一个TableBlock类:

@interface BlockTable : NSObject

typedef void (^AddCellBlock)();
@property (nonatomic,copy)AddCellBlock addCell;@end

先这么简单,一个BlockTable只有一个block属性,然后输出一段释放的日记。

-(void)dealloc
{
    NSLog(@"Table relase");//relase为错误字,为了和下图保持一致的错别字,这里就不改了。
}

随之,随意找一个地方写写代码:来new了一个BlockTable,并打印一下音信:

管理 1

这时候它的引用数是1,并且出了Table relase 。

紧接着给addCell属性赋一个值,并运行:

管理 2

一个空的风波,里面并不曾引用到table,所以引用数仍然1。

 

2:先河循环引用

在block引用table,让它发出循环引用,并运行:

管理 3

1.  测试函数

俺们看到:引用数成为了3,没有出口对象释放音信了,为何不是2呢?大大的问号!!

一个性能赋值,为何增强七个引用计数?

  在Python中,测试函数是用以自动化测试,使用python模块中的unittest中的工具来开展测试。

3:猜解跳跃的计数器

接下去,把性能设置为nil,运行看看:

管理 4

设置为nil,还有2?

也正常释放了?

为了验证自己对这么些看起来就很通晓的臆度:重写addCell的setter方法,不开展任何保存:

@implementation BlockTable
-(void)setAddCell:(AddCellBlock)addCell
{

}

同时去掉置为nil的代码:再运行看看:

管理 5

计数器仍为2,而且也释放了。

由此考虑,出来了以下的下结论:

1:块的定义本身,就会造成1次引用,不过这次引用,在块离开所在的函数时,释放时,抵消掉引用数。

2:存档块的时候,会造成1次引用,而这个引用,是内存无法释放的原因。

  例如,创设一个函数max_function()接受几个数字,求其最大值,再创立一个函数number_function()提醒用户输入几个数

4:按照上述解释,得到一个癫狂的下结论:

只要block的代码只执行1次的,都可以任性的self或其它强引用。

事实上,我们写的代码,很多block的确只执行一次,不管是传的时候就执行,还是传完之后过段时间回调再执行。

认定只要执行1次的,就不需要WeakSelf,除非第三方框架的设计者造孽留坑,忘了在存档block执行后补上block=nil这一刀。

  代码1:

5:消灭赋值的引用计数:

持续发挥想象力,既然存的时候,会大增三次引用,辣么,让它不增添引用不就好了:

@implementation BlockTable
-(void)setAddCell:(AddCellBlock)addCell
{
    __weak AddCellBlock addCellWeak=addCell;
    _addCell=addCellWeak;
}

我们先给这多少个block定义一个弱引用,然后再赋值给_addCell,运行看看:

管理 6

哇草,成功了!计数器为2,正常释放了,看来自己的想象力,仍然得以的!!

接下去,我们补充完善一下代码,扩充一个reloadData方法,方法里调用事件。

完整的代码如下:

@interface BlockTable : NSObject

typedef void (^AddCellBlock)();
@property (nonatomic,copy)AddCellBlock addCell;

-(void)reloadData;
@end

@implementation BlockTable
-(void)setAddCell:(AddCellBlock)addCell
{
    __weak AddCellBlock addCellWeak=addCell;
    _addCell=addCellWeak;
}
-(void)reloadData
{
    if(self.addCell)
    {
        self.addCell();
     self.addCell();//没事来两次,模拟table多次循环清加cell
    }
}
-(void)dealloc
{
    NSLog(@"Table relase");
}
@end

修改一下增添日志输出,现在再实践一下看看:

管理 7

一切看起来都十分完善,不需要引入第三,需要反复利用的,只是在存的时候,存个弱引用,就搞定了。

1 def get_max_number(x,y):
2     """求两个数中的最大值"""
3     if x > y :
4         max_number = x
5     else:
6         max_number = y
7     return max_number

6:弱引用降低计数的毛病:

块的定义,和使用的场景,必须在同一个函数。

说白了就是块离开函数体就会消亡,所以要用要赶紧,且用且珍惜。

常规一个Table写完代码reloadData后,数据出来了。

 

但如若前边还跟有一个刷新重新加载的功效?

而这一个重新调用reloadData的位置,可能跟block不在同一个函数,比如代码像那样:

-(void)start
{
    BlockTable *table=[BlockTable new];
    self.table=table;//搞到全局变量中
    table.addCell = ^{
        __weak typeof(table) this=table;
        NSLog(@"addCell call");
    };
    [table reloadData];
    NSLog(@"table retain = %ld",CFGetRetainCount((__bridge CFTypeRef)(table)));
}
-(void)reflesh
{
    [self.table reloadData];
}

给外界的类定义了一个table属性,然后调用完start后再调用reflesh,运行,会如何呢?

管理 8

并发了IOS上最骇人听闻的EXC_BAD_ACCESS 野指针错误。

对此block离开函数后,消亡了容易了然,只是这里:

这怎么是平素抛相当?哥不是作了判断了么?

让咱们换种代码写法:

管理 9

此外从上图看:_addCell如故有值的。

为什么if(self.addCell)判断就直接死,if(_addCell)却没死呢?

正常self.addCell正常不是也return _addCell么?

这个问题,留给让你们思考了。

 

最吓人的,依然下边的这段话:

管理 10

  代码2:

7:避开野指针,仍是弱引用,效率不变

OK,继续揭橥想象力,看看怎么避开野指针,同时仍然促成上述的功力:

1:把block属性从copy改成weak

@property (nonatomic,weak)AddCellBlock addCell;

2:赋值代码手工copy:

-(void)setAddCell:(AddCellBlock)addCell
{
    addCell=[addCell copy];
    _addCell=addCell;
    //_addCell=[addCell copy];这样简写是不行的,不明白为虾米呢

    //    原来是这样写的:
    //   __weak AddCellBlock addCellWeak=addCell;
    //    _addCell=addCellWeak ;
}

双重运行,神奇的作业时有产生了:

管理 11

流程仍然很顺,不会有野批针相当,Table也释放了。

唯一的遗憾,就是跳出函数后,block不可以再复用了:

管理 12

 1 from max_function import get_max_number
 2 
 3 print("Enter 'q' at any time to quit.")
 4 while True:
 5     x = input("Please enter x:")
 6     if x == 'q':
 7         break
 8     y = input("Please enter y:")
 9     if y == 'q':
10         break
11     max_number = get_max_number(x,y)
12     print("The max is :",max_number,".")

8:block的copy方法:

对于默认传进来的block(有两种形态:全局、栈、堆)

全局 copy 还是全局

堆 copy 还是堆

栈 copy 变成堆

粗略,copy只对品种是栈是才使得。

那是因为:栈的block,在履行完后出括号后,间接是绝迹对象。

一经有弱引用过去,会招致野指针。

而任何二种档次,销毁时,会将指针指向一个空指针。

addCell=[addCell copy] 和默认copy的属性 _addCell=addCell 也是执行了copy操作。

施行后,addCell的品类就改为堆形态,这样销毁的时候,是空指针。

 

9:空指针和野指针的分别:

空指针:指向一个:人为创造的一个指针,它的名字叫空,有座空房子,里面什么也没有。

野指针:就是指向的都不知哪去了,连空房子都木有。

  运行结果:

10:扩张想象力,怎么着消灭引用数,仍可以长期保留? 

弱引用的坏处,就是block出了函数,就不再可用这多少个block了。

这仍可以如何是好吧?没事,我还有想象力!!!!!

假若block能够重建呢?

比如:

1:将block转成字符串存档,适当时机还原回来重新赋值

2:将block序列化保存,适当时机还原回来?

3:runtime读取block的__FuncPtr,存档再动态创建?

管理 13

伪代码大体如下:

-(void)setAddCell:(AddCellBlock)addCell
{
    addCell=[addCell copy];
    _addCell=addCell;

    //_addCell=[addCell copy];这样简写是不行的,不明白为虾米呢

    //    原来是这样写的:
    //   __weak AddCellBlock addCellWeak=addCell;
    //    _addCell=addCellWeak ;

    //存档block的字符串
}
-(void)reloadData
{
    if(!_addCell)
    {
        //从存档的block字符串还原block
        //_addCell=还原block
    }
    if(_addCell)
    {
        _addCell();
        _addCell();
    }
}

这就是说就剩下多少个问题?

1:怎么把block存档?

2:怎么将存档数据还原成block。

对搞C#的来说,这个都司空眼惯,oc这块还不熟,有路过的情侣可顺路给支支招!!

1 Enter 'q' at any time to quit.
2 Please enter x:12
3 Please enter y:23
4 The max is : 23 .
5 Please enter x:66
6 Please enter y:99
7 The max is : 99 .
8 Please enter x:q

11:假诺第10的形式解决不了,就不得不,只可以,引入时机第三者了

可是这多少个引入第三者,只是一个火候切入点,在那些机遇触发的时候,将内部的一方的引用设置为nil。

像Sagit框架的布局方面的时机,就选在导航回退等事件中拍卖。

唯独这里需要一个小技巧:

在存档block时,不必然要留存当前目标,也足以用一个统一的全局block管理起来。

这么在事情处理时,依照业务意况,从大局block里来移除某些block即可。

切实取决于业务,所以这些就不开展了。

 

总结:

信任,一路看下,看懂了,后续的状况,基本上已经用不上WeakSelf这东西了,因为像一个block,其生命周期必须和所有者保持一致的,仍旧挺少的。

而这种少的景观,虽然第10步解决了,基本就全都解决了,解决不了,还有11。

深信不疑读完此文,假若能一心知晓,你就再也看不到block前WeakSelf这种,WeakSelf也尚无存在必要了。

末尾,欢迎咱们关注IT连创业,虽可是今本身都在折腾IOS,哈哈。

然而IOS基础依然要打劳,后续产品改进起来才有质的迅猛。

1.1  单元测试和测试用例

  Python标准库中的模块 unittest
提供了代码测试工具。单元测试用于核实函数的某个方面并未问题;测试用例是一组单元测试,这一个单元测试一起核实函数在各个场地下的所作所为都符合要求。卓绝的测试用例考虑到了函数可能收到的各个输入,包含针对所有这多少个意况的测试。全覆盖式测试用例包含一整套单元测试,涵盖了各样可能的函数使用方法。对于大型项目,要实现全覆盖可能很难。平日,最初只要针对代码的首要表现编写测试即可,等系列被广泛使用时再考虑全覆盖。

 

1.2 可经过的测试

  创造测试用例的语法需要一段时间才能习惯,但测试用例创立后,再添加针对函数的单元测试就很粗略了。要为函数编写测试用例,可起头入模块
unittest 以及要测试的函数,再创制一个无冕 unittest.TestCase
的类,并编制一多元措施对函数行为的不同方面展开测试。

  例如,创制一个分包一个情势的测试用例,它检查函数get_max_number()在给定x、y值时,是否能正确求出最大值。

  代码:

 1 import  unittest
 2 
 3 from max_function import get_max_number
 4 
 5 class TestMaxCase(unittest.TestCase):
 6     """测试max_function.py"""
 7 
 8     def test_x_y(self):
 9         """能够处理x,y这样的数字"""
10         max_number = get_max_number(20,18)
11         self.assertEqual(max_number,20)
12 
13 unittest.main

  说明:

  第1行,导入一个模块unittest。

  第2行,从模块max_function中导入函数get_max_number()。

  第5行,创立了一个名为Test马克斯Case的类,用于包含一文山会海针对
get_max_number() 的单元测试。

  第8行,定义一个格局test_x_y()。

  第10行,调用函数get_max_number()求出最大值,并赋值给变量max_number

  第11行,调用 unittest 的艺术 assertEqual() ,并向它传递
max_number 和
20代码行self.assertEqual(max_number,20)的情趣是说:“将
max_number的值同字数字20展开比较,如若它们相当于,就顺风,假若它们不对等,跟自己说一声!”

  第13行,调用unittest的main方法。

 

  运行结果:

1 Ran 1 test in 0.001s
2 
3 OK

   从第1行可知,测试了一个函数,花费了0.001s;第3行,表达测试OK,通过测试。

 

1.3 不可能通过的测试

  例如,大家创制的一个求多个数的最大值的函数,不过只给其提供五个值。

  代码1:

 1 def get_max_number(x,y,z):
 2     """求三个数中的最大值"""
 3     if x > y  and  x > z:
 4         max_number = x
 5     elif y > z:
 6         max_number = y
 7     else:
 8         max_number = z
 9 
10     return max_number

 

  代码2:

 1 import  unittest
 2 
 3 from max_function1 import get_max_number
 4 
 5 class TestMaxCase(unittest.TestCase):
 6     """测试max_function.py"""
 7 
 8     def test_x_y(self):
 9         """能够处理x,y这样的数字"""
10         max_number = get_max_number(20,18)
11         self.assertEqual(max_number,20)
12 
13 unittest.main

 

  代码2的运作结果如下:

 1  Ran 1 test in 0.000s
 2 
 3 FAILED (errors=1)
 4 Launching unittests with arguments python -m unittest test_max_function1.TestMaxCase in F:\PyProject\s14\exercise\chapter_eleven
 5 
 6 Error
7 Traceback (most recent call last):
8   File "D:\Python\Python36\lib\unittest\case.py", line 59, in testPartExecutor
9     yield
10   File "D:\Python\Python36\lib\unittest\case.py", line 601, in run
11     testMethod()
12   File "F:\PyProject\s14\exercise\chapter_eleven\test_max_function1.py", line 12, in test_x_y
13     max_number = get_max_number(20,18)
14 TypeError: get_max_number() missing 1 required positional argument: 'z'

  从以上运行结果可知,由于取最大值的函数需要多少个职务实参,然而只给其传了五个,少了一个,由于报错。

 

1.4 测试未经过时咋做

  当测试不通过时,我们决不测试区修改用于测试的函数,而应当去修改被测试的函数,使其经过测试。

  例如,对1.3中测试未经过的函数举办系数。

  代码1:

 1 def get_max_number(x=0,y=0,z=0):
 2     """求三个数中的最大值"""
 3     if x > y  and  x > z:
 4         max_number = x
 5     elif y > z:
 6         max_number = y
 7     else:
 8         max_number = z
 9 
10     return max_number

  说明:

  第1行,为了在用户不提供任何参数而调用求最大值的函数时不报错,我们给每个形参都赋予一个默认值0。

  

  代码2:

 1 import  unittest
 2 
 3 from max_function2 import get_max_number
 4 
 5 class TestMaxCase(unittest.TestCase):
 6     """测试max_function.py"""
 7 
 8     def test_x_y(self):
 9         """能够处理x,y这样的数字"""
10         max_number = get_max_number(20,18)
11         self.assertEqual(max_number,20)
12 
13 unittest.main

 

  运行结果:

1 Ran 1 test in 0.000s
2 
3 OK

  从上述运行结果可以,测试已透过。

  

1.5 添加新测试

  例如,我们在以上求最大的模块中,扩充一个求最小值的函数。

  代码1:

 1 def get_max_number(x=0,y=0,z=0):
 2     """求三个数中的最大值"""
 3     if x > y  and  x > z:
 4         max_number = x
 5     elif y > z:
 6         max_number = y
 7     else:
 8         max_number = z
 9 
10     return max_number
11 
12 def get_min_number(x=0,y=0,z=0):
13     """求三个数中的最小值"""
14     if x < y and x < z :
15         min_number = x
16     elif y < z :
17         min_number = y
18     else:
19         min_number = z
20 
21     return min_number

 

  代码2:

 1 import  unittest
 2 
 3 from max_min_function import get_max_number
 4 from max_min_function import get_min_number
 5 
 6 class TestMaxCase(unittest.TestCase):
 7     """测试max_function.py"""
 8 
 9     def test_max_number(self):
10         """能够处理x,y这样的数字"""
11         max_number = get_max_number(20,18)
12         self.assertEqual(max_number,20)
13 
14     def test_min_number(self):
15         """能否处理x、y、z这样的数字"""
16         min_number = get_min_number(1,13,18)
17         self.assertEqual(min_number,1)
18 
19 
20 unittest.main

 

  运行结果:

1 Ran 2 test in 0.001s
2 
3 OK

  从上述结果可知,两测试都已通过,耗时0.001s。

 

2. 测试类

  测试类就是针对类来编排测试,验证类是否能想自己没希望的那么。倘使针对类的测试通过了,我没就能确信对类所做的改进没有意外地破坏其本来面目标行为。

 

2.1 各类断言方法

  Python在 unittest.TestCase
类中提供了许多预言方法。断言方法检查自己没觉着应该满意的基准是否确实满足。假若该原则实在满意,我们对程序行为的比方就赢得了认可,我们就足以确信其中没有错误。假使大家以为应当满意的原则实在并不满足,Python将掀起那么些。

  常用的预言方法有:

  (1)assertEqual(a, b),核实 a == b。

*  (2)*assertNotEqual(a, b),核实 a != b。

  (3)assertTrue(x),核实 x 为 True。

  (4)assertFalse(x),核实 x 为 False。

  (5)assertIn( item , list ),核实 item 在 list 中。

  (6)assertNotIn( item , list ),核实 item 不在 list 中。

 

2.2  测试类

  类的测试与函数的测试相似,只但是类测试所做的大多数行事都是测试类中艺术的一言一行,但存在部分不同之处。

  例如,我们创制一个管制匿名调查问卷的类AnonymousSurvey,并将其存于模块survey中,然后测试它。

  (1)创建类AnonymousSurvey

  代码1:

 1 class AnonymousSurvey():
 2     """收集匿名调查问卷的答案"""
 3 
 4     def __init__(self,question):
 5         """存储一个问题,并为存储答案做准备"""
 6         self.question = question
 7         self.responses = []
 8 
 9     def show_question(self):
10         """显示调查问卷"""
11         print(self.question)
12 
13     def store_response(self,new_response):
14         """存储单份调查问卷"""
15         self.responses.append(new_response)
16 
17     def show_results(self):
18         """显示收集到的所有答案"""
19         print("Survey results:")
20         for reponse in  self.responses:
21             print(('-' + reponse))

  说明:

  第4~7行,存储了一个点名的检察问题,并创办了一个空列表response,用于存储答案。

  第9~11行,创制一个函数show_question(),用于显示调查问卷。

  第13~15行,创制一个函数store_response(),用于存储单份调查问卷,即便用append()方法往答案列表中添加新答案。

  第17~21行,创造一个函数show_response(),用于显示收集到的所有答案,其中使用for循环遍历答案列表response。

 

  代码2:

 1 from survey import AnonymousSurvey
 2 
 3 # 定义一个问题,并创建一个表示调查的AnonymousSurvey对象
 4 question = "What language did you first learn to speak?"
 5 my_survey = AnonymousSurvey(question)
 6 
 7 # 显示问题并存储答案
 8 my_survey.show_question()
 9 print("Enter 'q' at time to quit.\n")
10 while True:
11     response = input("language:")
12     if response == 'q':
13         break
14     my_survey.store_response(response)
15 
16 # 显示调查结果
17 print("\nThank you to everyone who participated in the survey!")
18 my_survey.show_results()

  说明:

  第1行,从模块survey中导入一个类AnonymousSurvey。

  第4~5行,定义一个题目,并使用该问题创立一个表示调查的AnonymousSurvey对象。

  第8~14行,调用 show_question()来展现问题,并提示用户输入答案。收到每个答案的还要将其储存起来。用户输入所有答案(输入
q 要求退出)

后。

  第17~18行,彰显调查结果,即调用 show_results() 来打印调查结果。

 

  运行结果:

 1 What language did you first learn to speak?
 2 Enter 'q' at time to quit.
 3 
 4 language:English
 5 language:Spanish
 6 language:English
 7 language:Chinese
 8 language:Mandarin
 9 language:q
10 
11 Thank you to everyone who participated in the survey!
12 Survey results:
13 -English
14 -Spanish
15 -English
16 -Chinese
17 -Mandarin

 

  (2)测试 AnonymousSurvey 类

  • 测试单个答案

  代码1:

 1 import unittest
 2 from survey import AnonymousSurvey
 3 
 4 class TestAnonmyousSurvey(unittest.TestCase):
 5     """针对AnonymousSurvey类的测试"""
 6 
 7     def test_store_single_response(self):
 8         """测试单个答案会被妥善地存储"""
 9         question = "What language did you first learn to speak?"
10         my_survey = AnonymousSurvey(question)
11         my_survey.store_response('English')
12         self.assertIn('English', my_survey.responses)
13 
14     unittest.main

  说明:

  第1行,导入测试模块unittest。

  第2行,从模块survey中导入类AnonymousSurvey。

  第4行,继承模块unittest中的类TestCase创立一个测试类TestAnonmyousSurvey。

  第7~12行,创造一个格局test_store_single_response来测试单个答案会被妥善地囤积。

  第14行,调用模块unittest中的main方法来执行测试。

 

  运行结果:

1 Ran 1 test in 0.000s
2 
3 OK

 

  

  • 测试五个答案

  代码2:

 1 import unittest
 2 from survey import AnonymousSurvey
 3 
 4 class TestAnonmyousSurvey(unittest.TestCase):
 5     """针对AnonymousSurvey类的测试"""
 6 
 7     def test_store_single_response(self):
 8         """测试单个答案会被妥善地存储"""
 9         question = "What language did you first learn to speak?"
10         my_survey = AnonymousSurvey(question)
11         my_survey.store_response('English')
12         self.assertIn('English', my_survey.responses)
13 
14     def test_store_three_responses(self):
15         """测试三个答案会被妥善地存储"""
16         question = "What language did you first learn to speak?"
17         my_survey = AnonymousSurvey(question)
18         responses = ['English', 'Spanish', 'Mandarin']
19         for response in responses:
20             my_survey.store_response(response)
21 
22         for response in responses:
23             self.assertIn(response, my_survey.responses)
24 
25     unittest.main

 

  运行结果:

1 Ran 2 tests in 0.000s2 
3 OK

 

2.3 方法 setUp()的使用

  在前头的test_survey.py中,大家在各类测试方法中都创设了一个
AnonymousSurvey 实例,并在每个方法中都开创了答案。 unittest.TestCase
类包含方法 setUp(),让我们只需创设那么些目的四遍,并在每个测试方法中使用它们。若是你在
TestCase 类中带有了措施 setUp(),Python将先运行它,再运行各类以test_一马抢先的办法。那样,在大家编辑的各类测试方法中都可采用在艺术
setUp()中成立的靶子了。

   例如,使用 setUp() 来成立一个调研对象和一组答案,供方法
test_store_single_response() 和test_store_three_responses() 使用。

  代码:

 1 import unittest
 2 from survey import AnonymousSurvey
 3 
 4 class TestAnonymousSurvey(unittest.TestCase):
 5     """针对AnonymousSurvey类的测试"""
 6     def setUp(self):
 7         """
 8         创建一个调查对象和一组答案,供使用的测试方法使用
 9         """
10         question = "What language did you first learn to speak?"
11         self.my_survey = AnonymousSurvey(question)
12         self.responses = ['English', 'Spanish', 'Mandarin']
13 
14     def test_store_single_response(self):
15         """测试单个答案会被妥善地存储"""
16         self.my_survey.store_response(self.responses[0])
17         self.assertIn(self.responses[0], self.my_survey.responses)
18 
19     def test_store_three_responses(self):
20         """测试三个答案会被妥善地存储"""
21         for response in self.responses:
22             self.my_survey.store_response(response)
23         for response in self.responses:
24             self.assertIn(response, self.my_survey.responses)
25 
26     unittest.main

  说明:

  第11行,创立一个检察对象。

  第12行,创造一个答案列表。

 

  运行结果:

1 Ran 1 test in 0.000s
2 
3 OK

  

  测试自己编辑的类时,方法 setUp() 让测试方法编写起来更便于:可在
setUp()方法中开创一名目繁多实例并安装它们的属性,再在测试方法中一向行使那些实例。相比较于在各样测试方法中都创立实例并设置其特性,这要容易得多。  

  运行测试用例时,每完成一个单元测试,Python都打印一个字符:测试通过时打印一个句点;测试引发错误时打印一个
E ;测试导致断言战败时打印一个 F
。这就是您运行测试用例时,在输出的率先行中看看的句点和字符数量各不相同的原由。假若测试用例包含众多单元测试,需要周转很长日子,就可通过寓目这多少个结果来获知有微微个测试通过了。

 

Post Author: admin

发表评论

电子邮件地址不会被公开。 必填项已用*标注