美文网首页iOS模块详解ios程序员
iOS基础深入补完计划--带你重识Property

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

作者: kirito_song | 来源:发表于2017-12-04 13:24 被阅读291次
    灵剑山扉页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的东西。如果有问题、欢迎留言。哪里写的不对更欢迎斧正。

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

    相关文章

      网友评论

      本文标题:iOS基础深入补完计划--带你重识Property

      本文链接:https://www.haomeiwen.com/subject/kcxxixtx.html