iOS开发之属性修饰符

iOS开发之属性修饰符

  1. 多线程的特性
  2. 读写特性
  3. 访问修饰符
  4. 修饰符的用法介绍

一、多线程特性
默认:automic
**1、automic:原子的,表示线程安全,(即加锁,保证setter和getter存取方法的线程安全,仅仅对setter加锁,正因为这个原因不是绝对安全)。**使用automic的目的是为了确保其他线程不再同一时间内访问形同的资源。使用工程中不会被线程调度机制打断的操作,原子操作一旦开始,就要一直运行到结束,不会被打断。(ios10之前是自旋锁,ios10之后使用的是互斥锁,编译器会自动生成互斥加锁的代码,避免变量的读写不同步)但往往即使声明了automic属性也不一定保证线程安全,而且这种机制是耗费系统资源的。(所以一般都声明为nonatomic属性)

首先第一点,你要记住,@property(atomic)NSArray *array 其实修饰的是这个指针,也就是这个8字节内存,跟第二部分数据n字节没有任何关系,被atomic 修饰之后,你不可能随意去多线程操作这个8字节,但是对8字节里面所指向的n字节没有任何限制!这就是所有网络上所说的 atomic 不安全的真相。
atomic 的意义就在于保护指针的安全性,保证这个“指针”被有序的访问。但是对指向的内容 不保证安全。

@property (atomic, strong) NSMutableArray *dataArray;
// 以下操作非线程安全
[self.dataArray addObject:newObj];  // 需额外对dataArray加锁

**2、nonatomic:非原子的。表示线程不安全。**可以在不同的地方读取和设置属性的值,(可能会导致读写不同步)编译器会少生成一些互斥加锁的代码,可以提高效率。
总结:涉及到多线程的时候,使用atomic,保证安全。不涉及多线程,使用nonatomic效率会更高。博主建议 所有的属性,都尽可能使用nonatomic,以提高效率,除非真的有必要考虑线程安全。
atomic与nonatom的主要区别就是系统自动生成的getter/setter方法不一样
atomic会对系统自动生成的setter方法会进行加锁操作。
nonatomic系统自动生成的getter/setter方法不会进行加锁操作。
二、读写特性
默认:readwrite
**readwrite:**编译器会为属性生成get和set方法
**readonly:**编译器只生成get方法
即,readwrite属性修饰时可读可写,而readonly修饰时只能读取它的属性而能修改。

三、访问修饰符(类的实例化权限,也叫继承权限)
这里首先介绍一下property与synthesize
property | synthesize
这两个关键字是编译器特性,可以让 Xcode 可以自动生成 getter/setter 的声明和实现!iOS 6 之后 (xcode4.4)LLVM 编译器引入property autosynthesis,即属性自动合成。换句话说,就是编译器会为每个 @property 添加 @synthesize

  1. @property 它可以自动生成某个成员变量的 setter/getter 的声明,比如 @property int age 会自动扩展成下面两句
- (void)setAge:(int)age;
- (int)age;
  1. @synthesize 帮助生成成员变量的 setter/getter 的实现,比如 synthesize age = _age 会创造一个带下划线前缀的实例变量名,同时使用这个属性生成 getter/setter 方法的实现
    注:使用 @synthesize 只有一个目的,就是给实例变量起个别名,或者说为同一个变量添加两个名字
- (void)setAge:(int)age{
    _age = age;
}
- (int)age{
    Return _age;
}

例子:
在.h文件中

@property(nonatomic)NSString *age;

在.m中重写age的getter和setter方法

- (NSString *)age {
    return _age;
}

- (void)setAge:(NSString *)age {
    _age = age;
}

编译后会报错:Use of undeclared identifier ‘_age’; did you mean ‘_name’?, 当 setter/getter 方法均是手动实现时,那么编译器将不会生成成员变量且编译报错

在.m的@implementation里面加入

@synthesize age = _age;

或者

@synthesize age;

再次编译就OK了。

