IOS开发基础——属性关键字(copy strong weak等)

参考博客:

小记

在ios的开发中,咱们最经常使用到的就是那些修饰属性的关键字。ios

  • 内存管理有关的关键字:weak,assign,strong,retain,copy
  • 线程安全有关的的关键字:nonatomic,atomic
  • 访问权限有关的的关键字:readonly,readwrite(只读,可读写)
  • 修饰变量的关键字:const,static,extern 这些都是咱们在平常的开发中经常使用到的一些关键字。关于他们的详细用法以及做用,在下面进行详细的分析讲解。

内存管理有关的的关键字:(weak,assign,strong,retain,copy)

关键字weak

一样常常用于修饰OC对象类型的数据,修饰的对象在释放后,指针地址会自动被置为nil,这是一种弱引用。安全

注意:在ARC环境下,为避免循环引用,每每会把delegate属性用weak修饰;在MRC下使用assign修饰。当一个对象再也不有strong类型的指针指向它的时候,它就会被释放,即便还有weak型指针指向它,那么这些weak型指针也将被清除。bash

关键字assign

常常用于非指针变量,用于基础数据类型 (例如NSInteger)和C数据类型(int, float, double, char, 等),另外还有id类型。用于对基本数据类型进行复制操做,不更改引用计数。也能够用来修饰对象,可是,被assign修饰的对象在释放后,指针的地址仍是存在的,也就是说指针并无被置为nil,成为野指针。app

注意:之因此能够修饰基本数据类型,由于基本数据类型通常分配在栈上,栈的内存会由系统自动处理,不会形成野指针函数

以及:在MRC下常见的id delegate每每是用assign方式的属性而不是retain方式的属性,为了防止delegation两端产生没必要要的循环引用。例如:对象A经过retain获取了对象B的全部权,这个对象B的delegate又是A, 若是这个delegate是retain方式的,两个都是强引用,互相持有,那基本上就没有机会释放这两个对象了。测试

weak 和 assign 的区别:

  • 修饰的对象:weak修饰oc对象类型的数据,assign用来修饰是非指针变量。
  • 引用计数:weak 和 assign 都不会增长引用计数。
  • 释放:weak 修饰的对象释放后,指针地址自动设置为 nil,assign修饰的对象释放后指针地址依然存在,成为野指针。
  • 修饰delegate 在MRC使用assign,在ARC使用weak。

关键字stronng:

用于修饰一些OC对象类型的数据如:(NSNumber,NSString,NSArray、NSDate、NSDictionary、模型类等),它被一个强指针引用着,是一个强引用。在ARC的环境下等同于retain,这一点区别于weak。它是一咱们一般所说的指针拷贝(浅拷贝),内存地址保持不变,只是生成了一个新的指针,新指针和引用对象的指针指向同一个内存地址,没有生成新的对象,只是多了一个指向该对象的指针。 注意:因为使用的是一个内存地址,当该内存地址存储的内容发生变动的时候,会致使属性也跟着变动:ui

关键字copy:

一样用于修饰OC对象类型的数据,同时在MRC即(MMR)手动内存管理时期,用来修饰block,由于block须要从栈区copy到堆区,在如今的ARC时代,系统自动给咱们作了这个操做,所一如今使用strong或者copy来修饰block都是能够的。copy和strong相同点在于都是属于强引用,都会是属性的计数加一,可是copy和strong不一样点在于,它所修饰的属性当引用一个属性值时,是内存拷贝(深拷贝),就是在引用是,会生成一个新的内存地址和指针地址来,和引用对象彻底没有相同点,所以它不会由于引用属性的变动而改变。atom

copy与strong的区别(深拷贝 浅拷贝):

浅拷贝:指针拷贝,内存地址不变呢,指针地址不相同。spa

深拷贝:内存拷贝,内存地址不一样,指针地址也不相同。线程

声明两个copy属性,两个strong属性,分别为可变和不可变类型:

