美文网首页
iOS 底层探索-属性

iOS 底层探索-属性

作者: 搬砖的crystal | 来源:发表于2020-05-28 15:31 被阅读0次

    一、属性简介

    属性是OC语言中的一个机制,在OC中用@property来声明一个属性,编译器会自动为实例变量生成setter和getter方法。
    所以在获取属性值NSLog(@"%@",self.name);和设置属性值(self.name = @"xiaoming")的时候其实是调用了getter和setter方法获取和设置实例值。
    一般编译器生成的实例变量是“_属性名”。也可以使用@synthesize定义实例变量名。也可以自定义setter和getter方法。

    #import "ViewController.h"
    
    @interface ViewController ()
    @property (nonatomic,copy)NSString *name;
    @end
    @implementation ViewController
    //定义实例变量名
    @synthesize name = _nibName;
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.name = @"xiaoming";
        NSLog(@"%@",self.name);//输出xiaohong
        NSLog(@"%@",_nibName);//输出xiaoli,不执行getter方法取值
    }
    -(void)setName:(NSString *)name{
        _nibName = @"xiaoli";
    }
    -(NSString *)name{
        return @"xiaohong";
    }
    @end
    

    二、属性存储

    1.Runtime属性的存储

    在OC中类是objc_class的结构体指针,结构体如下:

    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class _Nullable super_class                              OBJC2_UNAVAILABLE;
        const char * _Nonnull name                               OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    
    • objc_ivar_list是类的成员变量列表
    struct objc_ivar_list {
        int ivar_count                                           OBJC2_UNAVAILABLE;
    #ifdef __LP64__
        int space                                                OBJC2_UNAVAILABLE;
    #endif
        /* variable length structure */
        struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
    }   
    
    • objc_method_list是类的方法列表
    struct objc_method_list {
        struct objc_method_list * _Nullable obsolete             OBJC2_UNAVAILABLE;
    
        int method_count                                         OBJC2_UNAVAILABLE;
    #ifdef __LP64__
        int space                                                OBJC2_UNAVAILABLE;
    #endif
        /* variable length structure */
        struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
    }  
    

    属性生成过程分为以下几步:
    (1)创建属性,设置其objc_ivar,通过偏移量和内存占用就可以方便获取。
    (2)生成getter和setter方法。
    (3)将属性的ivar添加到类的ivar_list中,作为类的成员变量存在。
    (4)将getter和setter方法加入类的method_list中。
    (5)将属性描述添加到类的属性描述列表中。

    2.获取成员变量和属性

    (1)获取成员变量列表,返回一个指向成员变量信息的数组,数组中每个元素是指向成员变量信息的objc_ivar结构体指针。这个数组不包括在父类中声明的变量。

    Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
    

    (2)获取属性列表

    objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
    

    示例:

    #import <Foundation/Foundation.h>
    @interface Person : NSObject{
        NSString *number;
    }
    @property (nonatomic,strong)NSString *name;
    @end
    
    #import "ViewController.h"
    #import "Person.h"
    #import <objc/message.h>
    @interface ViewController ()
    
    @end
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        unsigned int count = 0;
        Ivar *ivarList = class_copyIvarList([Person class], &count);
        for (int  i = 0; i<count; i++) {
            Ivar ivar = ivarList[i];
            const char *ivarChar = ivar_getName(ivar);
            NSString *ivarName = [NSString stringWithUTF8String:ivarChar];
            NSLog(@"成员变量名 %d == %@",i,ivarName);
        }
        objc_property_t *propertyList = class_copyPropertyList([Person class], &count);
        for (int i = 0; i<count; i++){
            objc_property_t property = propertyList[i];
            const char* propertyChar =property_getName(property);
            NSString *propertyName = [NSString stringWithUTF8String:propertyChar];
            NSLog(@"属性名 %d == %@",i,propertyName);
        }
    }
    
    @end
    
    3.属性修饰符
    修饰符 作用
    readwrite 可读可写,生成setter和getter方法,默认属性
    readonly 只读,只生成getter
    nonatomic 非原子属性,提高性能但线程不安全
    atomic 原子性,线程安全但可能降低性能
    assign 简单赋值,不更改引用计数,用于修饰基础数据类型和C语言类型数据
    strong 强引用,持有对象,引用计数+1 (retain新值,release旧值)
    weak 弱引用,不持有对象,不增加引用计数(在属性所指的对象遭到推毁时,属性值也会清空)
    copy 深拷贝

    (1)atomic和nonatomic
    atomic属性是为了保证程序在多线程情况下,编译器会自动生成一些加锁代码,避免该变量的读写不同步问题。
    atomic属性内部的锁是自旋锁。自旋锁是如果有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问资源被解锁,则等待资源的线程会立即执行。

    • 大部分情况会使用nonatomic,提高效率,减少资源的消耗。

    (2)assign和weak
    assign和weak类似,在使用时,不会增加被引用对象的引用计数。weak在引用的对象被销毁后会被指向nil。而assign不会被置为nil。
    assign和unsafe_unretained其实差不多,如果用assign修饰对象,当assign指向对象被释放时,assign就成了一个悬空指针,也就是会指向一块无用内存,这是给这个assign修饰的属性发送消息时会发生崩溃(也有可能不崩溃,取决于发消息时那块内存还是否有效)。
    所以一般不使用assign修饰对象,因为用assign修饰指针会成为悬空指针导致错误。
    为什么用assign修饰基本数据类型呢,是因为基本数据类型是被分配到栈上的,栈的内存由系统自动处理,不会造成悬空指针。

    • assign用于修饰基础类型的数据(NSInteger)和C语言类型数据(int,float,double,char,bool) ,而weak只能用于修饰对象。

    (3)strong和copy
    使用strong和copy时都会使引用计数+1。但使用copy修饰的属性在某些情况下赋值的时候会创建对象的副本,也就是深拷贝。

    • 声明NSString属性时,使用copy:
      当使用NSString的时候是不希望值改变的,一般情况下使用copy,希望进行深拷贝,那么源字符串修改,就不会影响到NSString属性的值。
      但当源字符串为不可变类型是,copy也只是进行浅拷贝,当源属性为可变类型时,才进行深拷贝。
      所以建议在使用NSString属性时使用copy,避免可变字符串的修改导致一些非预期的问题。
    • 声明NSMutableString属性时,不能使用copy:
      使用copy就是深拷贝一个不可变的NSString对象,如果对这个对象进行可变操作,会产生崩溃。
    • 自定义类使用copy修饰符
      自定义类具有拷贝功能,需要实现NSCopying协议。如果自定义的对象分为可以变和不可变,需要同时实现NSCopying和NSMutableCopying协议。
    #import <Foundation/Foundation.h>
    @interface Person : NSObject
    @property (nonatomic,copy)NSString *name;
    @end
    
    #import "Person.h"
    @interface Person()<NSCopying>
    @end
    @implementation Person
    - (id)copyWithZone:(nullable NSZone *)zone{
        Person *model = [[Person class] allocWithZone:zone];
        model.name = [_name copy];
        return model;
    }
    @end
    
    #import "ViewController.h"
    #import "Person.h"
    @interface ViewController ()
    @end
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        Person *person1 = [[Person alloc] init];
        person1.name = @"郑";
           
        Person *person2 = [person1 copy];
        person2.name = @"吴";
        
        NSLog(@"person1 == %@",person1.name);//郑,深拷贝,person1不会被改变
    }
    @end
    
    三、成员变量、实例变量和属性区别

    1.成员变量

    • 定义在{}中的变量
    • 成员变量用于类内部,无需与外界接触的变量
    @interface ViewController ()
    {
        //成员变量
        Person *person;
        int a;
    }
    @end
    

    2.实例变量

    • 如果成员变量的数据类型是一个类,则成这个变是实例变量
    • 实例变量+基本数据类型变量=成员变量
    @interface ViewController ()
    {
        //成员变量--实例变量
        Person *person;
        //成员变量--基本数据类型变量
        int a;
    }
    @end
    

    3.属性

    • @ property前缀的
    • 编译器会自动生成setter和getter方法和带下划线的实例变量
    • 可以通过点语法访问属性,编译器会把点语法转换为对setter和getter方法的调用
    • 属性变量可用于与其他对象交互的变量
    • 属性变量可以用修饰符(strong、assign、weak等)修饰
    @interface ViewController ()
    //属性
    @property(nonatomic,strong)Person *person;
    @end
    

    相关文章

      网友评论

          本文标题:iOS 底层探索-属性

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