原因:@property已经自动生成了age的get和set方法,也生成了带下划线的age,手动重写age的get和set方法使@property的作用就被覆盖了,并且带下划线的age也不存在了。在.m的@implementation里面加入@synthesize,编译器在编译时会自动为我们合成name属性的getter和setter方法。
dynamic
注:使用 @dynamic 可阻止 @synthesize 自动合成!经典的使用场景是你知道已经在某处实现了getter/setter 方法,而编译器不知道的情况
@dynamic告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成。(当然对于readonly的属性只需提供getter即可)。假如一个属性被声明为@dynamic var,然后你没有提供@setter方法和@getter方法,编译的时候没问题,
但是当程序运行到instance.var =someVar,由于缺setter方法会导致程序崩溃;
或者当运行到 someVar = var时,由于缺getter方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
1、【缺省】修饰@protected
即受保护的成员变量,1)、父类可以在自己的成员方法中使用,继承于该父类的子类也可以在成员方法中使用。2)、不能在外部函数使用。3)、默认情况下,所有的成员变量都是受保护。
2、@public 即公开的成员变量
1)、父类可以用,子类可以用,外部方法也可以调用。2)、完全没有权限可言,想用就可以用。
注意:不建议使用,极不安全。
3、@private 私有的成员变量
1)、只能在父类的成员方法中使用,继承于该类的子类,没有任何使用权限
2)、在外部也不能使用
四、修饰符的用法介绍
1、copy
**1)NSString:**通常都使用copy,以得到新的内存分配,而不只是原来的引用。
这里写图片描述
这里写图片描述在这里插入图片描述

通过上面两图你发现了什么,当testStr1用strong修饰时我们只给self.testStr1赋了一次值,但是self.testStr1 的值改变了,这是因为把可变字符的地址指向了testStr1,所以string1的值改变了,self.testStr1也跟着改变,而当用copy时,相当于对string1做了一次深拷贝,两者地址不同,当修改string1的值时,testStr1当然不会变,考虑到数据的安全性,字符串用copy修饰。

注意:
在这里插入图片描述赋值时要用self,这样回触发setter方法,才会重新分配内存

2)NSMutableString 为什么用strong不用copy

定义4个变量
@property (nonatomic,strong)NSMutableString *myStr1;
@property (nonatomic,copy)NSMutableString *myStr2;
@property (nonatomic,copy)NSMutableString *myStr3;
@property (nonatomic,copy)NSString *myStr4;
//myStr1用的strong修饰,使用appendString没问题
    self.myStr1 = [[NSMutableString alloc] initWithString:@"123"];
    [self.myStr1 appendString:@"KKK"];
    
    //myStr2 未初始化
    
    //myStr3初始化没问题,
    self.myStr3 = [[NSMutableString alloc] initWithString:@"123"];
    if([self.myStr3 isKindOfClass:[NSMutableString class]]){
        NSLog(@"myStr3 是NSMutableString");
    }else if ([self.myStr3 isKindOfClass:[NSString class]]){
        NSLog(@"myStr3 是NSString");
    }else {
        NSLog(@"其他");
    }
    //myStr3使用appendString发生闪退
    //[self.myStr3 appendString:@"444"];
    
    //给myStr3设置初始值
    self.myStr4 = @"2345";

执行后发现:会打印myStr3 是NSString
因为myStr3使用copy修饰,初始化时进行了copy操作,变成了NSString,所以在使用NSMutableString的API时发生闪退。

2)Block声明用copy
普及:
1.Block分为全局Block、堆Block和栈Block
2.方法是在内存的栈区,每一个方法都是在被调用的时候从硬盘到内存,然后去执行,执行完就消失,所以,方法的内存不需要我们管理。
3.OC中的类对象(在堆区),block默认是在栈区,我们没办法控制他的消亡。
当我们用copy修饰的时候,系统会把该 block的实现拷贝一份到堆区,这样我们对应的属性,就拥有的该block的所有权。就可以保证block代码块不会提前消亡。, 因此如果block中用到self时, 需要将其弱化, 通过__weak或者__unsafe_unretained.
2、strong和weak、assign
weak
不增加引用计数,也不持有对象,ARC时才会使用,ARC模式下会使用,相当于assign,对象废弃可以把对应的指针变量置为nil的状态。只可以修饰对象,如果修饰基本数据类型,编译器会报错。
weak比assign多了一个功能,当对象消失后自动把指针变成nil,好处不言而喻。
runtime如何实现weak变量的自动置nil
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,key是所指对象的地址,value是weak指针的地址(这个地址的值是所指对象的地址)数组。
weak 的实现原理可以概括一下三步:
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用objc_storeWeak() 函数,objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
assign
修饰基本数据类型:基本数据类型是分配在栈上的,由系统分配和释放,所以不会造成野指针。
修饰对象:如果用assign修饰对象,当对象被释放后,指针的地址还是存在的,也就是说指针并没有被置为nil,从而造成了野指针。因为对象是分配在堆上的,堆上的内存由程序员分配释放。而因为指针没有被置为nil,如果后续的内存分配中,刚好分配到了这块内存,就会造成崩溃。

