iOS-认识@property

作者: Tr2e | 来源:发表于2017-11-17 18:52 被阅读261次

    前言

    关于@property基础的一次总结学习


    属性与实例变量

    当我们写下@property NSObject *foo时,编译器帮我们做了以下几件事(这个过程也被称为“自动合成(autoSynthesize)“)

    • 创建实例变量_foo
    • 声明foo属性的settergetter方法
    • 实现foo属性的settergetter方法

    但是很久之前的GCC编译器时代,声明一个属性,需要分三步书写

    .h
    {
        NSObject *foo;
    }
    @property NSObject *foo
    .m
    @synthesize foo;
    

    创建foo成员变量,@property负责声明setter/getter方法,而@synthesize则负责实现这两个方法

    后来苹果将编译器改为LLVM,我们就不必再为属性对应声明实例变量,编译器自动创建一个下划线开头的_foo实例变量,并且为我们实现@synthesize foo = _foo的属性与实例变量的对应,这就解释了为什么我们重写setter/getter方法时,都是操作的_foo实例变量,而不是foo。当然,如果你愿意,可以不使用系统自动生成的_foo的命名,自己用@synthesize foo = differentFoo去指定一下这个局部变量名字。

    与自动合成相对应的是@dynamic,作用是告诉编译器取消自动执行的上述三个步骤,对应操作与开发者自己实现。最为常见的是程序中实现对NSUserDefault读写的管理类,这个类的属性就不需要编译器帮我们自动合成


    @property中属性的作用

    // 无数次不厌其烦的书写是因为烦也得写
    @property (nonatomic, assign) NSInteger testTag;
    @property (nonatomic, strong) UIButton *btn;
    

    原子性:atomic&nonatomic

    • atomic:原子性,这个属性是默认的,通过在setter中加@synchronized(self)保证数据数据的读写安全,需要注意的是,但它不是线程安全
    • nonatomic:非原子性,就是不加锁

    atomic和nonatomic的区别在于,系统自动生成的getter/setter方法不一样。如果自己写getter/setter,那atomic/nonatomic/retain/assign/copy这些关键字只起提示作用,写不写都一样

    nonatomic

    //@property(nonatomic, retain) UITextField *userName;
    //系统生成的代码如下:
    
    - (UITextField *) userName {
        return userName;
    }
    
    - (void) setUserName:(UITextField *)userName_ {
        [userName_ retain];
        [userName release];
        userName = userName_;
    }
    

    atomic

    //@property(retain) UITextField *userName;
    //系统生成的代码如下:
    
    - (UITextField *) userName {
        UITextField *retval = nil;
        @synchronized(self) {
            retval = [[userName retain] autorelease];
        }
        return retval;
    }
    
    - (void) setUserName:(UITextField *)userName_ {
        @synchronized(self) {
          [userName release];
          userName = [userName_ retain];
        }
    }
    

    atomic通过加锁@synchronized(self)来保障读写安全,并且在getter中引用计数同样会 +1,来向调用者保证这个对象会一直存在。假如不这样做,如有另一个线程调setter可能会出现线程竞态,导致引用计数降到0,原来那个对象就释放掉了

    参考文章:《爆栈热门iOS问题 atomic和nonatomic有什么区别?》


    读写权限:readwrite&readonly

    • readwrite:可读写属性,默认属性,允许编译器@synthesize自动合成
    • readonly:只读属性,不生成setter

    方法名定义:getter=/setter=

    看一下UIButton中的应用,一般都是在Bool类型的属性时使用,方便阅读

    @property(nonatomic,getter=isEnabled) BOOL enabled;                                  // default is YES. if NO, ignores touch events and subclasses may draw differently
    @property(nonatomic,getter=isSelected) BOOL selected;                                // default is NO may be used by some subclasses or by application
    @property(nonatomic,getter=isHighlighted) BOOL highlighted;  
    

    内存管理

    • assign
    • unsafe_unretained
    • strong
    • retain
    • weak
    • copy
    assign

    assign用于值类型,如int、float、double和NSInteger,CGFloat等表示单纯的赋值,简单覆盖原值,没有多余的操作

    unsafe_unretained

    语义同assign一样,区别是适用于对象类型。但是对象销毁后,属性值并不等于nil而是指向了一块野地址,形成野指针。如果访问,则会出现BAD_ACCESS

    strong/retain

    id类型及对象类型的所有权修饰符。strong是在iOS引入ARC的时候引入的关键字,是retain的一个可选的替代。表示实例变量对传入的对象要有所有权关系,即强引用。strong跟retain的意思相同并产生相同的代码,但是语意上strong更能体现对象的关系。作用是在setter中,对新值retain,对旧值release,并返回新值

    weak

    使用范围同strong。区别在于在setter方法中,对传入的对象不进行引用计数加1的操作,即对传入的对象没有所有权。属性所指的对象销毁时,属性也会清空(nil out)

    copy

    会将修饰的结果copy一份,重新保存。所以通常用于修饰NSString/NSArray/NSDictionary的属性保护封装性。因为多态性,父类指针可以指向子类,如果给一个上述不可变属性赋值了他们的子类,那么在子类发生增删时,同样会影响了当前属性的值,违背了不可变的初衷,使程序变的不可控。而mutable类型copy操作后,就会保存当前的值储存,成为不可变的类型(immutable)

    我们简单写个例子,检验一下copy对属性的影响:

    1. 声明两个分别用strongcopy修饰类型为可变数组的属性
    2. 声明两个分别用strongcopy修饰类型为不可变数组的属性
    @property (nonatomic, strong) NSMutableArray *smArr;
    @property (nonatomic, copy) NSMutableArray *cmArr;
    @property (nonatomic, strong) NSArray *strongImArr;
    @property (nonatomic, copy) NSArray *CopyImArr;
    

    接下来分别给他们赋值一个可变数组的局部变量,并在改变这个局部变量前后打印几个属性的信息

    // 初始化一个局部可变数组
        NSArray *arr = @[@"1"];
        NSLog(@"original arr address:%p class:%@",arr,[arr class]);
        NSArray *carr = [arr copy];
        NSLog(@"copy arr address:%p class:%@",carr,[carr class]);
        NSMutableArray *marr = [arr mutableCopy];
        NSLog(@"mutable arr address:%p class:%@",marr,[marr class]);
    
    // 将这个可变数组分别赋值给声明的属性并打印信息
        self.cmArr = marr;
        NSLog(@"copy mutable property address:%p class:%@ property:%@",self.cmArr,[self.cmArr class],self.cmArr);
        self.smArr = marr;
        NSLog(@"strong mutable property address:%p class:%@ property:%@",self.smArr,[self.smArr class],self.smArr);
        
        self.strongImArr = marr;
        NSLog(@"strong immutable property address:%p class:%@ property:%@",self.strongImArr,[self.strongImArr class],self.strongImArr);
        self.CopyImArr = marr;
        NSLog(@"copy immutable property address:%p class:%@ property:%@",self.CopyImArr,[self.CopyImArr class],self.CopyImArr);
     
    // 改变可变数组并打印信息
        [marr addObject:@"2"];
        NSLog(@"copy mutable property address:%p class:%@ property:%@",self.cmArr,[self.cmArr class],self.cmArr);
        NSLog(@"strong mutable property address:%p class:%@ property:%@",self.smArr,[self.smArr class],self.smArr);
        NSLog(@"strong immutable property address:%p class:%@ property:%@",self.strongImArr,[self.strongImArr class],self.strongImArr);
        NSLog(@"copy immutable property address:%p class:%@ property:%@",self.CopyImArr,[self.CopyImArr class],self.CopyImArr);
    

    打印结果

    original arr address:0x600000017f50 class:__NSSingleObjectArrayI
    copy arr address:0x600000017f50 class:__NSSingleObjectArrayI
    mutable arr address:0x60c000250680 class:__NSArrayM
    
    copy mutable property address:0x608000201de0 class:__NSSingleObjectArrayI property:(
        1
    )
    strong mutable property address:0x60c000250680 class:__NSArrayM property:(
        1
    )
    strong immutable property address:0x60c000250680 class:__NSArrayM property:(
        1
    )
    copy immutable property address:0x608000201dd0 class:__NSSingleObjectArrayI property:(
        1
    )
    
    // 改变局部变量后的打印结果
    copy mutable property address:0x608000201de0 class:__NSSingleObjectArrayI property:(
        1
    )
    strong mutable property address:0x60c000250680 class:__NSArrayM property:(
        1,
        2
    )
    strong immutable property address:0x60c000250680 class:__NSArrayM property:(
        1,
        2
    )
    copy immutable property address:0x608000201dd0 class:__NSSingleObjectArrayI property:(
        1
    )
    

    可以看到,copy将原本是可变数组的的属性self.cmArr的类型变成了__NSSingleObjectArrayI不可变,同时对比内存地址可以发现,编译器开辟了一块新的内存空间用来存储copy的值。所以当我们如果写下类似@property (nonatomic, copy) NSMutableArray *foo时,需要考虑一下这样声明是否有意义。

    同样有趣的是,我们本来声明为不可变数组self.strongImArr却因为多态(父类指针指向子类),指向了一个可变数组,并在marr改变时,同样也发生了变化。这与开发者声明它为不可变时的用意可能会出现出入,对比最后一行打印的结果,可能copy会更贴近开发者的真正想法。所以这就是很多文章提倡用copy修饰NSString/NSArray/NSDictionary属性的用意所在。


    在认识@property后,接下来,我们会继续iOS-探究@property,学习更多@property的底层知识

    博客地址:iOS-认识@property

    相关文章

      网友评论

        本文标题:iOS-认识@property

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