美文网首页
OC基础-@property 属性(七)

OC基础-@property 属性(七)

作者: 浮华_du | 来源:发表于2021-08-31 15:25 被阅读0次

    @property 的本质
    本质 @property = ivar + getter + setter;(成员变量 + getter方法 + setter方法)。
    在编译期自动生成 getter、setter,还自动向类中添加适当类型的实例变量,也可以用 @synthesize 语法来指定实例变量的名字

    1.@ property预编译指令的作用是自动-- 声明-- 属性的setter getter方法(在封装一节中).

    @property + 数据类型 + 名称(不要下划线);

    #import <Foundation/Foundation.h>
    @interface Person : NSObject{
        NSString *_name;
        int _age;
    }
    @property NSString *name;
    @property int age;
    
    @end
    
    @implementation Person
    @synthesize name;
    @synthesize age;
    @end
    
    
    int main(int argc, const char * argv[])
    {
        Person *p = [[Person alloc]init];
        p.age = 11;
        p.name = @"张三";
        NSLog(@"姓名:%@===年龄:%d",p.name,p.age);
     return 0;
    }
    
    //姓名:张三===年龄:11
    

    2.@ synthesize 表示自动生成getter setter方法的实现;(自动生成私有属性)

    @ synthesize + property名称

    相当于自动生成了

    @implementation Person{
    //自动生成私有属性
        NSString *name;
        int age;
    }
    
    
    - (void)setName:(NSString *)name{
        self->name = name;
    }
    - (NSString *)name{
        return name;
    }
    
    - (void)setAge:(int)age{
        self->age = age;
    }
    
    - (int)age{
        return age;
    }
    
    @end
    
    • 在实现文件中使用 @synthesize propertyName,编译器先会查找这个属性名的setter方法和getter方法有没有被人为实现,如果已经实现,则不再实现,如果没有,则会帮我们生成一个属性名的setter方法和getter方法。
    • 当在实现文件中使用了 @synthesize propertyName,编译器还会做一件事情,在类成员变量中查找一个名为 _propertyName 的成员变量,如果没有,再继续查找名为 propertyName 的成员变量,如果这两个都没有,编译器会自动为我们生成一个私有的名为 _propertyName 的成员变量。注意,系统自动创建的都是私有的。
    @synthesize propertyName = varName
    • 当在实现文件中这样写 @synthesize propertyName = 已经存在的属性名varName;时,这样@synthesize就不会再生成私有属性, setter 和 getter 方法所对应的是一个名为 varName 的成员变量,修改和读取的是 varName 成员变量的值。


      image.png

      相当于

    @implementation Person
    
    
    - (void)setName:(NSString *)name{
        _name = name;
    }
    - (NSString *)name{
        return _name;
    }
    
    - (void)setAge:(int)age{
        _age = age;
    }
    
    - (int)age{
        return _age;
    }
    
    @end
    
    • 当我们在实现文件中不写 @synthesize propertyName 时,在Xcode 4.5之前的版本不会帮我们自动实现 setter 和 getter 方法,系统当然也不再会为我们生成对应的成员变量。但是在Xcode 4.5之后可以不用写@synthesize了。
    @interface Person : NSObject
    @property NSString *name;
    @property int age;
    -(void)test;
    @end
    
    @implementation Person
    @end
    
    • 当我们既定义了 @synthesize,又在实现文件中人为重写 setter 和 getter 方法时,那么 @synthesize 将不再工作,也就不会为我们创建没有定义的 _propertyName 成员变量了,这时候如果在 setter 和 getter 方法中调用 _propertyName 将会发生编译错误
      @ property float height,weight; //同类型批量声明
      @ synthesize height,weight;//不同类型批量实现

    3.在Xcode 4.5之后的@property

    • 自动生成私有属性;
    • 生成getter+setter声明;
    • 自动生成getter+setter的实现;

    @synthesize 和 @dynamic
    @property 有两个对应的词 @synthesize、 @dynamic。如果 @synthesize 和@dynamic 都没写,那么默认的就是 @syntheszie var = _var;

    • @synthesize 语义是如果没有手动实现 setter方法 和 getter方法,那么编译器会主动添加。
    • @dynamic 告诉编译器 setter方法 和 getter方法 用户自己实现,不自动生成。假如一个属性被声明为 @dynamic var,然后你没有实现 @setter方法和 @getter 方法,编译时候没有问题,但程序运行到 instance.var = someVar,由于缺 setter 方法 会导致程序崩溃,或者运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。

    4.@property 的4种参数

    • 原子性(多线程管理):atomic 、 nonatomic
    • setter语意:assign 、retain / copy
    • 读写属性:readwrite 、readonly
    • 强弱引用:strong 、 weak
    原子性(多线程管理):atomic 、 nonatomic
    • atomic 安全 效率低
      默认属性,访问方法都为原子型事务访问。锁被加到所属对象实例级,性能低。原子性就是说一个操作不可以中途被 cpu 暂停然后调度, 即不能被中断, 要不就执行完, 要不就不执行. 如果一个操作是原子性的,那么在多线程环境下, 就不会出现变量被修改等奇怪的问题。原子操作就是不可再分的操作,在多线程程序中原子操作是一个非常重要的概念,它常常用来实现一些同步机制,同时也是一些常见的多线程 Bug 的源头。当然,原子性的变量在执行效率上要低些。
    • nonatomic 不安全 效率低
      非原子性访问。不加同步,尽量避免多线程抢夺同一块资源。是直接从内存中取数值,因为它是从内存中取得数据,它并没有一个加锁的保护来用于cpu中的寄存器计算Value,它只是单纯的从内存地址中,当前的内存存储的数据结果来进行使用。 多线程并发访问会提高性能,但无法保证数据同步。尽量避免多线程抢夺同一块资源,否则尽量将加锁资源抢夺的业务逻辑交给服务器处理,减少移动客户端的压力。
      当有多个线程需要访问到同一个数据时,OC中,我们可以使用 @synchronized (变量)来对该变量进行加锁(加锁的目的常常是为了同步或保证原子操作)。
    setter语意:assign 、retain / copy
    • assign 生成的setter方法的实现就是直接赋值 (属性类型是非OC对象)

    • retain 生成的setter方法的实现就是标准的MRC内存管理代码,先判断新旧对象是否为同一个对象,如果不是release 旧值,retain 新值;不会在delloc中生成release代码,需要手动释放. (属性类型是OC对象)

    • copy release 旧值,copy 新值。希望获得源对象的副本而不改变源对象内容时(一般用于 NSString ,block )

    读写属性:readwrite 、readonly
    • readwrite setter getter同时生成(默认)
    • readonly 只读, 只会生成 getter 方法
    强指针(strong)、弱指针(weak)
    • 强指针(strong)
      strong 系统一般不会自动释放,在 oc 中,对象默认为强指针。作用域销毁时销毁引用。在实际开放中一般属性对象一般 strong 来修饰(NSArray,NSDictionary),在使用懒加载定义控件的时候,一般也用strong。
    • 弱指针(weak)
      weak 所引用对象的计数器不会加一,当对象被释放时指针会被自动赋值为 nil,系统会立刻释放对象。
    • __unsafe_unretained 弱引用
      当对象被释放时指针不会被自动赋值为 ni

    在ARC时属性的修饰符是可以用 assign 的(相当于 __unsafe_unretained)
    在ARC时属性的修饰符是可以用 retain 的 (相当于 __strong)
    假定有N个指针指向同一个对象,如果至少有一个是强引用,这个对象只要还在作用域内就不会被释放。相反,如果这N个指针都是弱引用,这个对象马上就被释放

    在使用 sb 或者 xib 给控件拖线的时候,为什么拖出来的先属性都是用 weak 修饰呢?
    由于在向 xib 或者 sb 里面添加控件的时候,添加的子视图是添加到了跟视图 View 上面,而 控制器 Controller 对其根视图 View 默认是强引用的,当我们的子控件添加到 view 上面的时候,self.view addSubView: 这个方法会对添加的控件进行强引用,如果在用 strong 对添加的子控件进行修饰的话,相当于有两条强指针对子控件进行强引用, 为了避免这种情况,所以用 weak 修饰。
    注意:
    (1)addSubView 默认对其 subView 进行了强引用
    (2)在纯手码实现界面布局时,如果通过懒加载处理界面控件,需要使用strong强指针

    ARC管理内存是用 assign 还是用 weak ?
    assign : 如果由于某些原因代理对象被释放了,代理指针就变成了野指针。
    weak : 如果由于某些原因代理对象被释放了,代理指针就变成了空指针,更安全(weak 不能修饰基本数据类型,只能修饰对象)。

    5.weak修饰符

    • weak的作用:
      weak 关键字的作用弱引用,所引用对象的计数器不会加一,并在引用对象被释放的时候自动被设置为 nil,大大避免了野指针访问坏内存引起崩溃的情况,另外 weak 还可以用于解决循环引用。

    • 使用场景:
      用于一些对象相互引用的时候,避免出现强强引用,对象不能被释放,出现内存泄露的问题。

    • 实现原理:
      runtime 维护了一个 weak 表,用于存储指向某个对象的所有 weak 指针。weak 表其实是一个hash(哈希)表,Key 是所指对象的地址,Value 是 weak 指针的地址(这个地址的值是所指对象的地址)数组。(备注:strong 是通过 runtime 维护的一个自动计数表结构)
      weak 的实现原理可概括三步:

      (1). 初始化时:runtime 会调用 objc_initWeak 函数,初始化一个新的 weak 指针指向对象的地址。
      (2). 添加引用时:objc_initWeak 函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
      (3). 释放时,调用 clearDeallocating 函数。clearDeallocating 函数首先根据对象地址获取所有 weak 指针地址的数组,然后遍历这个数组把其中的数据设为 nil ,最后把这个 entry 从 weak 表中删除,最后清理对象的记录。

    6.@protocol 和 category 中如何使用 @property

    • 在 protocol 中使用 property 只会生成 setter 和 getter 方法声明,使用属性的目的,是希望遵守该协议的对象能实现该属性。
    • category 使用 @property 也只会生成 setter 和 getter 方法声明,如果真的要给 category 增加属性实现,需要借助运行时的两个函数: objc_setAssociatedObject、objc_getAssociatedObject。

    7.NSString 为什么用 copy 而不用 retain

    当.h用@property (nonatomic, retain) NSString *name;时,_name = [name retain];相当于[name retain] 和 _name = name;,而这两句话相当于是先把原来的作引用计数+1,再把指针付给 _name ,实际上指向的是一块内存,这样会导致原来的内容改变,_name 也会改变,而实际中我们一般不希望 _name 改变,所以我们不用retain。

    - (void)setName:(NSString *)name {
        if (_name != name) {
            [_name release];
            
            _name = [name retain];
            //_name = [name retain];相当于下边两句,而这两句话相当于是先把原来的作引用计数+1,再把指针付给_name,实际上指向的是一块内存,这样会导致原来的内容改变,_name也会改变,而实际中我们一般不希望_name改变,所以我们不用retain
    //        [name retain];
    //        _name = name;
        }
    }
    
    - (void)dealloc {
    //    [_name release];
    //    _name = nil;
        self.name = nil;
        [super dealloc];
    }
    

    当.h用@property (nonatomic, copy) NSString *name;时,当传入的值为可变对象时,调用 _name = [name copy]; copy 会创建一个新的对象赋值给 _name,所以 _name 和 name 是两块无关的内容,改变 name 不会影响 _name

    - (void)setName:(NSString *)name {
        if (_name != name) {
            [_name release];
            // 当传入的值为可变对象时,copy会创建一个新的对象赋值给_name,所以_name和name是两块无关的内容,改变name不会影响_name
            _name = [name copy];
        }
    }
    
    - (void)dealloc {
    //    [_name release];
    //    _name = nil;
        self.name = nil;
        [super dealloc];
    }
    
    
    

    https://juejin.cn/post/6844903824436494343

    使用访问器方法让内存管理更轻松

    如果类中有对象类型的属性,则你必须确保在使用过程中该属性赋值的对象不被释放。因此,在赋值对象时,你必须持有对象的所有权,让其引用计数加 1。还必须要把当前持有的旧对象的引用计数减 1。
    例如下方代码: Counter 类中定义了一个NSNumber对象属性

    @interface Counter : NSObject
    //@property会自动生成setter和getter方法的声明
    @property (nonatomic, retain) NSNumber *count;
    @end;
    

    在这个类的实现中,getter方法只需要返回合成的实例变量,所以不用进行retain和release。

    setter方法中,需要先对新对象进行retain,再对旧对象进行release,然后再进行赋值操作。(在Objective-C中允许给nil发送消息,且这样会直接返回不做任何事情。所以就算是第一次调用,_count 变量为nil,对其进行 release也没事。)

    最好的做法如下:先判断新旧对象是否是同一个对象,如果是的话就什么都不做;如果新旧对象不是同一个对象,则对旧对象进行release,对新对象进行retain并赋值给合成的实例变量。

    - (void)setCount:(NSNumber *)newCount {
        if (_count != newCount) {
            [_count release];
            _count = [newCount retain];
        }
    }
    
    @interface Person : NSObject
    
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, assign) int age;
    
    @end
    
    @implementation Person
    
    - (void)setName:(NSString *)name {
        if (_name != name) {
    //先判断新旧对象是否是同一个对象,如果是的话就什么都不做;如果新旧对象不是同一个对象,则对旧对象进行release,对新对象进行retain并赋值给合成的实例变量。
            [_name release];
            _name = [name retain];
        }
    }
    
    - (void)setAge:(int)age {
        _age = age;
    }
    
    - (void)dealloc {
        self.name = nil;
        [super dealloc];
    }
    
    @end
    

    相关文章

      网友评论

          本文标题:OC基础-@property 属性(七)

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