strong与week
strong与weak是由ARC新引入的对象变量属性,ARC引入了新的对象的生命周期限定,即零弱引用。如果零弱引用指向的对象被deallocated的话,零弱引用的对象会被自动设置为nil。即一旦最后一个指向该对象的strong类型的指针离开,这个对象将被释放,如果这个时候还有weak指针指向该对象,则会清除掉所有剩余的weak指针。
强引用与弱引用的广义区别:
强引用也就是我们通常所讲的引用,其存亡直接决定了所指对象的存亡。如果不存在指向一个对象的引用,并且此对象不再显示列表中,则此对象会被从内存中释放。
弱引用除了不决定对象的存亡外,其他与强引用相同。即使一个对象被持有无数个若引用,只要没有强引用指向他,那么其还是会被清除。
简单讲,strong等同retain,weak比assign多了一个功能,当对象消失后自动把指针变成nil。
strong:对于继承于NSObject类型的对象,若要声明为强使用,使用strong,若要使用弱引用,使用__weak来引用,用于解决循环强引用的问题。

1)对于xib上的控件引用,使用weak

这里写图片描述

当我们用xib拉一个控件的时候,系统会默认使用weak修饰,这是应为,在连接时因为这个label已经放到view上了,label的引用计数为1,因此只要这个View不被释放,这个label的引用计数都不会为0,因此这里使用weak引用。

如果我们不使用xib/storyboard,而是使用纯代码创建仍然用weak修饰
@property (nonatomic, weak) UILabel *label;
由于label在创建时,没有任何强引用,因此就有可能提前释放。所以只要我们在创建以后需要使用它,我们必须保证至少有一个强引用,否则引用计数为0,就会被释放掉。得出结论:使用纯代码时控件需要用strong修饰。
2)代理用weak修饰
我们这里举一个例子建立一个Person类和一个Pig类,Pig类中有一个PigDelegate协议
weak:指明该对象并不负责保持delegate这个对象,delegate这个对象的销毁由外部控制
这里写图片描述

strong:该对象强引用delegate,外界不能销毁delegate对象,会导致循环引用(Retain Cycles)
这里写图片描述
代码逻辑如下:
建立一个Pig类
pig.h文件
这里写图片描述

pig.m文件
这里写图片描述
然后建立一个Person类
Person.h文件
这里写图片描述

Person.m文件
这里写图片描述

在ViewController中
这里写图片描述

结果
1)当代理用weak修饰时,运行程序后在控制台会打印:
这里写图片描述
说明两者都被正常释放销毁
2)当代理用strong修饰时,运行程序后在控制台打印内容为空
这里写图片描述
说明没有走到两者的delloc方法中,两者并没有释放销毁,就造成了内存泄漏。
3、__weak与__strong

__weak用于声明一个弱引用。弱引用不会增加对象的引用计数,因此它不会阻止ARC释放对象。当对象被释放时,弱引用会自动置为nil,从而避免野指针问题。
比如:用于解决Block引起的循环引用
举例:某对象self,有strong类型的成员变量blockA,blockA内部引用了self,如果self不经过__weak处理,就会变成:self强引用blockA,blockA强引用self,这就是循环引用。
使用用法:
__weak typeof(self) weakSelf = self;
就可以直接使用weakSelf代替self了,当self释放时,weakSelf已经等于nil。

__strong用于声明一个强引用。强引用会增加对象的引用计数,从而保持对象在内存中。当对象的引用计数降为0时,ARC会自动释放对象。总结就是__strong 修饰在 block 中主要是为了在合适的时机增强对外部对象的持有,在避免循环引用的前提下,保证在 block 执行期间对象能正常被访问和操作。
在 iOS 开发中,以下是一些在 block 里面使用 __strong 的常见场景:
1.捕获对象并防止提前释放:当 block 内部需要访问外部的对象,且希望在 block 执行期间保证该对象一直存活时,通常会使用 __strong 修饰。
2.涉及到对象的异步操作且后续依赖该对象:假设你从网络获取数据并在获取成功后需要更新界面相关的操作,并且在更新过程中要持续持有相关的 UI 控件对象等情况。
3.嵌套的 block 中对外部已捕获对象的使用:当存在多层 block 嵌套,且内部的 block 也需要访问外部已经被捕获的对象时,也常使用 __strong 来确保对象的有效性。

4、__block
Blocks可以访问局部变量,但是不能修改如果修改局部变量,需要加__block

这里写图片描述
本图说明在block里面修改tempNum的值系统会报Variable is not assignable (missing __block type specifier)错误.

