iOS advanced (1) block and property

iOS advanced (1) block and property

 

This reading note mainly introduces the in-depth understanding of C language memory allocation, block difficulties, and property. I have made a systematic summary of these three pieces, hoping to help you.

C language memory allocation

Objective-C can be known from the name as a super-C language, so understanding the memory model of the C language is very helpful for understanding the memory management of Objective-C. The C language memory model diagram is as follows:

416556-5a074ec54c658b80.png

1-1 C memory allocation.png

It can be seen from the figure that the memory is divided into 5 areas, and the contents stored in each area are as follows:

  • The stack area (stack): stores the parameter values ​​of the function, the value of the local variables, etc., which are automatically allocated and released by the compiler, usually released after the execution of the function , and its operation is similar to the stack in the data structure. The stack memory allocation operation is built into the processor's instruction set, which is very efficient, but the allocated memory capacity is limited. For example, the size of the stack area in iOS is 2M.

  • Heap area (heap): It is the memory block allocated by new, malloc, and realloc. The compiler does not care about their release, and is released by our application. If the application is not released, the operating system will automatically recycle . The allocation method is similar to a linked list.

  • Static area: Global variables and static variables are stored in one area, initialized global variables and static variables are in one area, and uninitialized global variables and uninitialized static variables are in another adjacent area. After the program ends, it is released by the system.

  • Constant area: constants are stored here and cannot be modified.

  • Code area: store the binary code of the function body.

When does the stack area release memory? Let's illustrate with the following example:

1

2

3

4

5

- (void)print {

    int i = 10;

    int j = 20;

    NSLog(@"i+j = %d", (i+j));

}

In the above code, when the program executes to }, the scope of the variables i and j has ended, and the compiler will automatically release the memory occupied by i and j, so if you understand the scope, you can understand the stack area. memory allocation.

The main differences between the stack area and the heap area are as follows:

  • For the stack, memory management is automatically allocated and released by the compiler; for the heap, the release work is controlled by the programmer.

  • The stack size is much smaller than the heap.

  • The stack is a data structure provided by the machine system, and the computer will provide support for the stack at the bottom layer, so the allocation efficiency is higher than that of the heap.

  • The variables stored in the stack are invalid when they are out of scope, and the life cycle of the variables can be extended because the heap is released under the control of the programmer.

Reference article:

Memory Management of C Programs

Detailed explanation of C/C++ memory management

block

Why use copy when declaring the block property?

Before explaining why copy is used, consider whether the block is stored in the stack area or the heap area? In fact, there are 3 types of blocks:

  • Global Block (_NSConcreteGlobalBlock)

  • Stack Block (_NSConcreteStackBlock)

  • Heap Block (_NSConcreteMallocBlock)

The global block is stored in the static area (also called the global area), which is equivalent to the singleton in Objective-C; the stack block is stored in the stack area, and is immediately destroyed when it exceeds the scope. The heap block is stored in the heap area and is a reference-counted object that needs to manage its memory by itself.

How to determine the storage location of a block?

  • block does not access external variables (including variables in the stack and heap)

The block is neither in the stack nor in the heap, it is a global block at this time, both under ARC and MRC.

  • block access external variables

In the MRC environment: the blocks that access external variables are stored in the stack area by default.

In the ARC environment: the blocks that access external variables are stored in the heap by default. In fact, they are first placed in the stack area. In the case of ARC, they are automatically copied to the heap area and automatically released.

The role of using the copy modifier is to copy the block from the stack area to the heap area. Why do you want to do this? Let's take a look at the answer given by Apple's official documentation:

416556-1aaba024873e003e.png

1-2 block copy.png

It can be seen from the official documents that the main purpose of copying to the heap area is to save the state of the block and prolong its life cycle. Because if the block is on the stack, the variable scope to which it belongs ends, the block is released, and the __block variable in the block is also released at the same time. To solve the problem of stack blocks being freed after their variable scope ends, we need to copy the block to the heap.

Different types of blocks use the copy method to have different effects, as shown below:

QQ screenshot 20170503095516.png

block type storage area copy effect

_NSConcreteStackBlock stack is copied from stack to heap

_NSConcreteGlobalBlock static area (global area) does nothing

_NSConcreteMallocBlock heap reference count incremented

Why can you modify the variables outside the block after adding __block?

Let's look at example 1 first:

1

2

3

4

5

6

7

8

9

10

11

- (void)testMethod {

    int anInteger = 42;

 

    void (^testBlock)(void) = ^{

        NSLog(@"Integer is : %i", anInteger);

    };

 

    anInteger = 50;

 

    testBlock();

}

The output after running is as follows:

1

Integer is : 42

Why not 50? This question will be explained later. We are looking at the case of adding __block, example 2 is as follows:

1

2

3

4

5

6

7

8

9

10

11

- (void)testMethod {

    __block int anInteger = 42;

 

    void (^testBlock)(void) = ^{

        NSLog(@"Integer is : %i", anInteger);

    };

 

    anInteger = 50;

 

    testBlock();

}

The output after running is as follows:

1

Integer is : 50

