iOS基础深入补完计划--带你重识Property

灵剑山扉页182.jpg

知识基础不够牢。
一知半解、想当然的用没准将来会出大问题。
借用@我就叫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更多的是为了语义化(详见官方文档)。
WechatIMG201.jpeg
  • 那么、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&&copy声明相关延伸》

  • ※※深拷贝浅拷贝

关于深拷贝和浅拷贝的概念、这是基础中的基础。

  • 浅拷贝
  • 源对象和副本对象是同一对象;
  • 源对象(也就是副本对象)引用计数器+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修饰基本类型。

其实这两个区别本质上就是第一种、因为基本类型通常被分配在栈上、由系统自动释放。而如果对象释放、指针指向没有释放。那么就会出现野指针

WechatIMG214.jpeg

什么时候用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;
}

规律就这样~没啥必要再总结下划线和大小写了吧。
效果长这样:


WechatIMG215.jpeg
  • nonnull:不能为空

和nullable相对、用法相同。

但有一点需要注意。

oc中的nonnull和swift中的non-optional不同。即使声明成nonnull、你依然可以让他为空。编译器只会提示警告、依旧会通过编译。


屏幕快照 2017-12-11 下午5.28.55.png
  • 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的东西。如果有问题、欢迎留言。哪里写的不对更欢迎斧正。

感谢您的阅读、希望我的这篇总结能起到一些帮助。


最后

本文主要是自己的学习与总结。如果文内存在纰漏、万望留言斧正。如果不吝赐教小弟更加感谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,373评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,732评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,163评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,700评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,036评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,425评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,737评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,421评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,141评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,398评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,908评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,276评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,907评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,018评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,772评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,448评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,325评论 2 261

推荐阅读更多精彩内容