美文网首页基础知识iOS Developer
@Property剖析、以及属性关键字。

@Property剖析、以及属性关键字。

作者: 蓝白自由 | 来源:发表于2016-10-31 12:11 被阅读678次

    //
    // @property 、属性、修饰符
    // Created by mac on 2016/10/30.
    // Copyright © 2016年 Rock. All rights reserved.
    //
    1> @property是什么?
    @property 是声明属性的语法,它可以快速的为实例变量创建存取器,并允许我们通过点语法使用存取器。
    @property 是一个属性访问声明,扩号内支持以下几个属性(下面有讲解)
    @property 它提供成员变量的访问方法的声明、也控制成员变量的访问权限、控制多线程时成员变量的访问环境。
    @property 可以在interface,在协议protocol. 和类别category中使用。
    @property (attribute1 , attribute2, ...) 是property的官方表达方式
    @property 功能:让编译器自动编写一对与数据成员同名的方法声明来省去读写方法的声明。
    @property 是Objective-C语言关键词,与@synthesize (ˈsɪnθəˌsaɪz 合成)配对使用。xcode4.5以及以后的版本,@synthesize可以省略。@synthesize 合成访问器方法,实现@property所声明的方法的定义。其实说直白就像是:@property声明了一些成员变量的访问方法 ,@synthesize则定义了由@property声明的方法。他们之前的对应关系是 @property 声明方法,头文件中申明的方法。 @synthesize定义方法在.m中 Cpp文件中定义的方法
    《存取器(accessory): 用于获取和设置实例变量的方法。 用于获取实例变量值的存取器是 getter, 用于设置实例变量值的存取器的存取器是 setter 》
    声明property的语法为:@property (参数1,参数2) 类型 名字;
    如:@property(nonatomic,retain) UIWindow *window;
    @property声明的是属性,而类后面加{}声明的是成员变量,属性跟成员变量是一回事,
    只是 @property将我们的成员变量公开出去让外部类可以调用,
    不允许外部访问的统统写在{}内作为成员变量供自己内部使用。

    //-----------------------------------------------------
    为了跟好的理解@property 来了解一下存取器:
    2> 创建存取器 我们先看两段代码:

     // Car.h
     #import <Foundation/Foundation.h>
     @interface Car : NSObject
     {
         // 实例变量
         NSString *carName;
         NSString *carType;
     }
     
     // setter
     - (void)setCarName:(NSString *)newCarName;
     // getter
     - (NSString *)carName;
     // setter
     - (void)setCarType:(NSString *)newCarType;
     // getter
     - (NSString *)carType;
     @end
    

    //-----------------------------------------------------
    上面的代码中 carName 和 carType 就是 Car 的实例变量,并且可以看到 分别对这两个实例变量声明了get/set 法,即存取器。
    .m中实现
    #import "Car.h"
    @implementation Car
    // setter
    - (void)setCarName:(NSString *)newCarName
    {
    carName = newCarName;
    }
    // getter
    - (NSString *)carName
    {
    return carName;
    }

     // setter
     - (void)setCarType:(NSString *)newCarType
     {
         carType = newCarType;
     }
     // getter
     - (NSString *)carType
     {
         return carType;
     }
     @end
    


    上面代码是对实例变量存取器的实现。我们可以看到,存取器就是对实例变量进行赋值和取值。按照约定赋值 法以set开头,取值方法以实例变量名命名。 我们看看如何使用:
    // main.m
    #import "Car.h"
    int main(int argc, char * argv[])
    {
    @autoreleasepool {

         Car *car = [[Car alloc] init];
         [car setCarName:@"Jeep Cherokee"];
         [car setCarType:@"SUV"];
         NSLog(@"The car name is %@ and the type is %@",[car carName],[car carType]);
       }
         return 0; 
     }
     上面的代码中我们注意到,对象 Car 使用了消息语法,也就是使用括号的setter语法给存取器发送消息。
     返回结果为:The car name is Jeep Cherokee and the type is SUV
    

    //----------------------------------------------------------------------
    2.1 使 @property创建存取器
    // Car.h
    #import <Foundation/Foundation.h>
    @interface Car : NSObject
    {
    // 实例变量
    NSString *carName;
    NSString *carType;
    }
    @property(nonatomic,strong) NSString *carName;
    @property(nonatomic,strong) NSString *carType;
    @end

     上面代码中,我们使  @property 声明两个属性,名称与实例变量名称相同
     (让我们先忽略 nonatomic 和 strong )。
     
     // Car.m
     #import "Car.h"
     @implementation Car
     
     @synthesize carName;
     @synthesize carType;
     @end
    
         在.m 文件中我们使 @synthesize 自动生成这两个实例变量的存取器,
    并且隐藏了存取器,虽然我们看不到存取器,但它们确实是存在的。
         
         // main.m
         int main(int argc, char * argv[])
         {
           @autoreleasepool {
             Car *car = [[Car alloc] init];
             car.carName = @"Jeep Compass";
             car.carType = @"SUV";
             NSLog(@"The car name is %@ and the type is %@",
    car.carName,car.carType);
           }
           return 0;
         }
         在上面的代码中我们可以注意到, Car 对象使用点语法给存取器发送消息, 
    并且get与set的语法是相同的,并且点语法可以根据语境判断我们是要赋 值还是取值。
         
         当然我们也依然可以使用消息语法来使用:
         // main.m
         int main(int argc, char * argv[])
         {
           @autoreleasepool {
             Car *car = [[Car alloc] init];
             // 点语法
             // car.carName = @"Jeep Compass";
             // car.carType = @"SUV";
             //NSLog(@"The car name is %@ and the type is %@",car.carName,car.carType);
             // 消息语法
             [car setCarName:@"Jeep Compass"];
             [car setCarType:@"SUV"];
             NSLog(@"The car name is %@ and the type is %@",
    [car carName],[car carType]);
            }
            return 0; 
         }
         上面两段代码的执 结果都是:
         The car name is Jeep Compass and the type is SUV
         总结: @property   等同于在.h 件中声明实例变量的get/set方法,
              @synthesize 等同于在.m 件中实现实例变量的get/set方法。
              使@property 和 @synthesize 创建存取器 声明两个存取器方法
    ( getter 和 setter )更简单。 且我们在使 属性时可以使用点语法赋值或取值,
    语法更简单,更符合面向对象编程。
        
    //---------------------------------------------------------------------
         2.2 不必单独声明实例变量
         如果使用@Property ,就不必单独声明实例变量了。
    因为在没有显式提供实例变量声明的前提下,
    系统会自动帮你完成实例变量。我们通过以下代码来说明:
         
         // Car.h
         #import <Foundation/Foundation.h>
         @interface Car : NSObject
         @property(nonatomic,strong) NSString *carName;
         @property(nonatomic,strong) NSString *carType;
         - (NSString *)carInfo;
         @end
         在.h 件中我们并没有声明实例变量,只是声明了 carName和 carType 
    两个属性,以及 一个 carInfo方法,返回值为 NSString * 。
         
         // Car.m
         #import "Car.h"
         @implementation Car
         
         - (NSString *)carInfo
         {
             return [NSString stringWithFormat:@"The car name is %@and the type is%@",_carName,_carType];
         }
         @end
         
         在.m 件中我们可以注意到,在 carInfo方法中我们使用了 _carName 和 _carType 实例变量,
    这就是当我们没有显式声明实例变量时,系统为我们自动生成的。命名规则是以 _ 为前缀,加上属性名,
         即 _propertyName 。
         其实在.m 件中实际是存在 @synthesize 声明语句的,只是系统将其隐藏 了:
         @synthesize carName = _carName;
         @synthesize carType = _carType;
         
         那么如果我们不喜欢默认的实例变量命名方法,
    或者我们希望使用更有语义的名称,应该怎么做呢。其实很简单:
         
         // Car.m
         #import "Car.h"
         @implementation Car
         @synthesize carName = i_am_car_name;
         @synthesize carType = i_am_car_type;
         
         - (NSString *)carInfo
         {
             return [NSString stringWithFormat:@"The car name is %@and the type is %@", 
    i_am_car_name,i_am_car_type];
         }
         @end
    

    //----------------------------------------------------
    2.3 再看一些介绍:关于类Class中的属性property
    在iOS第一版中,我们为输出口同时声明了属性和底层实例变量,
    那时属性是OC语言的一个新的机制,
    并且要求你必须声明与之对应的实例变量。 例如:

    @interface MyViewController :UIViewController
         {
             UIButton *myButton;
         }
         @property (nonatomic, retain) UIButton *myButton;
         @end
    
         苹果将默认编译器从GCC转换为LLVM(low level virtual machine),
    从此不再需要为属性声明实例变量了。
         如果LLVM发现一个没有匹配实例变量的属性,
    它将自动创建一个以下划线开头的实例变量。
         因此,在LLVM这个版本中,我们不再为输出口声明实例变量。
           
         例如:MyViewController.h文件
         @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;
         }
         @end
    
      
         
    .m文件中,self.name 这样的表达式是错误的。
         xcode会提示你使用->,改成self->name就可以了。因为OC中点表达式是表示调用方法,而上面的代码中没有name这个方法。
    OC语法关于点表达式的说明: {"点表达式(.)看起来与C语言中的结构体访问以及java语言汇总的对象访问有点类似,其实这是OC的设计人员有意为之。如果点表达式出现在等号 = 左边,该属性名称的setter方法将被调用。如果点表达式出现在右边,该属性名称的getter方法将被调用。" } 所以在OC中点表达式其实就是调用对象的setter和getter方法的一种快捷方式, 例如:People.age = 18; 完全等价于 [People.age setAge:18];
      ```      
         以前的用法,声明属性跟与之对应的实例变量:
         @interface MyViewController :UIViewControlle
         {
         UIButton *myButton;
         }
         @property (nonatomic, retain) UIButton *myButton;
         @end
    
         这种方法基本上使用最多,现在大部分也是在使用,因为很多开源的代码都是这种方式。
    但是ios5更新之后,苹果是建议以以下的方式来使用:
         @interface MyViewController :UIViewController
         @property (nonatomic, retain) UIButton *myButton;
         @end
           
         因为编译器会自动为你生成以下划线开头的实例变量_myButton,不需要自己手动再去写实例变量。而且也不需要在.m文件中写@synthesize myButton;也会自动为你生成setter,getter方法。@synthesize的作用就是让编译器为你自动生成setter与getter方法。  @synthesize 还有一个作用,可以指定与属性对应的实例变量,例如@synthesize myButton = xxx;那么self.myButton其实是操作的实例变量xxx,而不是_myButton了。
           
         在实际的项目中,我们一般这么写.m文件
         @synthesize myButton;
         这样写了之后,那么编译器会自动生成myButton的实例变量,以及相应的getter和setter方法。注意:_myButton这个实例变量是不存在的,因为自动生成的实例变量为myButton而不是_myButton,所以现在@synthesize的作用就相当于指定实例变量;
         如果.m文件中写了@synthesize myButton;那么生成的实例变量就是myButton;如果没写@synthesize myButton;那么生成的实例变量就是_myButton。所以跟以前的用法还是有点细微的区别。
         通过上述代码可以看到,我们只需要通过 @synthesize 来声明我们希望的实例变量名。
         总结:如果我们希望使用默认的实例变量命名显式,那么我们在.m 件中 就不需要使  @synthesize 声明,
    系统会帮我们自动完成。如果我们希望自己命名实例变量命,那么我们就使  @synthesize 显式声明我们希望的实例变量名。
               
    // @property  属性描述 只要属性描述过之后 就可以用对象名打点调用此属性 例如:
    
         @property  NSString  *name; //相当于
         1 . 声明了一个属性 叫做 NSString  *_name;
         2.  声明并实现了一个set方法
         - (void)setName:(NSString *)name;
         3. 声明并实现了 一个get方法
         - (NSString *)name;
        
             
       *****************************3、@property的特性*********************
         @property 还有一些关键字,它们都是有特殊作用的, 
         如上述代码中的 nonatomic,strong : 
         @property(nonatomic,strong) NSString *carName;
         
         主要属性分为3类:
         分别是:原子性,存取器控制,内存管理。
         原子性  (Atomicity) 包含:nonatomic
         读写属性(Writability,有时也可以称为存取器控制)       包含:readwrite / readonly
         内存管理(有时也可以称为 setter语义,Setter Semantics)包含:assign / retain / copy
             
    //     ***********各控制权限的关键字意义如下:*****************
    
    1、{
         存取器控制/可读性: readonly 、readwrite
         readwrite 、readonly:决定是否生成set访问器,
         readwrite  是默认属性,是可读可写特性、表明该属性同时拥有 setter 和 getter方法的声明与实现 。
         readonly   是只读特性  只会生成getter方法 不会生成setter方法 ;不希望属性在类外改变,注意 :readonly关键字代表setter不会被生成, 所以它不可以和 copy/retain/assign组合使用。
         @property(readwrite,....) valueType value;这个属性是变量的默认属性,就是如果你(readwrite and readonly都没有使用,那么你的变量就是readwrite属性),通过加入readwrite属性你的变量就会有get方法,和set方法。
         @property(readonly,...) valueType value;这个属性变量就是表明变量只有可读方法,也就是说,你只能使用它的get方法。
         有时候为了语意更明确可能需要定义访问器的名字 修改方法名称的关键字 :
         getter=getterName,setter=setterName,设置setter与getter的方法名 eg:
         @property (nonatomic, setter = mySetter:,getter = myGetter) NSString *name;
         最常用的是BOOL类型,一般情况下,BOOL类型的属性的getter方法都是以is开头的。
         如标识View是否隐藏的属性hidden。可以这样声明:
         @property (nonatomic,getter = isHidden ) BOOL hidden;
         setter表示修改生成的setter方法的名称,不过一般不修改。
       从OC 2.0开始,我们能让系统自动生成设置变量值的方法或获取变量值的方法,
         即系统会自动为我们生成setter/getter方法。
         注意:默认的property行为有:atomic,assign,readwrite。
         如果这样使用:@property  BOOL _flag;//代表这_flag有着atomic,assign,readwrite三种行为。
         所以我们一定要提防这种默认行为潜在的危险。
         如:@property NSMutableArray *photoViews;//此时会有警告出现,因为NSMutableArray是一种obj类型,并且是 NSArray类型,根据前面的分析,最好采用retain。所以默认的assign会带来警告提示。
         }
         2、{
         内存管理:
         @property 有显式的内存管理策略。
         这使得我们只需要看一眼 @property 声明就明白它会怎样对待传递的值。
     ************************************************
         assign (默认): assign 用于值类型,
         如 int 、 float 、 double 和 NSInteger , CGFloat 等表的单纯的复制。
         还包括不存在所有权关系的对象, 如常用的delegate。
         @property(nonatomic) int running;
         @property(nonatomic,assign) int running;
         以上两段代码是相同的。
         在 setter 方法中,采用直接赋值来实现设值操作:
             -(void)setRunning:(int)newRunning{
                 _running = newRunning;
             }
        
     retian :在 setter 方法中,需要对传的对象进行引用计数加1的操作。 简单来说,就是对传的对象拥有所有权,只要对该对象拥有所有权,该对象就不会被释放。如下代码所 :
         -(void)setName:(NSString*)_name{
         //先判断是否与旧对象一致,如果不一致 进行赋值。
         //因为如果是一个对象的话,进行if内的代码会造成一个极端的情况:
         当此name的retain为1时,使用此次的set操作让实例name提前释放,而达不到赋值的。
    

    if ( name != _name){
    [name release];
    name = [_name retain];
    }
    }

    
    strong : strong 是在IOS引入ARC的时候引用的关键字,是retain的一个可选的替代。
    表示实例变量对传入的对象要有所有权关系,即强引用。strong跟retain的意思相同并产生相同的代码,但是语意上更好更能体现对象的关系。
         weak :在 setter 方法中,需要对传入的对象不进行引用计数加1的操作。 简单来说,就是对传入的对象没有所有权,当该对象引用计数为0时,即该对象被释放后,weak 声明的实例变量指向nil ,即实例变量的值为 0。
         注: weak 关键字是IOS5引进的,IOS5之前是不能使用该关键字的。
         delegate 和 IBOutlet一般用 weak 来声明。
    
         copy :与 strong 类似,但区别在于实例变量是对传对象的副本拥有有所有权,
    在setter方法中,首先复制传入的参数,然后将原来的旧值release,新的数值复制上去。
    

    -(void)setName:(NSString*)newName{
    if(newName!=name){
    [name release];
    name=[newName copy];
    }
    }

         
    注意:Foundation中可复制的对象,当我们copy的是一个不可变的对象的时候,它的作用相当与retain(cocoa做的内存优化)比如我们对NSString进行copy,copy得到的地址是不变的。 
         
         下面四段代码中,第一段显示的结果不会copy,而是retain.
        
         NSString *sstr = [NSString stringWithFormat:@"dfa"];
         NSLog(@"strassign:%p,    count:%ld", sstr , [sstr retainCount]);
         NSString *sstr2 = [sstr copy];
         NSLog(@"strassign:%p,    count:%ld", sstr2 , [sstr2 retainCount]);
         
         NSString *sstr = [NSString stringWithFormat:@"dfa"];
         NSLog(@"strassign:%p,    count:%ld", sstr , [sstr retainCount]);
         NSString *sstr2 = [sstr mutableCopy];
         NSLog(@"strassign:%p,    count:%ld", sstr2 , [sstr2 retainCount]);
        
         NSMutableString *str = [NSMutableString stringWithFormat:@"dfa"];
         NSLog(@"strassign:%p,    count:%ld", str , [str retainCount]);
         NSMutableString *str2 = [str copy];
         NSLog(@"strassign:%p,    count:%ld", str2 , [str2 retainCount]);
       
         NSMutableString *str = [NSMutableString stringWithFormat:@"dfa"];
         NSLog(@"strassign:%p,    count:%ld", str , [str retainCount]);
         NSMutableString *str2 = [str mutableCopy];
         NSLog(@"strassign:%p,    count:%ld", str2 , [str2 retainCount]);
         
         *******************************************************************
         
    

    //================我是分割线===============新的剖析================
    assign、 retain 、copy: 这些属性用于指定set访问器的语义,
    也就是说,这些属性决定了以何种方式对数据成员赋予新值。
    assign:默认类型,setter方法直接赋值,不进行任何retain操作,
    为了解决原类型与环循引用问题
    retain:setter方法对参数进行release旧值,再retain新值。所有实现都是这个顺序
    copy: setter方法进行Copy操作,与retain处理流程一样,先旧值release,
    再Copy出新的对象,retainCount为1。这是为了减少对上下文的依赖而引入的机制。
    copy是在你不希望a和b共享一块内存时会使用到。a和b各自有自己的内存
    (通过这些我们可以看出这些属性和setter方法有关系,
    所以面试的或者笔试题会有让写出一个assign、retain、copy方法)
    //------------------------------------------------------
    面试题中会有:试写出 @property中带retain和assign关键字,
    通过@synthesize自动生成的的合成存取方法(set、get方法)的实现代码:
    getter分析:

     @property (nonatomic, retain) test*aTest;
     @property (nonatomic, copy) test*aTest;
     等效代码:
     -(void)aTest {
         return aTest;
     }
     
     
     @property (retain) test* aTest;
     @property (copy) test* aTest;
     等效代码:
     -(void)aTest
     {
         [aTest  retain];
         return [aTest  autorelease];
     }
     ======== 貌似我是分割线 =====
     setter分析:
     @property (nonatomic, retain) test*aTest;
     @property (retain) test* aTest;
     等效于:
     -(void)setaTest:(test *)newaTest {
         if (aTest !=newaTest) {
            [aTest  release];
            aTest = [newaTest  retain];
         }
     }
     ======== 貌似我是分割线 =====
     @property (nonatomic, copy) test*aTest;
     @property (copy) test* aTest;
     等效于:
     -(void)setaTest:(test *)newaTest {
         if (aTest != newaTest){
            [aTest  release];
            aTest = [newaTest  copy];
         }
     }
     *******************************************************************
    
         //------------------------第二轮解析---------------------------------
        
     **************************** assign *******************************    
    1、{
         assign    普通、直接赋值,索引计数不改变,一般常用于简单数据类型,
    如NSInteger,double,bool,CGFloat,C数据类型(int, float,double, char, 等等)常见委托设计模式,以此来防止循环引用。assign是赋值特性,仅设置变量时,setter方法将传入参数赋值给实例变量;
        (补充:
             assign与weak,它们都是弱引用声明类型,最大的区别在那呢?
             如果用weak声明的变量在栈中就会自动清空,赋值为nil。
             如果用assign声明的变量在栈中可能不会自动赋值为nil,就会造成野指针错误!
             二、实例
             他们常用在基本类型属性,比如BOOL,int等,还有就是delegate。
             在使用delegate时,需要注意,非ARC时是使用assign,但到了ARC时代,都建议使用weak,这样更安全。
         ```
         不管是在非ARC还是ARC,使用assign时,都需要注意释放,
         @property (nonatomic , assign) id <MyDelegate> delegate;
         如果你写的library比较早,那时还是非ARC的,你的delegate设成assign property 
    这样是为了不造成circular reference
         当别人使用你的library的时候,记得在你自己dealloc的时候,把delegate设成nil,以避免crash的事情发生。
         这在非ARC模式下是比较自然的,都会这么去做,
         当myViewController 的 ratain count 变成0 ,则会dealloc
         同时在dealloc 中,也是一并把myClass release,则myClass 也跟着被release.
    
     //MRC
     - (void) dealloc {
         myClass.delegate = nil ;
         [myClass release];
         [super dealloc];
     }
     但在ARC模式下,使用方就不会有dealloc处理的习惯了
     - (void) dealloc {
         //myClass.delegate = nil ;
         [myClass setDelegate:nil];
     }
     如果在ARC下,没有做这个逻辑的话,当页面销毁的时候,
     很可能出现delegate对象无效,导致程序crash.
     所以,在我们使用网上下载的开源库或者时间比较久的代码时,
     记得检查delegate的修饰符,如果是assign的需要及时修改为weak。
     @property (nonatomic , weak) id <MyDelegate> delegate;
     
     ###为什么基本数据类型用assgin?
     iOS中的垃圾处理机制是根据一个对象的索引数来处理的,
     为0的时候表示没有地方使用该对象,则该对象将被清除,
     而基本数据类型不属于对象,它的创建和使用都是在内存中,
     超出对应方法体即被清除,所以不需要使用垃圾处理机制,
     无需记录索引值,所以使用assgin
    
     循环引用: 所有的引用计数系统,都存在循环应用的问题。 
     循环引用:指针的是两个对象中,你中有我,我中有你。
     跟java中的一对一很相似。
     至于产生内存泄露的原因:主要是相互之间强指针指着对方,
     感觉跟java里面谁来hibernate设置谁来管理对方。
     (在这里我们引入了强指针与弱指针在ARC中会提到,这里不做解释。)
     解决循环引用的方式:让其中一方设置为assign。
     多个对象之间不要封闭环,如果出现封闭的环,那么环中所有的对象将得不到释放。
     解决的方法,让其中一端为虚线。
     例如下面的引用关系:
     ·对象a创建并引用到了对象b. 
     ·对象b创建并引用到了对象c. 
     ·对象c创建并引用到了对象b. 这时候b和c的引用计数分别是2和1。
     当a不再使用b,调用release释放对b的所有权,因为c还引用了b,
     所以b的引用计数为1,b不会被释放。b不释放,c的引用计数就是1,
     c也不会被释放。从此,b和c永远留在内存中。这种情况,
     必须打断循环引用,通过其他规则来维护引用关系。
     比如,我们常见的delegate往往是assign方式的属性而不是retain方式 的属性,
     赋值不会增加引用计数,就是为了防止delegation两端产生不必要的循环引用。
     如果一个UITableViewController 对象a通过retain获取了UITableView对象b的所有权,
     这个UITableView对象b的delegate又是a, 如果这个delegate是retain方式的,
     那基本上就没有机会释放这两个对象了。自己在设计使用delegate模式时,
     也要注意这点。因为循环引用而产生的内存泄露也是Instrument无法发现的,所以要特别小心。
     
     ###assign和weak的区别
     在ARC中的assign和weak可以说非常相像,导致有很多人误以为他们是一摸一样的,
     在任何时候都可以划等价,但事实却不是这样。
     有人问,id类型的delegate属性到底是用assign还是weak?
     
     @property (weak, nonatomic) id<AnyDelegate> delegate;
     @property (assign, nonatomic) id<AnyDelegate> delegate;
     大家众说纷纭,说都可以的,说assign的,说weak的都有,下面我们来看一本书中的描述:
     
     “The main difference between weak and assign is that the with weak,
       once the object being pointed to is no longer valid, 
      the pointer is nilled out. Assigning the pointer the value nil avoids 
      many crashes as messages sent to nil are essentially no-ops”
     Excerpt From: Daniel H Steinberg.
     “Developing iOS 7 Apps for iPad and iPhone.” 
     Dim Sum Thinking, Inc, 2014. iBooks. iTunes - Books
     大致的意思是说,weak比assign多了一个功能就是当属性所指向的对象消失的时候
     (也就是内存引用计数为0)会自动赋值为nil,
     这样再向weak修饰的属性发送消息就不会导致野指针操作crash。
     可能不太好理解下面我写了一个演示程序。
     OC:
     //  ViewController.m
     //  weak与assgin的区别:
     
     #import "ViewController.h"
     @interface ViewController ()
     @property (nonatomic,weak)   id    weakPoint;
     @property (nonatomic,assign) id    assignPoint;
     @property (nonatomic,strong) id    strongPoint;
     @end
     
     @implementation ViewController
     - (void)viewDidLoad {
         [super viewDidLoad];
     
         self.strongPoint = [NSDate date];
         NSLog(@"strong属性:%@",self.strongPoint);
         self.weakPoint = self.strongPoint;
         self.assignPoint = self.strongPoint;
         self.strongPoint = nil;
         NSLog(@"weak属性:%@",self.weakPoint);
         // NSLog(@"assign属性:%@",self.assignPoint);
     }
     @end
     
     当程序中的注释被打开时,运行程序有可能会崩溃(有时候不崩溃,你可能需要多运行几次),
     这是因为当assign指针所指向的内存被释放(释放并不等于抹除,只是引用计数为0),
     不会自动赋值nil,这样再引用self.assignPoint就会导致野指针操作,
     如果这个操作发生时内存还没有改变内容,依旧可以输出正确的结果,
     而如果发生时内存内容被改变了,就会crash。
     结论:在ARC模式下编程时,指针变量一定要用weak修饰,
     只有基本数据类型和结构体需要用assgin,例如delegate,
     一定要用weak修饰。
     )
     }
     ******************************* retain ********************************
    #2、{
     retain:指针的拷贝,retain表示持有特性,使用的是原来的内存空间。
     希望获得原对象的所有权时,对其他NSObject和其子类引用计数在原有基础上加1。
      setter方法将传入参数先保留,再赋值,传入参数的retaincount会+1,
      此属性只能用于Objective-C对象类型,而不能用于Core Foundation对象。
      (原因很明显,retain会增加对象的引用计数,
      而基本数据类型或者Core Foundation对象都没有引用计数)。
     
     copy:也用于对象,对象的拷贝,copy表示赋值特性,希望获得原对象的副本而不改变原对象内容时,
      用来复制对象,新申请一块内存空间,并把原始内容复制到那片空间。新对象的索引计数为1。
      此属性只对那些实行了NSCopying协议的对象类型有效。
      很多Objective-C中的object最好使用用retain,一些特别的object(例如:string)使用copy。
      一般字符串(NSString)使用copy,Foundation中的不可变对象使用copy效果相当于retain,
     只是引用计数+1。setter方法将传入对象复制一份;需要完全一份新的变量时。
     }
    

    ******************************* atomic ********************************

    3、{
          ***#对原子性的理解:#***
          1、原子性就是说一个操作不可以被中途CPU暂停然后调度, 即不能被中断, 要不就执行完, 要不就不执行. 如果一个操作是原子性的,那么在多线程环境下, 就不会出现变量被修改等奇怪的问题。原子操作就是不可再分的操作,在多线程程序中原子操作是一个非常重要的概念,它常常用来实现一些同步机制,同时也是一些常见的多线程Bug的源头。当然,原子性的变量在执行效率上要低些。
          2、关于异步与同步:并非同步就是不好,我们通常需要同时进行多个操作,这时使用异步,而对于程序来说,一般就是使用多线程,然而我们很多时候需要在多个线程间访问共享的数据,这个时候又需要同步来保证数据的准确性或访问的先后次序。当有多个线程需要访问到同一个数据时,OC中,我们可以使用@synchronized(变量)来对该变量进行加锁(加锁的目的常常是为了同步或保证原子操作)。
         3、**nonatomic:**非原子性,是直接从内存中取数值,因为它是从内存中取得数据,它并没有一个加锁的保护来用于CPU中的寄存器计算Value,它只是单纯的从内存地址中,当前的内存存储的数据结果来进行使用。在多线环境下可提高性能,但无法保证数据同步。
         **4、atomic:**原子性,如果你没有对对象进行一个声明(atomic or nonatomic),那么系统会默认你选择的是atomic。默认值是atomic,为原子操作(默认): atomic 意为操作是原子性的,意味着只有一个线程 访问实例变量。atomic是线程安全的, 少在当前的存取器上是安全的。 它是一个默认的特性,但是很少使 ,因为比较影响效率,这跟ARM平台和内部锁机制有关。
           ** nonatomic:**非原子性操作,非原子性访问,不加同步,多线程并发访问会提高性能。如果不加此属性,则默认是两个访问方法都为原子型事务访问。锁被加到所属对象实例级(所以不加nonatomic对与多线程是安全的),nonatomic 跟 atomic 刚好相反。非原子性操作,可以被多个线程访问。它的效率比atomic快。但不能保证在多线程环境下的安全性,在单线程和明确只有一个线程访问的情况下广泛使 。
           atomic和nonatomic用来决定编译器生成的getter和setter是否为原子操作。在多线程环境下,原子操作是必要的,否则有可能引起错误的结果。atomic表示多线程安全,一般使用nonatomic
         **5、原子(atomic) 跟非原子(non-atomic)属性有什么区别?**
          1). atomic 提供多线程安全。atomic是OC使用的一种线程保护技术,基本上来讲,是防止在写未完成的时候被另外一个线程读取,造成数据错误。而这种机制是耗费系统资源的,所以在iPhone这种小型设备上,如果没有使用多线程间的通讯编程,那么nonatomic是一个非常好的选择
          2). non-atomic 在自己管理内存的环境中,解析的访问器保留并自动释放返回的值,如果指定了 nonatomic ,那么访问器只是简单地返回这个值。
         }   
    //
    
     ********类别中的属性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方法。
     注意一点,匿名类别(匿名扩展)是可以添加实例变量的,非匿名类别是不能添加实例变量的,
     只能添加方法,或者属性(其实也是方法)。
     · 成员变量用于类内部,无需与外界接触的变量。
     · 根据成员变量的私有性,为了方便访问,所以就有了属性变量。
     属性变量的好处就是允许让其他对象访问到该变量。
      当然,你可以设置只读或者可写等,设置方法也可自定义。所以,
     属性变量是用于与其他对象交互的变量。
     一些建议:
     1.如果只是单纯的private变量,最好声明在implementation里.
     2.如果是类的public属性,就用property写在.h文件里
     3.如果自己内部需要setter和getter来实现一些东西,就在.m文件的类目里用property来声明
     //--------------------------------------------------------
     .h中的interface的大括号{}之间的实例变量,.m中可以直接使用;
     .h中的property变量,.m中需要使用self.propertyVariable的方式
     使用propertyVariable变量
    

    // ***第三轮解析 在MRC中的@property ********
    #1,assign :简单赋值,不更改索引计数
    假设你用alloc分配了一块内存,并且把它的地址赋值给了指针a,
    后来你希望指针b也共享这块内存,于是你又把a赋值给(assign)了b。
    此时a 和b指向同一块内存,请问当a不再需要这块内存,能否直接释放它?
    答案是否定的,因为a并不知道b是否还在使用这块内存,
    如果a释放了,那么b在使用这块内存的时候会引起程序crash掉
    应用场合:对基础数据类型 (例如NSInteger,CGFloat)
    和C数据类型(int, float, double, char, 等)
    适用简单数据类型、 特例 delegate
    //--------------------------------------------------------
    #2,retain:与strong相对应,使用了引用计数,retain+1,release -1;
    当引用 计数为0时,dealloc会被调用,内存被释放
    //-------------------------------------------------------
    #3,copy:用于非共享内存时,每个指针有自己的内存空间
    copy 当属性是NSString数据类型的时候就使用copy
    copy 此特质所表达的所属关系与strong类似。然而设置方法并不保留新值,而是将其“拷贝”

     copy与retain的具体区别为:
     copy其实是建立了一个相同的对象,而retain只是保存其对象,并且其计数值+1。例如:
     一个NSString对象,地址为0×1000,内容为@”string”
     copy到另外一个NSString之后,地址为0×2000,内容相同,
     新的对象retain为1,旧有对象没有变化
     retain到另外一个NSString之后,地址相同(建立一个指针,指针拷贝),内容当然相同,
     但是这个新对象的retain值+1,并释放旧的对象。
     因此,retain是指针拷贝,copy是内容拷贝。
     ```
    
     #4,atomic 默认属性
         A,当一个变量声明为atomic时,意味着在多线程中只能有一个线程能对它进行访问
         B,当一个变量声明为atomic时,该变量为线程安全型,但是会影响访问速度,
         C,当一个变量声明为atomic时,在非ARC编译环境下,
           需要设置访问锁来保证对该变量进行正确的get/set
         atomicity的默认值是atomic,读取函数为原子操作。
         atomic是保证读取变量是线程安全的,即它会保证每次getter和setter的操作都会正确的执行完毕,
         而不用担心其它线程在你get的时候set,可以说保证了某种程度上的线程安全。```
    
        # 5,nonatomic
         A,当一个变量声明为nonatomic时,意味着多个线程可以同时对其进行访问
         B,当一个变量声明为nonatomic时,它是非线程安全型,访问速度快;
         C,当一个变量声明为nonatomic时,当两个不同的线程对其访问时,容易失控。
         nonatomic是不能保证线程安全的。但是nonatomic比atomic速度要快。
         这也是为什么property基本上都用nonatomic了。
         仅仅靠atomic来保证线程安全是不可能的,要写出线程安全的代码,还是需要有同步和互斥机制。
         
        # 总结:atomic和nonatomic用来决定编译器生成的getter和setter是否为原子操作。
         在多线程环境下,原子操作是必要的,否则有可能引起错误的结果。
         加了atomic,setter函数会变成下面这样:
         if (property != newValue) {
             [property release];
             property = [newValue retain];
         }
            
    
     #  strong与weak是由ARC新引入的对象变量属性
     6,strong: ARC中默认属性,等于MRC中的retain, 与retain相对应,“拥有关系” 为这种属性设置新值时,
     设置方法先保留新值,并释放旧值,然后再将新值设置上去,strong是在iOS引入ARC的时候引入的关键字,
     是retain的一个可选的替代。表示实例变量对传入的对象要有所有权关系,即强引用。
     strong跟retain的意思相同并产生相同的代码,但是语意上更好更能体现对象的关系。
      应用场景:strong属性用于ARC中
      @property (strong,nonatomic) ViewController *viewController;
    
     7,weak: 与assign 相对应,“非拥有关系” 为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。
     此特质同assign类似,然而属性所指的对象遭到摧毁时,属性也会被清空(nil out),在setter方法中,
      需要对传入的对象不进行引用计数加1的操作,和assign相似。简单来说,就是对传入的对象没有所有权,
     当该对象引用计数为0时,即该对象被释放后,用weak声明的实例变量指向nil,即实例变量的值为0。
      应用场景:用于IBOutlets,如,UIViewController的子类,即一般的控件。
      @property (weak, nonatomic) IBOutlet UIButton *myButton;
    
      #//  8、strong与weak的区别举例:
       前提:我们把要用strong或者weak的对象比作一只风筝,风筝想挣脱线的束缚,自由飞翔去,
       如果此时有一根线,那么这只风筝就挣脱不了
       过程分析:
       strong属性的变量:当我们把指向一只风筝的变量声明为strong时,此时,你就拥有控制这只风筝的线
       假如此时有五个人同时控制这只风筝(即这只风筝对象有三个strong类型的变量指向它),
       那么只有一种情况,这只风筝才会挣脱掉线的束缚:这三个人都放掉手中的线,(release掉)
      
    
      #weak属性的变量:当我们把指向一只风筝的变量声明为weak时,此时,就像站在旁边看风筝的观众们一样,
     当上面的三个人还握着手中的线时,他们只能看到风筝,并不能控制它,他们能做的只能是用手指指向风筝,
     并大喊,“看,那只风筝飞得真高!”,然而,当上面的三个人把手中的线都放掉时,此时,风筝飞走了,
     看不见了,不管有再多的观众,他们再也看不到风筝了,
     这个故事告诉我们一个道理:
     当strong类型的指针被释放掉之后,所有的指向同一个对象的weak指针都会被清零。
    
     ARC引入了新的对象的新生命周期限定,即零弱引用。
     如果零弱引用指向的对象被dealloc的话,零弱引用的对象会被自动设置为nil。
     #(_strong强指针标识符,默认所有的指针都是强指针)
     作用:只要强指针指向的对象,那么这个对象就不会被释放。
     只要没有强指针指向的对象,那么这个对象将会被立即被释放。
     
     #(_weak弱指针标识符)
     弱指针:不参与内存管理,对内存管理没有影响。不会影响对象的回收。
     
     #注意:不要使用一个弱指针指向一个刚刚创建出来的对象,
     一旦这样做,刚创建出来的对象马上销毁,在OC中也是自动销毁机制。
     当出现循环引用的时候就必须要让一端使用弱指针。
     
     ###强引用与弱引用的广义区别:
     强引用也就是我们通常所讲的引用,其存亡直接决定了所指对象的存亡。如果不存在指向一个对象的引用,
     并且此对象不再显示列表中,则此对象会被从内存中释放。
     弱引用除了不决定对象的存亡外,其他与强引用相同。即使一个对象被持有无数个若引用,
     只要没有强引用指向他,那麽其还是会被清除。没办法,还是 “强哥” 有面子。
     简单讲strong 等同 retain,weak比assign多了一个功能,
     当对象消失后自动把指针变成nil,好处不言而喻。
     ###强引用(strong)和弱引用(weak)的一个笑话(便于理解):
     把对象想象成一条狗,它要跑 (be deallocated)。强指针就像一条拴在狗脖子上的狗链;
     只要攥在手里,狗就跑不了;如果5个人攥着5条狗链都拴着狗 (5个强指针指向对象),
     除非5条狗链都撒开,否则狗就跑不了。
     弱指针就像是孩子指着狗喊“看!狗!”;只要狗链还拴着狗,孩子就能指着狗喊。
     当所有狗链都撒开,不管有多少孩子指着狗喊,狗都跑了。当最后一个强指针不再指向对象,
     对象就会被释放,所有弱指针清零。我们什么时候使用弱指针呢?
     只有当你想避免保留循环 (retain cycles,) 时,我们才使用它。
     
     @property(strong) MyClass *myObject;
     相当于@property(retain) MyClass *myObject;
    
     @property(weak) MyOtherClass *delegate;
     相当于@property(assign) MyOtherClass *delegate;
    

    //-----------------------------------------------------------
    9、,unsafe_unretauined
    用在ARC编译环境下,在此环境下,与assign相似。它只是告诉ARC如何正确地调用声明为unsafe_unretauined变量的retain和release
    但是它适用于“对象类型”(object type),该特质表达一种“非拥有关系”(“不保留”,unretained),
    当目标对象遭到摧毁时,属性值不会自动清空(“不安全”,unsafe),这一点与weak有区别
    //------------------------------------------------------------

    在ARC中的@property 中 strong  对应所有的普通 OC对象 copy 对应字符串  、
    assign 基本数据类型 、weak 对于循环应用的时候,必须保证一段使用的是weak。
         ```
         #自动释放池:(自动释放池是一个栈)
         autorelease: 延长对象的释放生命周期。 作用: 把对象放进离自己最近的那个自动释放池中。
         (它与对象在何地创建没有关系,只要标记上就放进离自己最近的那个自动释放池中。)
         当自动释放池销毁的时候,它会把放在池中的所有对象进行一次release操作。
         调用几次autorelease,在自动释放池销毁的时候就调用几次release操作。
         在自动释放池中,只要是使用getter方法 构造方法返回来的对象都是放在池中。
         
         #注意:
         在iOS 5(Xcode 4.2)以后加入了ARC机制,ARC(automatic Reference Counting)自动内存管理
         ARC中编译器的特性:编译器会在适当的时候,
         加入内存管理的代码!!! 不需要再调用retain/release方法管理内存了,
         但这并不是说ARC会自动回收内存,它只是自动加入了retain/release的代码,
         OC的内存管理机制依然是 !!! 计数机制 !!!。
         assign生成的set方法中依然不会被自动加入retain/release代码。
         */

    相关文章

      网友评论

        本文标题:@Property剖析、以及属性关键字。

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