美文网首页iOS基本功
iOS成员变量、实例变量、成员属性、@property、@syn

iOS成员变量、实例变量、成员属性、@property、@syn

作者: 火星抄手 | 来源:发表于2022-02-11 20:07 被阅读0次

    一、iOS成员变量、实例变量、成员属性说明:

    1、成员变量、实例变量:

    1)、成员变量是在{}中声明的变量,如下代码所示:
    2)、如果成员变量的类型是一个类则称这个变量为实例变量
    3)、成员变量包括实例变量,所以可以通称为成员变量(这里只是便于概念理解分开解释)

    实例变量 = 成员变量 = ivar
    
    #import <Foundation/Foundation.h>
    NS_ASSUME_NONNULL_BEGIN
    @interface Persion : NSObject{
        NSString *name; //实例变量
        int age;        //成员变量
    }
    @end
    NS_ASSUME_NONNULL_END
    
    

    2、成员属性(也可称属性变量):

    通常我们使用@property声明的变量都叫做成员属性,也可称属性变量。

    3、成员变量和成员属性的关系:

    1、属性对成员变量扩充了存取方法 (例如在get和set方法中做其他逻辑);
    2、属性默认会生成带下划线的成员变量 ;
    3、但只声明了变量,是不会有属性的,可以通过以下代码证明:
    在Person.h 头文件中

    @interface Person : NSObject {
        @private
        //name为私有成员变量
        NSString *name;
    }
     // age 为成员属性
    @property (nonatomic ,copy) NSString *age;
    

    在viewController.m 中,通过RunTime机制获得对象的所有成员变量和成员属性。

        Person *p = [Person new];
        unsigned int count = 0; //count记录变量的数量
        
        // 获取类的所有成员变量
        Ivar *members = class_copyIvarList([Person class], &count);
        for (int i = 0; i < count; i++) {
            Ivar ivar = members[i];
            // 取得变量名并转成字符串类型
            const char *memberName = ivar_getName(ivar);
            NSLog(@"变量名 = %s",memberName);
        }
        // 获取类的所有成员属性
        objc_property_t *properties =class_copyPropertyList([Person class], &count);
        for (int i = 0; i<count; i++)
        {
            objc_property_t property = properties[i];
            const char* char_f =property_getName(property);
            NSString *propertyName = [NSString stringWithUTF8String:char_f];
            NSLog(@"属性名 = %@",propertyName);
        }
    

    打印结果为

    变量名 = name
    变量名 = _age
    属性名 = age
    

    二、@property、@synthesize、@dynamic说明:

    1、@property

    @property是用来定义成员属性的,通常情况会自动合成成员变量和set/get方法。

    简单来说:我们写@property声明属性,其实是做了三件事(@property = _ivar + getter + setter):
    .h: 声明了getter和setter方法;
    .m: 声明了成员变量(默认:下划线+属性名);
    .m: 实现了getter和setter方法。

    以上三件事是由编译器自动加上了@synthesize关键字的功能,只是常规情况下默认省略了。

    1. 如果这个成员变量(同名_ivar)已经存在,@property就不再生成新成员变量;
    2. 默认合成的成员变量创建后默认是@private类型,只能在本类中访问,子类也无法访问父类默认生成的成员变量_ivar;
    3. 在.h声明的成员变量会被子类访问,是@protected类型。(补充:.h中声明的成员变量都是protected,想要被非子类访问需要用@public修饰)

    接下来先看看以下问题:
    1、那@synthesize,@dynamic到底是干什么的?
    2、什么情况下不会自动合成成员变量和set/get方法呢?

    2、@synthesize

    @synthesize 是配合@property使用的。字面意思是合成,这个关键字在默认情况下可以省略,编译器自动会实现这个关键字的功能,也可以手动加上实现。

    1. 如果属性没有手动实现setter和getter方法,编译器为你自动生成setter与getter方法;
    2. 可以指定与属性对应的实例变量(例如:@syntheszie ivar = _ivar123,就会为成员属性ivar生成一个_ivar123的成员变量);
    3. 如果子类中有和父类重名的属性,就会报错:
      Auto property synthesis will not synthesize property 'name';
      it will be implemented by its superclass, use @dynamic to acknowledge intention
      这是因为当编译器检测到父类相同属性的时候子类不会自动生成@sythesize ivar = _ivar,此时子类只有属性没有生成对应成员变量_ivar,也不会有对应的set和get方法。
      在子类调用self.ivar时实际上是调用父类的属性。一旦这个子类的属性是开发者自定义的,开发者用这个属性调用方法时用了自定义的方法,这个方法父类属性没有的时候,就会造成崩溃;
      这个时候可以在子类添加@syntheszie ivar = _ivar ,子类会生成自己私有的_ivar成员变量,这个时候子类的self.ivar也就会访问自己的属性。

    另外,以下这些场景定义的属性不会合成成员变量:

    1)同时重写了 setter 和 getter 时
    2)重写了只读属性的 getter 时,如下第三部分readonly 和 writeonly情况下重写
    3)使用了 @dynamic 时
    4)在 @protocol 中定义的所有属性
    5)在 category 中定义的所有属性
    6)重载的属性(如果子类中有和父类重名的属性,就会警告,需要用@synthesize)

    如果条件满足且需要成员变量可以使用@synthesize关键字来合成

    3、@dynamic

    @dynamic告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。

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

    三、重写getter和setter方法注意事项

    只重写getter(懒加载):默认会自动生成下划线开头的变量,在getter中要使用下划线(return _ivar)来返回值,不能使用self.否则造成死循环

    只重写setter:默认会自动生成下划线开头的变量,在setter中要使用下划线( _ivar= ivar)来接收值,不能使用self.否则造成死循环

    两个都重写:同时手动重写了一个属性的get和set方法的话,Xcode不会再自动生成带有下划线的私有成员变量了这时如果不加,@synthesize就会报错,解决方法就是添加@syntheszie ivar = _ivar

    readonly 和 writeonly情况下重写:这时属性只会生成getter或者setter方法,如果我们重写了该方法,就需要我们重新添加@synthesize

    四、Objective-C 中的点语法

    • 点表达式(.)看起来与C语言中的结构体访问以及java语言汇总的对象访问有点类似,如果点表达式出现在等号 左边,调用该属性名称的setter方法。如果点表达式出现在右边,调用该属性名称的getter方法。
    • OC中点表达式(.)其实就是调用对象的settergetter方法的一种快捷方式,self.myString = @"张三";实际就是[self setmyString:@"张三"];

    属性访问方式 :
    这是我们最容易掌握的一种使用方式,所以甚至有的开发者在开发中只会定义属性
    person .name = @"xiaoming";

    指针访问方式 :
    作为一个有洁癖的程序员,更多时候还是定义成员变量而不是属性,因为至少减少了一次方法调用,减少了内存占用
    person->_name = @"xiaowang";

    KVC访问方式 :
    如果一个类的成员变量是私有的,然后我想访问它,可以使用KVC的方式
    [person setValue:@"xiaohua" forKey:@"name"];

    五、self.ivar和_ivar的区别

    其中self.ivar是调用的xx属性的get/set方法,而_ivar则只是使用成员变量_ivar,并不会调用get/set方法。两者的更深层次的区别在于,通过存取方法访问比直接访问多做了一些其他的事情(例如内存管理,复制值等),例如如果属性在@property中属性的修饰符有retain,那么当使用self.xx的时候相应的属性的引用计数器由于生成了setter方法而进行加1操作,此时的retaincount为2

    六、属性、成员变量、self.ivar、_ivar使用经验总结

    • 需要与外部类交互的都写成属性

    • 所有属性在使用时最好使用self.来调用,其他内部使用的对象尽量用成员变量定义(减少内存占用,调用更快)

    • 需要懒加载的对象定义为属性(或私有属性)

    • 重写getter(懒加载)和setter方法时在内部使用_ivar来操作,避免造成死锁。

    七、其他

    1、经常看到block里面有报警:

    Block implicitly retains 'self'; explicitly mention 'self' to indicate this is intended behavior

    block中使用了self的成员变量_ivar,因此block会隐式的retain住self。Xcode认为这可能会给开发者造成困惑,或者因此而因袭循环引用,所以警告我们要显示的在block中使用self,以达到block显示retain住self的目的。

    参考:
    iOS彻底搞清属性与成员变量
    objective-c指针解引用

    相关文章

      网友评论

        本文标题:iOS成员变量、实例变量、成员属性、@property、@syn

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