美文网首页
iOS property相关的知识

iOS property相关的知识

作者: yyggzc521 | 来源:发表于2018-12-15 14:34 被阅读0次

    @property 本质

    @property = 实例变量 + get方法 + set方法。
    get方法用来获取变量的值,set方法用来设置变量的值。
    使用@property生成的实例变量、get方法、set方法的命名有严格的规范

    下面均以 @property (nonatomic, copy) NSString *name 为例

    使用属性生成的实例变量、get方法、set方法是在编译过程中自动生成的。set方法也可以称为setter方法,同理,get方法也被称为getter方法。
    这些方法、变量是通过自动合成(autosynthesize)的方式生成并添加到类中。一个类经过编译后,会生成变量列表ivar_list,方法列表method_list,每添加一个属性,在变量列表ivar_list会添加对应的变量,如_name;方法列表method_list中会添加对应的setter方法和getter方法

    setter方法

    属性name生成的setter方法如下

    - (void)setName:(NSString *)name;
    

    该命名方法是固定的,是约定成束的。如果属性名是firstName,那么setter方法是:

    - (void)setFirstName:(NSString *)firstName;
    

    项目中,很多时候会有重写setter方法的需求,只要重写对应的方法即可。比如说重写name属性的setter方法

    - (void)setName:(NSString *)name {
    
        NSLog(@"rewrite setter");
        _name = name;
    }
    
    getter方法

    编译器自动生成的getter方法是

    - (NSString *)name;
    

    getter方法的命名也是固定的。如果属性名是firstName,那么getter方法是:

    - (NSString *)firstName;
    

    重写getter方法:

    - (NSString *)name {
    
        NSLog(@"rewrite getter");
        return _name;
    }
    

    注意:如果我们定义了name属性,并且按照上面所述,重写了getter方法和setter方法,Xcode会提示如下的错误:

    Use of undeclared identifier '_name'; did you mean 'name'?
    

    为什么会出现这个❌错误?如何解决?后面我们会讲到


    实例变量

    实例变量的名称就是_name。实例变量的命名也是有固定格式的,下划线+属性名。
    如果属性是@property firstName,那么生成的实例变量就是_firstName。这也是为何我们在setter方法和getter方法,以及其他的方法中可以使用_name的原因

    动态合成

    和自动合成相对应的就是非自动合成,非自动合成又称为动态合成。定义一个属性,系统默认是自动合成的,会生成getter方法和setter方法,这也是为何我们可以直接使用self.属性名的原因。实际上,自动合成对应的代码是

    @synthesize name = _name;
    

    这行代码是编译器自动生成的,如果我们想要动态合成,需要自己写如下代码

    @dynamic name;
    

    这行代码就是告诉编译器,name属性的变量名、getter方法、setter方法由开发者自己来添加,编译器无需处理

    那么这样写和自动合成有什么区别呢?来看下面的代码:

    Student *stu = [[Student alloc] init];
    stu.name = @"male";
    

    编译不会有任何问题。运行也没问题。但是当代码执行到这一行的时候,程序崩溃了,崩溃信息是

    [Student setName:]: unrecognized selector sent to instance 0x60000217f1a0
    

    即:Student没有setName方法,没有属性name的setter方法。这就是动态合成和自动合成的区别。
    动态合成,需要开发者自己来写属性的setter方法和getter方法。还需要手动定义_name变量

    @interface Student : NSObject
    {
        NSString *_name;
    }
    
    @property (nonatomic, copy) NSString *name;
    
    - (void)setName:(NSString *)name {
    
        _name = name;
    }
    @end
    

    现在再编译,运行,没有错误和崩溃。

    接上文我们讲到,重写了name的set、get方法后,会报下面的错误

    Use of undeclared identifier '_name'; did you mean 'name'?
    

    我们没有声明@dynamic,那默认就是@autosynthesize,为何提示没有没有_name变量呢?奇怪的是,倘若我们把getter方法,或者setter方法注释掉,gettter、setter方法只留下一个,不会有错误,为什么呢?

    这是因为编译器做了些处理。对于一个可读写的属性来说,当我们重写了其sett、get方法时,编译器会认为开发者想手动管理@property,此时会将@property作为@dynamic来处理,因此也就不会自动生成变量。

    解决方法,显示的将属性和一个变量绑定

    @synthesize name = _name;
    

    这样就没问题了。如果一个属性是只读的,重写了其getter方法时,编译器也会认为该属性是@dynamic

    @property修饰符

    修饰符有四种:

    1. 原子性有nonatomic、atomic两个值,默认是atomic的。如果属性是atomic的,那么在访问其getter和setter方法之前,会判断是否可以访问,这里系统使用的是自旋锁。由于使用atomic并不能绝对保证线程安全,且会耗费一些性能,因此通常情况下都使用nonatomic。
    2. 读写权限有两个取值,readwrite和readonly。默认是readwrite的。如果某个属性不想让其他人来写,那么可以设置成readonly。
    3. 内存管理。内存管理的取值有assign、strong、weak、copy、unsafe_unretained。
    4. set、get方法名。如果不想使用自动合成所生成的setter、getter方法,声明属性时甚至可以指定方法名。比如指定getter方法名:
    @property (nonatomic, assign, getter=isPass) BOOL pass;
    

    属性pass的getter方法就是

    - (BOOL)isPass;
    

    默认修饰符

    • 如果是基本数据类型;atomic,readwrite,assign
    • 如果是Objective-C对象;atomic,readwrite,strong

    声明属性时,通常使用nonatomic修饰符,原因就是因为atomic并不能保证绝对的线程安全。atomic 只会保证 setter/getter 方法是原子操作的;weak只能用来修饰对象,不能用来修饰基本数据类型,否则会发生编译错误,weak修饰的对象,当对象释放之后,即引用计数为0时,对象会置为nil;assign修饰的对象,当对象释放之后,即引用计数为0时,对象会变为野指针,不知道指向哪,再向该对象发消息,非常容易崩溃。

    为何基本数据类型可以用assign来修饰呢? 这就涉及到堆和栈的问题

    • 栈的空间小,约1M左右,是一段连续的结构。栈中的空间,开发者不需要管
    • 堆的空间大,通常是不连续的结构,使用链表结构。需要开发者自己去释放。OC中的对象,如 UIButton 、UILabel ,[[UIButton alloc] init] 出来的,都是分配在堆空间上。

    因为这些基本数据类型是分配在栈上,栈上空间的分配和回收都是系统来处理的,因此开发者无需关注,也就不会产生野指针的问题。

    栈的线程安全

    堆是多个线程所共有的空间,操作系统在对进程进行初始化的时候,会对堆进行分配; 栈是每个线程所独有的,保存线程的运行状态和局部变量。栈在线程开始的时化,每个线程的栈是互相独立的,因此栈是线程安全的。

    copy、strong、mutableCopy
    通常情况下,不可变对象属性修饰符使用copy,可变对象属性修饰符使用strong。
    可变对象和不可变对象的区别是,不可变对象的值一旦确定就不能再修改。

    - (void)testNotChange {
    
        NSString *str = @"123";
        NSLog(@"str = %p",str);
        str = @"234";
        NSLog(@"after str = %p",str);
    }
    

    NSString是不可变对象。虽然在程序中修改了str的值,但是此处的修改实际上是系统重新分配了空间,定义了字符串,然后str重新指向了一个新的地址。这也是为何修改之后地址不一致的原因:

    2018-12-06 22:02:41.350812+0800 TestClock[884:17969] str = 0x106ec1290
    2018-12-06 22:02:41.350919+0800 TestClock[884:17969] after str = 0x106ec12d0
    

    再来看可变对象的例子:

    - (void)testChangeAble {
    
        NSMutableString *mutStr = [NSMutableString stringWithString:@"abc"];
        NSLog(@"mutStr = %p",mutStr);
        [mutStr appendString:@"def"];
        NSLog(@"after mutStr = %p",mutStr);
    }
    

    NSMutableString是可变对象。程序中改变了mutStr的值,且修改前后mutStr的地址一致:

    2018-12-06 22:10:08.457179+0800 TestClock[1000:21900] mutStr = 0x600002100540
    2018-12-06 22:10:08.457261+0800 TestClock[1000:21900] after mutStr = 0x600002100540
    

    参考资料
    https://juejin.im/post/5c105c7ce51d4562d138086f

    相关文章

      网友评论

          本文标题:iOS property相关的知识

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