美文网首页
@Property那些事

@Property那些事

作者: 修_远 | 来源:发表于2017-09-08 14:18 被阅读42次

    1. @property 和@synthesizer

    在objective-c 1.0中,我们为interface同时声明了属性和底层实例变量,那时,属性是oc语言的一个新的机制,并且要求你必须声明与之对应的实例变量,例如:

    @interface MyViewController :UIViewController
    {
        UIButton *myButton;
    }
    @property (nonatomic, retain) UIButton *myButton;
    @end
    

    在objective-c 2.0中,@property它将自动创建一个以下划线开头的实例变量。因此,在这个版本中,我们不再为interface声明实例变量。变成我们常见的形式

    @interface MyViewController :UIViewController
    @property (nonatomic, retain) UIButton *myButton;
    @end
    

    在MyViewController.m文件中,编译器也会自动的生成一个实例变量_myButton。那么在.m文件中可以直接的使用_myButton实例变量,也可以通过属性self.myButton.都是一样的。

    注意这里的self.myButton其实是调用的myButton属性的getter/setter方法。这与C++中点的使用是有区别的,C++中的点可以直接访问成员变量(也就是实例变量)。

    例如在oc的.h文件中有如下代码

    @interface MyViewController :UIViewController
    {
        NSString *name;
    }
    

    .m文件中,self.name 这样的表达式是错误的。xcode会提示你使用->,改成self->name就可以了。因为oc中点表达式是表示调用方法,而上面的代码中没有name这个方法。所以在oc中点表达式其实就是调用对象的setter和getter方法的一种快捷方式。

    你可能还见过这种写法

    #import "ViewController.h"
    
    @interface ViewController ()
    @property (nonatomic, strong) UIButton *myButton;
    @end
    
    @implementation ViewController
    @synthesize myButton;
    

    @synthesize 语句只能被用在 @implementation 代码段中,@synthesize的作用就是让编译器为你自动生成setter与getter方法,@synthesize 还有一个作用,可以指定与属性对应的实例变量,例如@synthesize myButton = xxx;那么self.myButton其实是操作的实例变量xxx,而不是_myButton了。

    如果.m文件中写了@synthesize myButton;那么生成的实例变量就是myButton;如果没写@synthesize myButton;那么生成的实例变量就是_myButton。所以跟以前的用法还是有点细微的区别。

    2. 类别中的属性property

    类与类别中添加的属性要区分开来,因为类别中只能添加方法,不能添加实例变量。经常会在ios的代码中看到在类别中添加属性,这种情况下,是不会自动生成实例变量的。比如在:UINavigationController.h文件中会对UIViewController类进行扩展

    @interface UIViewController (UINavigationControllerItem)
    @property(nonatomic,readonly,retain) UINavigationItem *navigationItem;
    @property(nonatomic) BOOL hidesBottomBarWhenPushed;
    @property(nonatomic,readonly,retain) UINavigationController *navigationController;
    @end
    

    这里添加的属性,不会自动生成实例变量,这里添加的属性其实是添加的getter与setter方法。注意一点,匿名类别(匿名扩展)是可以添加实例变量的,非匿名类别是不能添加实例变量的,只能添加方法,或者属性(其实也是方法),常用的扩展是在.m文件中声明私有属性和方法。 Category理论上不能添加变量,但是可以使用rRuntime机制来弥补这种不足。

    #import
    static const void * externVariableKey =&externVariableKey;
    @implementation NSObject (Category)
    @dynamic variable;
    - (id) variable
    {
           return objc_getAssociatedObject(self, externVariableKey);
    }
    - (void)setVariable:(id) variable
    {
        objc_setAssociatedObject(self, externVariableKey, variable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    

    3. @private、@protect、@public

    • @protected是受保护的,只能在本类及其子类中访问,在{}声明的变量默认是@protect
    • @private是私有的,只能在本类访问
    • @public公开的,可以被在任何地方访问。

    在头文件.h中:

    @interface ViewController : UIViewController
    {
    // 成员变量
            @public
                NSString* publicString;
    
            @protected
                NSString* protectedString;
    
            @private
                NSString* privateString;
    }
    //属性变量
    @property (nonatomic,strong) NSArray *propertyString;
    @end
    
    • 成员变量用于类内部,无需与外界接触的变量。
    • 根据成员变量的私有性,为了方便访问,所以就有了属性变量。属性变量是用于与其他对象交互的变量。(属性变量的好处就是允许让其他对象访问到该变量。当然,你可以设置只读或者可写等,设置方法也可自定义。)

    编程习惯:
    1.如果只是单纯的private变量,最好声明在implementation里.
    2.如果是类的public属性,就用property写在.h文件里
    3.如果自己内部需要setter和getter来实现一些东西,就在.m文件的类目里用property来声明

    • .h中的interface的大括号{}之间的实例(成员)变量,.m中可以直接使用;
    • .h中的property(属性)变量,.m中需要使用self.propertyVariable的方式使用propertyVariable变量

    4. 成员变量和成员属性的关系

    • 属性对成员变量扩充了存取方法.
    • 属性默认会生成带下划线的成员变量.
    • 但只声明了变量,是不会有属性的.

    5. 细说 @property

    • 原子性--- nonatomic 特质
    • 读/写权限---readwrite(读写)、readonly (只读)
    • 内存管理语义---assign、strong、 weak、unsafe_unretained、copy
    • 方法名---getter=<name> 、setter=<name>
      eg:@property (nonatomic, getter=isAllow) BOOL allow;

    使用atomic一定是线程安全的吗?

    不是的

    • atomic原子操作,系统会为setter方法加锁。(但是也不安全,需要更深层次的锁定)
    • nonatomic不会为setter方法加锁。
    • atomic:非线程安全,还是需要消耗大量系统资源来为属性加锁
    • nonatomic:非线程安全,适合内存较小的移动设备

    引用计数内存管理的思考方式

    • 自己生成的对象,自己所持有。
    • 非自己生成的对象,自己也能持有。
    • 自己持有的对象不再需要时释放。
    • 非自己持有的对象无法释放

    strong retain

    在ARC下,实例变量本身是强引用,当ARC将传入值赋给实例变量时,它会保留传入的值,释放现有实例变量的值。非ARC下,setter方法会保留传入的值和释放现有实例变量的值。strong,retain是同义词。

    copy

    如果是不可变的值,行为与strong相同。
    如果是可变的值,会将一个副本赋给实例变量。当一个不可变类有一个可变的子类时(NSString NSMutableString,NSArray NSMutableArray)可以防止setter 方法传递一个可变的子类的对象。会导致我们在不知情的情况下修改对象的值。
    weak |

    weak

    • 带__weak 修饰符的变量不持有对象,所以在超出其变量作用域时,对象被释放。
      可以这样理解,__weak修饰的变量没有对象的所有权,不增加对象的引用计数。

    什么情况使用 weak 关键字?

    • 在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,
      比如: delegate 代理属性

    • 自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,
      自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。

    assign

    • 适用于值类型。而weak必须用于对象。
    • assign 和 weak 的区别

    assign适用于基本数据类型,weak是适用于NSObject对象,并且是一个弱引用。

    assign其实也可以用来修饰对象,那么我们为什么不用它呢?因为被assign修饰的对象在释放之后,指针的地址还是存在的,也就是说指针并
    没有被置为nil。如果在后续的内存分配中,刚好分到了这块地址,程序就会崩溃掉。

    而weak修饰的对象在释放之后,指针地址会被置为nil。所以现在一般弱引用就是用weak。

    synthesize和@dynamic

    @property有两个对应的词,一个是 @synthesize,一个是 @dynamic。

    如果 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var;

    @synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。

    @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。

    假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。

    @synthesize 合成实例变量的规则,有以下几点:

    1. 如果指定了成员变量的名称,会生成一个指定的名称的成员变量,
    2. 如果这个成员已经存在了就不再生成了.
    3. 如果是 @synthesize xjy; 还会生成一个名称为xjy的成员变量,也就是说:如果没有指定成员变量的名称会自动生成一个属性同名的成员变量,
    4. 如果是 @synthesize xjy = _xjy; 就不会生成成员变量了.

    什么时候不会自动合成

    • 同时重写了 setter 和 getter 时
    • 重写了只读属性的 getter 时
    • 使用了 @dynamic 时
    • 在@protocol中定义的所有属性
    • 在category中定义的所有属性
    • 重载的属性 (子类中重载父类的属性)
    @interface MyLabel ()
    @property (nonatomic, strong) NSString *text;
    @end
    
    @implementation MyLabel
    //必须用synthesize 手动指定ivar的名字就可以避免重载
    @synthesize text = _myText;
    @end
    

    6. 示例

    .h文件中添加这三个属性

    @interface ClassA : NSObject
    {
        int _num1;
        
    @private int _num2;
        
    @public  int _num3;
    }
    @end
    

    然后在main函数中使用这个实例对象的时候

    ClassA属性.jpeg

    发现只有_num3是可以访问的,其他两个都不能访问,_num2是手动设置为private属性,很显然是不能访问,_num1什么也没有设置,然而为什么也不能访问

    我们来尝试给_num1赋值,看看是什么个情况

    ClassANum1访问报错.jpeg
    系统给出了很详细的解释,说_num1protected,也就是说这里声明的属性默认是protected
    • 手动实现_num2_num3的 setter 和 getter 方法

    .h文件

    - (void)setNum2:(int)num2;
    - (int)num2;
    
    - (void)setNum3:(int)num3;
    - (int)num3;
    

    .m文件

    - (void)setNum3:(int)num3
    {
        _num3 = num3;
    }
    
    - (int)num3
    {
        return _num3;
    }
    
    - (void)setNum2:(int)num2
    {
        _num2 = num2;
    }
    
    - (int)num2
    {
        return _num2;
    }
    

    这样就可以实现对这两个属性的访问了

    ca.num2 = 2;
    ca.num3 = 2;
    

    很显然对每个属性都去实现 setter 和 getter 方法是很费时的,这个时候@Property就出来帮我们解决这个事了

    .h中添加 @property int num1; 这么一句话之后,就可以实现对_num1的访问了,因为@property已经自动为属性num1声明了 setter 和 getter 方法,但是为什么就直接可以访问到_num1了?

    我们来看这一句@synthesize num1 = _num1;

    ClassANum1Property声明.jpeg

    看到了系统默认生成的 setter 和 getter 方法,然后不管点击哪一个进入都会指向这一句话,因为我们没有在.m文件中手动实现num1的 setter 和 getter 方法,所以就会指向这里,可以发现@synthesize的作用就是告诉你如果没有手动实现setter和getter方法,编译器会自动帮你生成

    ClassANum1Synthsize.jpeg

    @property 生成的变量并不是 public 类型的,因为生成了 getter 和 setter 方法,在类外部访问的是该变量的 setter 和 getter 方法。

    7. 细说property的内存管理

    转载自

    atomic(原子性) vs nonatomic(非原子性)【线程安全】

    这主要是针对与多线程编程环境下来说的。怎么说那,假设我们现在多线程环境编程中,如果同时又两个或者两个以上的线程同时对一个属性就行赋值,这个时候为了保证属性调用前后的一致性,我们通常要做些多余的事,这就是传说中的线程安全。也就是说线程安全就是为了保证在多线程环境中,有且仅有一个线程能对当前属性进行set和get操作,其他线程必须等待其完成操作之后再进行操作。

    回到咱们主题上,Objective-C中原子性就是为了保证线程安全而存在。如果当前的某一属性的属性为原子性,那么任何一个线程对其记性set和get方法时都会对当前的属性进行加锁和解锁操作(Objective-C中保证线程安全的一种方法)。从而保证其在多线程编程环境的线程安全。通常情况下,我们不会涉及过多的线程安全,并且加锁和解锁操作也会造成相当多的资源开销,所以我们一般都将属性设置为非原子性。但是苹果公司为了安全考虑出发,默认情况下,这个属性是非原子性

    readwrite(读写) vs readonly (只读) 【访问控制】

    这两个属性相对还是比较好理解的。这属性默认情况下是读写的,这就是为什么我们可以对实例变量进行取值和赋值的操作,其实质就是有set和get方法。通过这个说明相信聪明的你已经猜到只读的含义和实质了。只读的含义大家用心领悟一下,在这里我我说一下。其实就是就是只写了get方法,没有提供get方法。到这里不知道大家有没有想过一个问题。为啥没有只写方法,想了半天,突然发现,只写有不能读有啥用啊

    另外如果你不喜欢编译器默认的set和get方法,你可以自定义set和get方法,声明如下

    @property (getter=isRunning, readonly) BOOL running;

    补充一句,在Swift中都是 isRunning 这种形式

    为了操作的方便,苹果属性将读写属性设置为实例变量的默认属性

    strong (强引用) vs weak(弱引用) vs assign(赋值) vs copy(复制)

    每一遍编程语言都无法绕开的深渊--内存管理。特别是在移动设备上这种内存资源相对短缺的设备上。当然这个也是有相对来说的,比如现在Android手机最大的运行内存已经可达到4G,呵呵哒...... 但是就目前iOS设备来说,内存资源还是比较稀缺的(貌似iPhone 7要扩容了)。

    在大多数的面向对象的语言中都是通过垃圾回收,但是苹果公司开发一套我认为相当流b的机制--对象拥有关系(object ownership)。专有的名词没找到,我就强行秀一波英语。大概就是说,当我们对一个对象就行操作时,我们必须确定他的存在,就是说我们必须拥有它。如果当前对象没有拥有者,那么操作系统(不是编译器)会在一个合适的时间(目前我也不确定,有种说法是两个runloop切换期间)将其销毁并释放掉其所占内存。这个机制的实现依赖于ARC机制。至于ARC机制在这里我不多讲,有一点跟大家说明,当对象拥有者增加时,当前对象的引用计数会+1,如果引用计数为0时,就是没有对象拥有,那么这个对象就可能被释放。

    strong属性就是表示我拥有当前对象,并且当前对象的引用计数+1.strong有一个好处就是说,我能够确认当前对象的存在,因为只要我不消失,当前对象就不会被销毁,即不会消失。这就保证了我能够在需要的时候随时访问当前这个对象。

    引用计数模型.png

    通过上面的介绍大家应该能推出来如果要释放一个对象,必须是他的引用计数(拥有者的数量)为0.好大家看下面这张图

    循环引用计数模型.jpg

    简单说明一下,对象A强应用对象B,同时对象B也强引用对象A,这时候大家想象一下,当我程序执行结束后,操作系统会怎样释放这两个对象那,因为程序执行过程中,两个对象的引用计数都不可能为0,因而都不会被释放,但是当程序执行结束,会怎样?没错,这个就是经典的循环引用计数问题,而他的直接后果就是鼎鼎大名的“内存泄漏”。当程序执行完成之后,操作系统不会释放掉任何一方,从而导致两者一直留在内存中,导致我们的内存越来越下。

    很明显这个不是我们想要的,但是以上的情况和多时候我们又无法避免。为了解决这个问题,另外一个属性被发明出来,他就说weak,同样咱们看一下模态图

    弱引用解决循环引用模型.jpg

    简单说明,对象A此时仍然强引用对象B,而对象B弱引用对象A。细心同学发现此时对象B拥有对象A为虚线,并且对象A的引用计数并没有增加。或许你会想是不是画错了。我可以告诉NO,这这你weak的精髓。简单明了,当B释放的时候,我的两个对象都会被释放掉,并且当前对象A的指针会被置为nil。弱引用的实质就是和strong一样拥有当前对象,能够对象当前对象进行操作,但是不使其引用计数增加。这样就完美的解决了循环引用问题。给他家提个醒,不要乱用weak,可能会导致奇怪的bug。代理协议的实例对象通常设置为weak属性

    至于copy这个属性可能是有人觉得比较茫然。如果一个属性被设置为copy属性时,对象记性读写操作,他获得的是当前的对象的一份copy,而不是简单对其进行应用。并且源对象的引用计数不会增加。通常经常下copy属性主要用于源对象可能发生改变,而不像当前对象受其影响。不如说我们将一个NSMutableString类型的String赋值给NSString类型的对象,这个时候我们为了防止修改NSMutableString对象对当前对象影响。我们会考虑将其设置为copy。通常情况string对象和block对象会被设置为copy属性。

    总结一下,以上三个属性都是针对于对象来说的,但是大家都知道Objective-C不是一门新的编程语言,它只是在C语言的基础上加上了一层面向对象的特性。那么问题来了,C语言中有好多基础类型,声明这些属性时我们怎样设置的他的属性?别急,不还有一个吗?assign主要用来修饰Objective-C中基础属性。Objective-C支持64位以后全面更新了基础属性的定义。其实就是做了一些兼容64的修改,例如int -> NSInteger等。

    当然还有一些其他的属性,比如unsafe_unretained,这个跟weak有很多相似,当时不同的是他不会将当前的对象指针置为nil。如果你需要使用他,请确定当前环境不支持weak,因为从他名字就知道这货不是很好。哈哈哈。

    当然以上的所有都是针对于ARC机制下来说,对于老的开发程序员(就是MRC下开发的前辈们),还有retain等属性。在这里我就不再展开来说。我表示我赶上了MRC的尾巴,但是当时没有研究这个东东,感觉太坑了。就直接跳进了ARC的怀抱。现在想想,还是真是幸福啊。哈哈

    相关文章

      网友评论

          本文标题:@Property那些事

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