这里写图片描述
但如果对tempNum加上__Block则不会报错
结论:
1.在block中可以修改全局变量,因为全局变量在任何作用域都可以调用,block不会对其进行捕获。
2.block对对象局部变量是指针捕获,会对其强引用,在内部可以修改变量而不报错
3.block对基本数据类型的auto局部变量是值捕获,无法去修改外部的变量。
__Block原理如下:
编译器会将__block变量包装成一个对象,变成对象后就可以根据指针地址在block内部去修改外部的变量.

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_tempNum_0 *__forwarding;
 int __flags;
 int __size;
 int temNum;
};

__Block_byref_tempNum_0中包含了isa指针,所以变量为对象。
__forwarding是指向自己的指针
tempNum是变量的值
block通过__forwarding指针去修改tempNum的值

补充点:
一、会循环引用

    _person2 = [[PersonModel alloc] init];
    _person2.name = @"张三";

    _firstView = [[YDDetailDownAlertView alloc] init];
    _firstView.firstPictureBlock = ^{
        NSLog(@"_person2.name=%@",self.person2.name);
    };
    [self.view addSubview:_firstView];

二、不会循环引用:
场景1:block 的持有者为 controller,当 self 用_weak修饰

    _person2 = [[PersonModel alloc] init];
    _person2.name = @"张三";

    __weak __typeof(self) weakSelf = self;
    _firstView = [[YDDetailDownAlertView alloc] init];
    _firstView.firstPictureBlock = ^{
        NSLog(@"_person2.name=%@",weakSelf.person2.name);
    };
    [self.view addSubview:_firstView];

场景2:block 的持有者不是 controller,即使 block 里面直接用 self,也不会循环引用,如果 controller 被pop除去,也会正常的 delloc。

 YDDetailDownAlertView *view = [[YDDetailDownAlertView alloc] init];
    view.firstPictureBlock = ^{
        NSLog(@"_person2.name=%@",self.person2.name);
    };
    [view show];

场景3:如果 block 里面存在延迟执行,但block 的持有者不是 controller,也不会有任何问题,block 里面延迟代码执行完,controller 会被正常 delloc

 YDDetailDownAlertView *view = [[YDDetailDownAlertView alloc] init];
    view.firstPictureBlock = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"_person2.name=%@",self.person2.name);
        });
    };
    [view show];

三、如果 block 里面存在延迟执行,只用 __weak,控制器会立即销毁,block 里面的代码也会执行,但是会打印 null

__weak __typeof(self) weakSelf = self;
    _person2 = [[PersonModel alloc] init];
    _person2.name = @"张三";

    _firstView = [[YDDetailDownAlertView alloc] init];
    _firstView.firstPictureBlock = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"_person2.name=%@",weakSelf.person2.name);
        });
    };
    [self.view addSubview:_firstView];

如果在 10s 内退出了controller,退出时 controller 已经被释放了,但 block 里面的内容仍然会被执行。,但已经获取不到 self 了。如果在dispatch_after里面有执行其他方法,因为获取不到 self,方法也不值执行。

PersonViewController被释放
_person2.name=(null)

*四、.如果 block 里面存在延迟执行,可以使用__typeof(&weakSelf) strongself = weakSelf或者__strong typeof(self) strongself = weakSelf,那么 controller 会在dispatch_after里面执行之后才 delloc。

-(void)dealloc {
    NSLog(@"PersonViewController被释放");
}
 _person2 = [[PersonModel alloc] init];
    _person2.name = @"张三";
    
    __weak __typeof(self) weakSelf = self;
    NSLog(@"self.address1=%p",self);
    NSLog(@"self.address2=%@",&*self);
    
    _firstView = [[YDDetailDownAlertView alloc] init];
    _firstView.firstPictureBlock = ^{
        
        NSLog(@"self.address3=%p",weakSelf);
        NSLog(@"self.address4=%@",&*weakSelf);
        
        //__typeof(&*weakSelf) strongself = weakSelf;
        __strong typeof(self) strongself = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"_person2.name=%@",strongself.person2.name);
        });
    };
    [self.view addSubview:_firstView];

控制台打印:

self.address1=0x109b07430
self.address2=<PersonViewController: 0x109b07430>
self.address3=0x109b07430
self.address4=<PersonViewController: 0x109b07430>
_person2.name=张三
PersonViewController被释放

5、unsafe_unretained: 这个是比较少用的,几乎没有使用到。在所引用的对象被释放后,该指针就成了野指针,不好控制。
6、_unsafeunretained: 也是很少使用。同上。
7、assign 不会使引用计数加1,直接赋值,可修饰对象,和基本数据类型。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值