The results of the two runs are different, what happened in the middle? We will analyze it in detail next.

In example 1, the block will copy the anInteger variable as its own private const variable, that is to say, the block will capture the variable (or pointer) on the stack and copy it as its own private const variable . In example 1, when the operation of anInteger = 50 is performed, the block has copied it as its own private variable, so the modification here will not have any effect on anInteger in the block.

In example 2, anInteger is a local variable stored on the stack. The effect of adding the __block modifier to anInteger is to put the memory address of the variable in the stack into the heap as long as it is observed that the variable is held by the block, regardless of the memory address of the external or internal anInterger of the block. All are the same, and the value of the anInterger variable can be modified whether outside or inside the block , so after anInteger = 50, the value output in the block is 50. It can be briefly described with a diagram:

416556-1216c73271563f9b.png

1-3 __block.png

Problems with circular references in blocks? Does using the system's block api also consider the issue of circular references? Difference between weak and strong?

When using blocks, we should pay special attention to the issue of circular references. Let's first look at an example of circular references:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

@interface XYZBlockKeeper : NSObject

 

@property (nonatomic, copy) void (^block)(void);

 

@end

 

@implementation XYZBlockKeeper

 

- (void)configureBlock {

    self.block = ^{

        [self doSomething];    

    };

}

 

@end

In the above code we declared a block property, so self has a strong reference to the block. And a strong reference to self is made inside the block, which forms a closed loop, which is what we often call a strong reference cycle. The reference relationship is as follows:

416556-32b0b108698f2ba9.png

1-4 strong retain cycle.png

在这种情况下,由于其相互引用,内存不能够进行释放,就造成了内存泄漏的问题。怎么解决循环引用的问题呢?我们经常通过声明一个weakSelf来解决循环引用的问题,更改后的代码如下:

1

2

3

4

5

6

- (void)configureBlock {

    __weak typeof(self) weakSelf = self;

    self.block = ^{

        [weakSelf doSomething];

    };

}

加入weakSelf之后,block对self就由强引用关系变成了弱引用关系,这样在属性所指的对象遭到摧毁时,属性值也会被清空,就打破了block捕获的作用域带来的循环引用。这跟设置self.block = nil是同样的道理。

在使用系统提供的block api需要考虑循环引用的问题吗?比如:

1

2

3

[UIView animateWithDuration:0.5 animations:^{

    [self doSomething];

}];

在这种情况下是不需要考虑循环引用的,因为这里只有block对self进行了一次强引用,属于单向的强引用,没有形成循环引用。

weak与strong有什么区别呢?先看一段代码:

1

2

3

4

5

6

7

8

- (void)configureBlock {

    __weak typeof(self) weakSelf = self;

    self.block = ^{

        [weakSelf doSomething]; // weakSelf != nil

        // preemption(抢占) weakSelf turned nil

        [weakSelf doAnotherThing];

    };

}

这段代码看起来很正常呀,但是在并发执行的时候,block的执行是可以抢占的,而且对weakSelf指针的调用时序不同可以导致不同的结果,比如在一个特定的时序下weakSelf可能会变成nil,这个时候在执行doAnotherThing就会造成程序的崩溃。为了避免出现这样的问题,采用__strong的方式来进行避免,更改后的代码如下:

1

2

3

4

5

6

7

8

9

- (void)configureBlock {

    __weak typeof(self) weakSelf = self;

    self.block = ^{

        __strong typeof(weakSelf) strongSelf = weakSelf;

        [strongSelf doSomething]; // strongSelf != nil

        // 在抢占的时候,strongSelf还是非nil的。

        [strongSelf doAnotherThing];

    };

}

从代码中可以看出加入__strong所起的作用就是在抢占的时候strongSelf还是非nil的,避免出现nil的情况

总结:

  • 在block不是作为一个property的时候,可以在block里面直接使用self,比如UIView的animation动画block。

  • 当block被声明为一个property的时候,需要在block里面使用weakSelf,来解决循环引用的问题。

  • 当和并发执行相关的时候,当涉及异步的服务的时候,block可以在之后被执行,并且不会发生关于self是否存在的问题。

参考文章:

iOS Block详解

property

@synthesize和@dynamic分别有什么作用?

在说两者分别有什么作用前,我们先看下@property的本质是什么:

1

@property = ivar + getter + setter;

从上面可以看出@property的本质就是ivar(实例变量)加存取方法(getter + setter)。在我们属性定义完成后,编译器会自动生成该属性的getter和setter方法,这个过程就叫做自动合成。除了生成getter与setter方法,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此做实例变量的名字。

@synthesize的作用就是如果你没有手动实现getter与setter方法,那么编译器就会自动为你加上这两个方法

@dynamic的作用就是告诉编译器,getter与setter方法由用户自己实现,不自动生成。当然对于readonly的属性只需要提供getter即可。

如果都没有写@synthesize和@dynamic,那么默认的就是@synthesize var = _var;

为了加深对@synthesize和@dynamic的理解,我们来看几个具体的例子,例子1代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

@interface ViewController ()

 

@property (nonatomic, copy) NSString *name;

 