@property(nonatomic,strong)NSString * Strstrong;
@property(nonatomic,copy)NSString * Strcopy;
@property(nonatomic,copy)NSMutableString * MutableStrcopy;
@property(nonatomic,strong)NSMutableString * MutableStrstrong;`
复制代码

对属性进行赋值:

```
NSString * OriginalStr = @"我已经开始测试了";
//对 不可变对象赋值 不管是 strong 仍是 copy 都是原地址不变,内存地址都为(0x10c6d75c0),生成一个新指针指向对象(浅拷贝)
self.Strcopy = OriginalStr;
self.Strstrong = OriginalStr;
self.MutableStrcopy = OriginalStr;
self.MutableStrstrong = OriginalStr;
NSLog(@"rangle=>%@\n normal:copy=>%@=====strong=>%@\nMutable:copy=>%@=====strong=>%@",OriginalStr,_Strcopy,_Strstrong,_MutableStrcopy,_MutableStrstrong);
NSLog(@"rangle=>%p\n normal:copy=>%p=====strong=>%p\nMutable:copy=>%p=====strong=>%p",OriginalStr,_Strcopy,_Strstrong,_MutableStrcopy,_MutableStrstrong);
NSLog(@"rangle=>%p\n normal:copy=>%p=====strong=>%p\nMutable:copy=>%p=====strong=>%p",&OriginalStr,&_Strcopy,&_Strstrong,&_MutableStrcopy,&_MutableStrstrong);
```
复制代码

输出结果:

```
//内容值:  rangle=>我已经开始测试了
//        normal:copy=>我已经开始测试了=====strong=>我已经开始测试了
//        Mutable:copy=>我已经开始测试了=====strong=>我已经开始测试了
//内存地址:rangle=>0x10c6d75c0
//        normal:copy=>0x10c6d75c0=====strong=>0x10c6d75c0
//        Mutable:copy=>0x10c6d75c0=====strong=>0x10c6d75c0
//指针地址:rangle=>0x7ffee360d368
//        normal:copy=>0x7fc238907490=====strong=>0x7fc238907498
//        Mutable:copy=>0x7fc2389074a0=====strong=>0x7fc2389074a8
```
复制代码

由上面能够看出,strong修饰的对象,在引用一个对象的时候,内存地址都是同样的,只有指针地址不一样,copy修饰的对象也是如此。为何呢?不是说copy修饰的对象是生成一个新的内存地址嘛?这里为何内存地址仍是原来的呢?

由于,对不可变对象赋值,不管是strong仍是copy,都是同样的,原内存地址不变,0x10c6d75c0,生成了新的指针地址。

而后咱们试试用 可变对象 对属性进行赋值:

```
NSMutableString * OriginalMutableStr = [NSMutableString stringWithFormat:@"我已经开始测试了"];
self.Strcopy = OriginalMutableStr;
self.Strstrong = OriginalMutableStr;
self.MutableStrcopy = OriginalMutableStr;
self.MutableStrstrong = OriginalMutableStr;
```
复制代码

这一次的输出结果:

```
//内容值:  rangle=>我已经开始测试了
//        normal:copy=>我已经开始测试了=====strong=>我已经开始测试了
//        Mutable:copy=>我已经开始测试了=====strong=>我已经开始测试了
//内存地址:rangle=>0x6000032972a0
//        normal:copy=>0x600003297720=====strong=>0x6000032972a0
//        Mutable:copy=>0x6000032974e0=====strong=>0x6000032972a0
//指针地址:rangle=>0x7ffee360d368
//        normal:copy=>0x7fc238907490=====strong=>0x7fc238907498
//        Mutable:copy=>0x7fc2389074a0=====strong=>0x7fc2389074a8
```
复制代码

在上面的结果能够看出,strong修饰的属性内存地址依然没有改变,可是copy修饰的属性内存值产生了变化,再也不是0x6000032972a0。由此得出结论:

对可变对象赋值 strong 是原地址不变(0x600003f173f0),引用计数+1(浅拷贝)。 copy是生成一个新的地址和对象(0x600003297720和0x6000032974e0),生成一个新指针指向新的内存地址(深拷贝)

咱们来测试一下此时修改一下OriginalMutableStr的值,看看结果:

```
[OriginalMutableStr appendFormat:@"改变了"];
```
复制代码

再打印一下:

```
//内容值:  rangle=>我已经开始测试了改变了
//        normal:copy=>我已经开始测试了=====strong=>我已经开始测试了改变了
//        Mutable:copy=>我已经开始测试了=====strong=>我已经开始测试了改变了
//内存地址:rangle=>0x6000032972a0
//        normal:copy=>0x600003297720=====strong=>0x6000032972a0
//        Mutable:copy=>0x6000032974e0=====strong=>0x6000032972a0
//指针地址:rangle=>0x7ffee360d368
//        normal:copy=>0x7fc238907490=====strong=>0x7fc238907498
//        Mutable:copy=>0x7fc2389074a0=====strong=>0x7fc2389074a8
```
复制代码

看到 strong 修饰的属性,跟着进行了改变 当改变了原有值的时候,因为OriginalMutableStr是可变类型,是在原有内存地址上进行修改,不管是指针地址和内存地址都没有b改变,只是当前内存地址所存放的数据进行改变。因为 strong 修饰的属性虽然指针地址不一样,可是指针是指向原内存地址的,因此会跟着 OriginalMutableStr 的改变而改变。

不一样于strong,copy修饰的类型不只指针地址不一样,并且指向的内存地址也和OriginalMutableStr 不同,因此不会跟着 OriginalMutableStr 的改变而改变。

注意

  • 使用self.Strcopy 和 _Strcopy 来赋值也是两个不同的结果,由于后者没有调用 set 方法,而 copy 和 strong 之因此会产生差异就是由于在 set 方法中,copy修饰的属性: 调用了 _Strcopy = [Strcopy copy] 方法。
  • copy也分为 copy 和 mutableCopy,在对容器对象和非容器对象操做的时候也是有区别,下面来分析下:

多种copy模式:copy 和 mutableCopy 对 容器对象 进行操做

在对容器对象(NSArray)进行copy操做时,分为多种:

  • copy:仅仅进行了指针拷贝
  • mutableCopy:进行内容拷贝这里的单层指的是完成了NSArray对象的深copy,而未对其容器内对象进行处理使用(NSArray对象的内存地址不一样,可是内部元素的内存地址不变)
[arr mutableCopy];
复制代码
  • 双层深拷贝:这里的双层指的是完成了NSArray对象和NSArray容器内对象的深copy(为何不是彻底,是由于没法处理NSArray中还有一个NSArray这种状况)使用:
[[NSArray alloc] initWithArray:arr copyItems:YES]
复制代码
  • 彻底深拷贝:完美的解决NSArray嵌套NSArray这种情形,可使用归档、解档的方式可使用:
[NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:testArr]];
复制代码

线程安全有关的的关键字:(nonatomic,atomic)

关键字nonatomic

nonatomic非原子操做:(不加锁,线程执行快,可是多个线程访问同一个属性时,结果没法预料)

关键字atomic

atomic原子操做:加锁,保证 getter 和 setter 存取方法的线程安全(仅对setter和getter方法加锁)。 由于线程枷锁的缘由,在别的线程来读写这个属性以前,会先执行完当前的操做。

例如:

线程A调用了某一属性的setter方法,在方法还未完成的状况下,线程B调用了该属性的getter方法,那么只有在执行完A线程的setter方法之后才执行B线程的getter操做。当几个线程同时调用同一属性的 setter 和 getter方法时,会获得一个合法的值,可是get的值不可控(由于线程执行的顺序不肯定)。

注意:

atomic只针对属性的 getter/setter 方法进行加锁,因此安全只是针对getter/setter方法来讲,并非整个线程安全,由于一个属性并不仅有 setter/getter 方法,例:(若是一个线程正在getter 或者 setter时,有另一个线程同时对该属性进行release操做,若是release先完成,会形成crash)

修饰变量的关键字:(const,static,extern)

常量 const

常量修饰符,表示不可变,能够用来修饰右边的基本变量和指针变量(放在谁的前面修饰谁(基本数据变量p,指针变量*p))。

经常使用写法例如:

const 类型 * 变量名a:能够改变指针的指向,不能改变指针指向的内容。 const放 号的前面约束参数,表示*a只读。只能修改地址a,不能经过a修改访问的内存空间

int x = 12;
int new_x = 21;
const int *px = &x; 
px = &new_x; // 改变指针px的指向,使其指向变量y
复制代码

类型 * const 变量名:能够改变指针指向的内容,不能改变指针的指向。 const放后面约束参数,表示a只读,不能修改a的地址,只能修改a访问的值,不能修改参数的地址

int y = 12;
int new_y = 21;
int * const py = &y;
(*py) = new_y; // 改变px指向的变量x的值
复制代码

常量(const)和宏定义(define)的区别:

使用宏和常量所占用的内存差异不大,宏定义的是常量,常量都放在常量区,只会生成一分内存 缺点:

  • 编译时刻:宏是预编译(编译以前处理),const是编译阶段。致使使用宏定义过多的话,随着工程愈来愈大,编译速度会愈来愈慢
  • 宏不作检查,不会报编译错误,只是替换,const会编译检查,会报编译错误。

优势:

  • 宏能定义一些函数,方法。 const不能。

常量 static

定义所修饰的对象只能在当前文件访问,不能经过extern来引用

默认状况下的全局变量 做用域是整个程序(能够经过extern来引用) 被static修饰后仅限于当前文件来引用 其余文件不能经过extern来引用

  • 修饰局部变量:

有时但愿函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次再调用的时候该变量已经有值。这时就应该指定该局部变量为静态变量,用关键字 static 进行声明。

  • 延长局部变量的生命周期(没有改变变量的做用域,只在当前做用域有用),程序结束才会销毁。

注意:当在对象A里这么写static int i = 10; 当A销毁掉以后 这个i还存在 当我再次alloc init一个A的对象以后 在新对象里 依然能够拿到i = 90 除非杀死程序 再次进入才能获得i = 0。

  • 局部变量只会生成一分内存,只会初始化一次。把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在
- (void)test{
    // static修饰局部变量1
    static int age = 0;
    age++;
    NSLog(@"%d",age);
}
-(void)test2{
    // static修饰局部变量2
    static int age = 0;
    age++;
    NSLog(@"%d",age);
}

[self test];
[self test2];
[self test];
[self test2];
[self test];
[self test2];
复制代码

输出结果:

2019-02-13 16:51:00.147356+0800 ProjectExecises[9872:2237314] 1
2019-02-13 16:51:00.916502+0800 ProjectExecises[9872:2237314] 1
2019-02-13 16:51:01.898249+0800 ProjectExecises[9872:2237314] 2
2019-02-13 16:51:02.514333+0800 ProjectExecises[9872:2237314] 2
2019-02-13 16:51:03.097697+0800 ProjectExecises[9872:2237314] 3
2019-02-13 16:51:05.832851+0800 ProjectExecises[9872:2237314] 3
复制代码

因而可知 变量生命周期延长了,做用域没有变

  • 修饰全局变量:
  • 只能在本文件中访问,修改全局变量的做用域,生命周期不会改
  • 避免重复定义全局变量(单例模式)

常量 extern

只是用来获取全局变量(包括全局静态变量)的值,不能用于定义变量。先在当前文件查找有没有全局变量,没有找到,才会去其余文件查找(优先级)。

#import "JMProxy.h"
@implementation JMProxy
int ageJMProxy = 20;
@end

@implementation TableViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    extern int ageJMProxy;
    NSLog(@"%d",ageJMProxy);
}
@end
复制代码

static与const联合使用

声明一个静态的全局只读常量。开发中声明的全局变量,有些不但愿外界改动,只容许读取。

iOS中staic和const经常使用使用场景,是用来代替宏,把一个常用的字符串常量,定义成静态全局只读变量.

// 开发中常常拿到key修改值,所以用const修饰key,表示key只读,不容许修改。
static  NSString * const key = @"name";

// 若是 const修饰 *key1,表示*key1只读,key1仍是能改变。

static  NSString const *key1 = @"name";
复制代码

extern与const联合使用

在多个文件中常用的同一个字符串常量,可使用extern与const组合

extern与const组合:只须要定义一份全局变量,多个文件共享

@interface JMProxy : NSProxy
extern NSString * const nameKey = @"name";
@end

#import "JMProxy.h"
@implementation JMProxy
NSString * const nameKey = @"name";
@end
复制代码
相关文章
相关标签/搜索