知识基础不够牢。
一知半解、想当然的用没准将来会出大问题。
借用@我就叫Sunny怎么了的一句话:
《一个人iOS的基础如何、只问一个property就够了》
决定(深入?)总结一下暂时所能想到的property。受篇幅所限、把一些相对冗余的原理探究放到了其他帖子。有兴趣的童鞋可以去看(个人觉得还是挺有用的)
目录
※ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?
※@property中有哪些属性关键字?
原子性关键字
nonatomic/atomic
atomic---原子性(atomic内部实现)
※※※atomic声明的属性一定是线程安全的么
※※※copy关键字
※何时用copy关键字声明
※※※block(block作用域/__block、__weak、__strong的原理)
NSString、NSArray、NSDictionary
※※深拷贝浅拷贝
※※weak/assign关键字
相似、区别、assign如何修饰对象、ib对象为什么用weak
strong关键字
unsafe_unretained关键字
readonly/readwrite关键字
可选值: nullable、nonnull、null_resettable、null_unspecified关键字
※ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?
-
对应基本数据类型默认关键字是
atomic,readwrite,assign
-
对于普通的OC对象
atomic,readwrite,strong
※@property中有哪些属性关键字?
按照性质可以分为四类。(原子性/内存管理语义/读写权限/setter/getter别名)
原子性: nonatomic、atomic
内存管理:assign、strong、 weak、unsafe_unretained、copy
读写权限:readwrite(读写)、readooly
方法名:getter=、setter=
可选值: nullable、nonnull、null_resettable、null_unspecified
其中atomic,nonatomic,copy在setter/getter中实现。
而weak和strong等则是直接作用于成员变量上。
-
原子性关键字
首先、nonatomic以及atomic除了自动合成中的setter/getter方法内部不同、其他并没有任何不同。
所以、如果你自己重写了setter/getter。那么‘原子性’标签将失效、只起到提示作用。
nonatomic---非原子
- 单纯的自动生成setter/getter方法。
- 读写速度优于atomic、如果不需要保证数据完整性(如多线程)。尽量使用nonatomic。
atomic---原子性
在自动合成setter/getter方法的过程中、为对象添加条件锁。
那么、atomic究竟是如何保护对象的原子性/这种保护是否需要可靠呢。
想再了解深入一些、可以参阅延展篇《原子性相关延伸》
-
※※※copy关键字
抛开一搜一大把的解释、这里还需要分为两部分来看:何时用copy关键字声明/深拷贝浅拷贝
-
※何时用copy关键字声明
为了确保属性对象的完整性以及封装性、只要实现属性所用的对象是“可变的” 、就应该在设置新属性值时拷贝一份。
※※※block
- 在MRC中、block内部的代码块依旧是在栈区的、使用copy可以把它放到堆区。
在ARC中、使用copy还是strong效果是一样的。使用copy更多的是为了语义化(详见官方文档)。
那么、block究竟有哪些特殊。究竟__block、__weak、__strong是如何工作的呢。
想再了解深入一些、可以参阅延展篇《Block相关延伸》
※NSString、NSArray、NSDictionary
简而言之、因为父类指针可以指向子类对象。通常我们为了保护变量的完整性、不希望这个对象被赋值之后又在不知情的情况下被改变。就像下面这样:
@interface ViewController () @property (nonatomic,strong,readwrite) NSString * strongedStr; @property (nonatomic,copy,readwrite) NSString * copyedStr; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. NSMutableString * mOStr = [[NSMutableString alloc]initWithString:@"123123"]; self.strongedStr = mOStr; self.copyedStr = mOStr; NSLog(@"原对象改变前的strongedStr---%@",self.strongedStr); NSLog(@"原对象改变前的copyedStr---%@",self.copyedStr); [mOStr appendString:@"lalala"]; NSLog(@"原对象改变后的strongedStr---%@",self.strongedStr); NSLog(@"原对象改变后的copyedStr---%@",self.copyedStr); }
结果
原对象改变前的strongedStr---123123 原对象改变前的copyedStr---123123 原对象改变后的strongedStr---123123lalala 原对象改变后的copyedStr---123123
如上所示、无论原对象如何改变。copy声明的对象都可以保持当初被赋值的样子。
那么、copy到底是如何工作的、他和strong的声明还有哪些不同、这里的copy和copy/mutableCopy是否相同呢。
想再了解深入一些、可以参阅延展篇《strong&©声明相关延伸》
-
※※深拷贝浅拷贝
关于深拷贝和浅拷贝的概念、这是基础中的基础。
浅拷贝
- 源对象和副本对象是同一对象;
- 源对象(也就是副本对象)引用计数器+1。
本质:并未产生新对象。(由于源对象本身就不可变)。
深拷贝
- 源对象和副本对象是不同的两个对象
- 源对象引用计数器不变,副本对象计数器为1。
本质:产生了新对象。(由于源对象本身就可变、需要分离)。
再简单点说
只有不可变对象的copy方式,是浅复制,其他都是深复制。
copy与mutableCopy
不论源对象是否可变
- copy复制出的对象都是不可变对象
- mutableCopy复制出的对象都是可变对象
基本类型不允许copy
自定义对象
需要实现对应协议NSCopying/NSMutableCopying
- (id)copyWithZone:(NSZone *)zone{ PersonModel *model = [[[self class] allocWithZone:zone] init]; model.firstName = self.firstName; model.lastName = self.lastName; //未公开的成员--将其拷贝成不可变 model->_friends = [[NSMutableSet alloc] initWithSet:_friends copyItems:YES]; return model; } - (id)mutableCopyWithZone:(NSZone *)zone{ PersonModel *model = [[[self class] allocWithZone:zone] init]; model.firstName = self.firstName; model.lastName = self.lastName; //未公开的成员--将其拷贝成可变 model->_friends = [_friends mutableCopy]; return model; }
需要注意一点
对集合对象而言、即使的深拷贝。也只是拷贝了自身容器、对象内部的元素依旧是浅拷贝。
解决的方式就是手动对内部元素进行copy。网上看到两种比较方便的解决办法。
-
※※weak/assign关键字
之所以把这两个关键字放在一起讲。是因为在ARC下、他俩很相似。
相似点:
- 都不会让对象的引用计数+1;
也有区别:
weak在指针对象释放的时候会自动置nil、assign则不进行任何操作。
weak修饰对象类型、assign修饰基本类型。
其实这两个区别本质上就是第一种、因为基本类型通常被分配在栈上、由系统自动释放。而如果对象释放、指针指向没有释放。那么就会出现野指针。
什么时候用weak:
当你不希望你的属性、需要被本类手动释放的时候。
最典型的例子就是代理、当外界主体被消灭。本类delegate指针也指向nil。
什么时候用assign:
几乎所有的普通类型变量。
assign能不能修饰对象:
-
可以。
既然我们知道二者的区别。那么只要让assign声明的对象、在本身释放的时候将指针指向置nil就可以了。
具体写要分情况。
比如可以在自己VC被释放的时候、将引用自己的代理手动置nil。-(void)dealloc { self.xxx.delegate = nil; }
-
如何让没有声明weak属性的对象、拥有weak属性。
weak声明的对象在runtime布局的时候、会被放入一个hash表中。
以对象的内存地址作为key、本身作为value。
当对象被释放、再以其地址作为索引去搜索hash表。
找出value、并将其置nil。
具体实现有空再新开帖补上~
-
ib创建的对象为什么用weak
简单来讲、ib创建的对象在ib中就已经被其父view强引用了一次。
只要父view没有被释放、其自然不会被释放。
然后、怎么证明呢?#define TLog(prefix,Obj) {NSLog(@"变量指针:%p,变量值地址:%p, 指向对象值:%@, 变量类型:%@--%@",&Obj,Obj,Obj,[Obj class],prefix);} @interface ViewController () @property(nonatomic,weak) UIButton *btn; @property(nonatomic,weak) UIButton *btn2; @end @implementation ViewController - (void)viewDidLoad { [self test]; TLog(@"btn", _btn); TLog(@"btn2", _btn2); } -(void)test { UIButton *button = [[UIButton alloc]init]; _btn = button; [self.view addSubview:_btn]; UIButton *button2 = [[UIButton alloc]init]; _btn2 = button2; }
我没有将button2添加到view上、在出了局部作用域之后、button2被系统释放。_btn2自然也指向空对象。
打印结果
变量指针:0x7f8538502600,变量值地址:0x7f8538609af0, 指向对象值:<UIButton: 0x7f8538609af0; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x6000000286c0>>, 变量类型:UIButton--btn 变量指针:0x7f8538502608,变量值地址:0x0, 指向对象值:(null), 变量类型:(null)--btn2
-
strong关键字
其实strong想不起好说的、因为都用的太熟了~
只有当strong不能满足我们需求的情况下、才会去用其他的修饰符。
- 当需要解决循环引用或者自身已经对其有过一次强引用的时候、用weak。
- 当NSString、NSArray、NSDictionary等需要保持自身完整性、不希望外界修改或者传进来一个可变类型的时候、用copy。
-
unsafe_unretained关键字
我入行的晚、unsafe_unretained基本没用过~
只知道是对象版本的assign。
修饰对象、但不会自动置nil。
但因为不需要创建hash表检测对象的存活情况、在明确知晓生命周期的时候。unsafe_unretained相比weak有一定的性能提升。
-
readonly/readwrite关键字
表示属性的读写权限。
- readwrite系统会自动合成setter&&getter方法
- readonly系统则只合成了get方法。
似乎也没什么太深的东西、不过还是有几点可以提一句的。
- 如果你自己实现了set/get或者声明了@dynamic(这个不属于property、所以先不写~大概就是告诉系统不要帮你合成)。那么系统将不再为你自动合成。
-
readonly虽然不可以直接修改、但是可以用KVC修改。
- 网上有说KVO无法监听readonly的属性、但只要你实现了内部的set方法。当对象被释放的时候、你依然可以收到通知。(具体什么时候用呢、比如单例对象的代理。当外部代理对象释放、你可以这样监听。让自身调用某个方法)。
-
getter=/setter=
-
setter= 重写set声明。没见谁用...如果哪位大佬有实用的地方、可以告诉小弟、不胜感激。
-
setter= 重写get声明。起到方便阅读的作用呗~
@property (nonatomic, getter=isOn) BOOL on;
-
可选值声明
nullable、nonnull、null_resettable、null_unspecified
这四个声明是xcode6新出的、应该是为了迎合swift中的optional、non-optional吧。方便我们的混编、而且可以让代码更加规范、易懂。在发生了不符合规定的行为时、编译器会发出警告。(虽然不会崩溃)
-
nullable:可以为空
作为属性、可以有三种声明规范:
// 方式一:
@property (nonatomic, copy, nullable) NSString *name;
// 方式二:
@property (nonatomic, copy) NSString *_Nullable name;
// 方式三:
@property (nonatomic, copy) NSString *__nullable name;
也可以声明在方法里
- (nullable NSString *)test1 {
return nil;
}
- (NSString * _Nullable)test2 {
return nil;
}
- (NSString * __nullable)test3 {
return nil;
}
规律就这样~没啥必要再总结下划线和大小写了吧。
效果长这样:
-
nonnull:不能为空
和nullable相对、用法相同。
但有一点需要注意。
oc中的nonnull和swift中的non-optional不同。即使声明成nonnull、你依然可以让他为空。编译器只会提示警告、依旧会通过编译。
-
null_unspecified不确定是否为空。
和nonnull/nullable一样、有三种声明方法。
-
null_resettable:get:不能返回空, set可以为空
只有一种声明方法
@property (nonatomic, copy, null_resettable) NSString *name;
需要注意的是:如果选用此声明、编译器会提示你自己实现get/set方法去处理nil的情况。
我一般是在懒加载的时候用。你可以把我置nil、但只要你需要我、我就是在的。(控制器的view也是)
-
NS_ASSUME_NONNULL_BEGIN以及NS_ASSUME_NONNULL_END
在此之间的属性、都会被设置为nonnull(非空)
@interface ViewController ()
NS_ASSUME_NONNULL_BEGIN
@property (nonatomic,weak) MyObject * obj;
@property (nonatomic,assign) MyObject * obj2;
@property(nonatomic,null_unspecified) UIButton *btn;
@property(nonatomic,weak) UIButton *btn2;
@property (nonatomic, copy,null_resettable) NSString * name;
NS_ASSUME_NONNULL_END
@end
-
结尾
暂时就能想到这么多关于property的东西。如果有问题、欢迎留言。哪里写的不对更欢迎斧正。
感谢您的阅读、希望我的这篇总结能起到一些帮助。
最后
本文主要是自己的学习与总结。如果文内存在纰漏、万望留言斧正。如果不吝赐教小弟更加感谢。