@end

 

@implementation ViewController

 

@dynamic name;

 

- (void)viewDidLoad {

    [super viewDidLoad];

 

    self.name = @"国士梅花";

    NSLog(@"name is : %@", self.name);

}

 

@end

我们在进行编译的时候没有问题,但是运行的时候就会发生崩溃,崩溃的原因如下:

1

'NSInvalidArgumentException', reason: '-[ViewController setName:]: unrecognized selector sent to instance 0x7fd28dd06000'

崩溃的原因是不识别setName方法,这也验证了如果加入了@dynamic的话,编译系统就不会自己生成getter和setter方法了,需要我们自己来实现。

我们在来看下@synthesize合成实例变量的规则是什么?例子2代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

@interface ViewController ()

 

@property (nonatomic, copy) NSString *name;

 

@end

 

@implementation ViewController

 

@synthesize name = _myName;

 

- (void)viewDidLoad {

    [super viewDidLoad];

 

    self.name = @"国士梅花";

    NSLog(@"name is : %@", _myName);

}

 

@end

从代码中可以看出,1、当我们指定了成员变量的名称(指定为带下划线的myName),就会生成指定的成员变量。如果代码中存在带下划线的name,就不会在生成了。2、如果是@synthesize name;还会生成一个名称为带下划线的name成员变量,也就是说如果没有指定成员变量的名称会自动生成一个属性同名的成员变量。3、如果是@synthesize name = _name; 就不会生成成员变量了。

在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景呢?先搞清楚一个问题,什么时候不会使用自动合成?

  • 同时重写了setter和getter时。

  • 重写了只读属性的getter时。

  • 使用了@dynamic时。

  • 在@protocol中定义的所有属性。

  • 在category中定义的所有属性。

  • 重载的属性。

注意点:

  • 在category中使用@property也是只会生成getter和setter方法的声明,如果真的需要给category增加属性的实现,需要借助于运行时的两个函数:objc_setAssociatedObject和objc_getAssociatedObject。

  • 在protocol中使用property只会生成setter和getter方法声明,使用属性的目的是希望遵守我协议的对象能够实现该属性。

weak、copy、strong、assgin分别用在什么地方?

什么情况下会使用weak关键字?

  • 在ARC中,出现循环引用的时候,会使用weak关键字。

  • 自身已经对它进行了一次强引用,没有必要再强调引用一次。

assgin适用于基本的数据类型,比如NSInteger、BOOL等。

NSString、NSArray、NSDictionary等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary;

除了上面的三种情况,剩下的就使用strong来进行修饰。

为什么NSString、NSDictionary、NSArray要使用copy修饰符呢?

要搞清楚这个问题,我们先来弄明白深拷贝与浅拷贝的区别,以非集合类与集合类两种情况来进行说明下,先看非集合类的情况,代码如下:

1

2

3

NSString *name = @"国士梅花";

NSString *newName = [name copy];

NSLog(@"name memory address: %p newName memory address: %p", name, newName);

运行之后,输出的信息如下:

1

name memory address: 0x10159f758 newName memory address: 0x10159f758

可以看出复制过后,内存地址是一样的,没有发生变化,这就是浅复制,只是把指针地址复制了一份。我们改下代码改成[name mutableCopy],此时日志的输出信息如下:

1

name memory address: 0x101b72758 newName memory address: 0x608000263240

我们看到内存地址发生了变化,并且newName的内存地址的偏移量比name的内存地址要大许多,由此可见经过mutableCopy操作之后,复制到堆区了,这就是深复制了,深复制就是内容也就进行了拷贝。

上面的都是不可变对象,在看下可变对象的情况,代码如下:

1

2

3

NSMutableString *name = [[NSMutableString alloc] initWithString:@"国士梅花"];

NSMutableString *newName = [name copy];

NSLog(@"name memory address: %p newName memory address: %p", name, newName);

运行之后日志输出信息如下:

1

name memory address: 0x600000076e40 newName memory address: 0x6000000295e0

从上面可以看出copy之后,内存地址不一样,且都存储在堆区了,这是深复制,内容也就进行拷贝。在把代码改成[name mutableCopy],此时日志的输出信息如下:

1

name memory address: 0x600000077380 newName memory address: 0x6000000776c0

可以看出可变对象copy与mutableCopy的效果是一样的,都是深拷贝。

总结:对于非集合类对象的copy操作如下:

  • [immutableObject copy]; //浅复制

  • [immutableObject mutableCopy]; //深复制

  • [mutableObject copy]; //深复制

  • [mutableObject mutableCopy]; //深复制

采用同样的方法可以验证集合类对象的copy操作如下:

  • [immutableObject copy]; //浅复制

  • [immutableObject mutableCopy]; //单层深复制

  • [mutableObject copy]; //深复制

  • [mutableObject mutableCopy]; //深复制

对于NSString、NSDictionary、NSArray等经常使用copy关键字,是因为它们有对应的可变类型:NSMutableString、NSMutableDictionary、NSMutableArray,它们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性时拷贝一份。

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325345074&siteId=291194637
Recommended