iOS 底层探索-属性

2020-05-28  本文已影响0人  搬砖的crystal

一、属性简介

属性是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;
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;
}   
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属性内部的锁是自旋锁。自旋锁是如果有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问资源被解锁,则等待资源的线程会立即执行。

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

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

#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.属性

@interface ViewController ()
//属性
@property(nonatomic,strong)Person *person;
@end
上一篇 下一篇

猜你喜欢

热点阅读