属性

作者: 飞哥漂流记 | 来源:发表于2020-05-25 00:17 被阅读0次

    什么是属性?

    属性=成员变量+set方法+get方法

    我们在OC中用@property来声明一个属性,编译器会自动为你的实例变量生成setter和getter方法。添加实例变量是有一个前提的,就是对象还没有同名的成员变量,就是如果已经有_str了,就不再添加了。

    实际上,一个类经过编译后,会生成变量列表ivar_list,方法列表method_list,每添加一个属性,在变量列表ivar_list会添加对应的变量,如_name,方法列表method_list中会添加对应的setter方法和getter方法。

    也就是说我们每次在增加一个属性,系统都会在ivar_list中添加一个成员变量的描述,在method_list中增加setter与getter方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出setter与getter方法对应的实现,在setter方法中从偏移量的位置开始赋值,在getter方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转.

    我们可以用运行时验证一下。

    #import "ViewController.h"

    #include <objc/runtime.h>

    @interface ViewController ()

    @property (nonatomic, strong) NSString *str;

    @end

    @implementation ViewController

    - (void)viewDidLoad {

        [super viewDidLoad];

        unsigned int count = 0;

        Ivar *varList = class_copyIvarList([self class], &count);

        for (unsigned int i = 0; i < count; i++) {

            const char *varName = ivar_getName(varList[i]);

            printf("成员变量-------%s\n",varName);

        }

        Method *methodList = class_copyMethodList([self class], &count);

        for (unsigned int i = 0; i < count; i++) {

            SEL methodName = method_getName(methodList[i]);

    NSLog(@"方法-------%@",NSStringFromSelector(methodName));

        }

    }

    @end

     打印的日志:

    成员变量-------_str

    2018-07-13 10:32:09.115049+0800 TestAppIOS[445:214283] 方法-------str

    2018-07-13 10:32:09.115161+0800 TestAppIOS[445:214283] 方法-------setStr:

    2018-07-13 10:32:09.115233+0800 TestAppIOS[445:214283] 方法-------.cxx_destruct

    2018-07-13 10:32:09.115264+0800 TestAppIOS[445:214283] 方法-------viewDidLoad

    同时我们可以自定义属性的存取方法,如果我们定义了一个,编译器还是会为我们生成另一个。例如,我们自定义get方法, 编译器就会为我们生成set方法。如果我们同时自定义了属性的存取方法,编译器就不会为对象声明实例变量了。


    @synthesize:

    1.在MRC下,@synthesize str这样,编译器才会自动合成str的存取方法。不过在ARC下就不必了,无论你是否@synthesize str,编译器都会自动合成str的存取方法。

    2.如果你声明的属性是str,系统自动给你添加的成员变量是_str,如果你对这个变量名字不满,可以这样@synthesize str = mystr;,自己给个名字。这样系统给添加的成员变量就是myStr,而不是_str,但是变量的存取方法没有变化。不过我建议最好不要这么办,因为都按照约定成俗的方式来命名变量,代码的可读性较高,大家都理解,所以我建议大家最好不要用这个关键字。

    @dynamic:

    @dynamic 关键字主要是告诉编译器不用为我们自动合成变量的存起方法, 我们会自己实现。即使我们没有实现,编译器也不会警告,因为它相信在运行阶段会实现。如果我们没有实现还调用了,就会报这个错误'-[ViewController setStr:]: unrecognized selector sent to instance 0x10040af10'。

    如果我们在子类中重写父类的属性,就会报下面的警告

    Autoproperty synthesis will not synthesize property'str';it will be implemented by its superclass,use @dynamicto acknowledge intention

    因为我们同时在父类和子类中同时声明了str的属性,系统就不知道该在哪里(父类Father还是子类Son?)自动合成str的存取方法,系统默认是在父类中声明,因为子类可以调用。不过,系统希望我们显式的声明这一点,这样有利于提高代码的可读性。


    属性关键字:

    读写权限:readonly,readwrite

    readwrite:读写。

    readonly:只读。

    属性默认都是readwrite的,表示可读可写,set/get方法编译器都会自动合成。如果用readonly修成属性,表示该属性是只读的,编译器只会自动合成get方法, 不会合成set方法。另外,我们可以在.h文件中用readonly中修饰属性,在.m文件类扩展中用readwrite修饰同一个属性,这样来防止外界篡改该属性。

    原子性:nonatomic,atomic

    nonatomic:非原子性。

    atomic:原子性。

    atomic 和 nonatomic 的区别在于,系统自动生成的 getter/setter 方法不一样。对于atomic的属性,系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。比如,线程 A 的 getter 方法运行到一半,线程 B 调用了 setter:那么线程 A 的 getter 还是能得到一个完好无损的对象。系统采用自旋锁,对资源进行保护。

    然而,即使用atomic也不能保证绝对的线程安全。举例来说,假设有一个线程A在不断的读取属性name的值,同时有一个线程B修改了属性name的值,那么即使属性name是atomic,线程A读到的仍旧是修改后的值。

    简单点说,只保证setter和getter的操作完整性,不保证属性的线程安全。

    除了不能保证线程安全,atomic还会消耗系统资源,因此,开发iOS程序时一般都会使用 nonatomic 属性

    内存管理语义:strong,weak,assign,copy, retain,unsafe_unretained

    有六个关键字,对象默认strong,基本数据类型默认assign。

    assign

    基本数据类型用 assign 修饰,如果assign修饰的对象,当对象废弃之后,对象会变为野指针,不知道指向哪,再向该对象发消息,非常容易崩溃。栈上空间的分配和回收都是系统来处理的,因此开发者无需关注,也就不会产生野指针的问题。对象是在堆上的,所以对象不能用。

    strong

    strong的对象会持有对象, 表示一种“拥有关系”。为属性设置新值的时候,设置方法会先保留新值(新值的引用计数加一),并释放旧值(旧值的引用计数减一),然后将新值赋值上去。

    weak

    weak不会持有对象,表示一种“非拥有关系”。用weak修饰属性的时候,为属性设置新值的时候,设置方法既不会保留新值(新值的引用计数加一),也不会释放旧值(旧值的引用计数减一)。当属性所指的对象释放的时候,属性也会被置为nil。用于修饰UI控件,代理(delegate)。

    @property (nonatomic, weak) UIView *weakViewA;

    @property (nonatomic, weak) UIView *weakViewB;

    @property (nonatomic, strong) UIView *strongView;

    - (void)viewDidLoad {

        [super viewDidLoad];

        self.weakViewA = [[UIView alloc] init];

        NSLog(@"weakViewA = %@", self.weakViewA);

        UIView *tempView = [[UIView alloc] init];

        self.weakViewB = tempView;

        NSLog(@"weakViewB = %@", self.weakViewB);

        self.strongView = [[UIView alloc] init];

        NSLog(@"strongView = %@", self.strongView);   

    }

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

        NSLog(@"touchesBegan-----weakViewA = %@", self.weakViewA);

        NSLog(@"touchesBegan-----weakViewB = %@", self.weakViewB);

        NSLog(@"touchesBegan-----strongView = %@", self.strongView);

    }

    打印结果:

    copy

    通常,可变对象属性修饰符使用strong,不可变对象属性修饰符使用copy。

    copy修饰的属性设置新值的时候,当新值是不可变的,和strong是一模一样的。当新值是可变的(开头是NSMutable),设置方法不会保留新值(新值的引用计数加一),而是对新值copy一份,不会影响新值的引用计数。copy常用来修饰NSString,因为当新值是可变的,防止属性在不知不觉中被修改。

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

    使用场景:

    一般情况下,copy可以用于对不可变容易的属性修饰中,主要是NSArray /NSDictionary/NSString, 也可以用来修饰block。

    在MRC和ARC下都可以使用。

    其setter方法,与retain处理流程一样,先旧值release,再copy出新的

    retain

    使用场景:

    一般情况下,retain用在MRC情况下,被retain修饰的对象,引用计数retainCount要加1的。

    retain只能修饰oc对象,不能修饰非oc对象,比如说CoreFoundation对象就是C语言框架,它没有引用计数,也不能用retain进行修饰。

    retain一般用来修饰非NSString 的NSObject类和其子类。

    unsafe_unretained

    在iOS5.0后,基本被weak替代。但是weak会额外消耗资源(后面说到weak原理会提到),所以并不全优于unsafe_unretained。

    unsafe_unretained,不安全的所有权修饰符,和weak一样不会持有对象。其修饰的指针又名悬垂指针,当指针指向的对象被废弃时,指针成为了野指针,处理不好会导致程序崩溃。这点和weak不一样,weak指针在对象废弃时,会置为nil。

    和assing修饰对象的时候是一模一样的。为属性设置新值的时候,设置方法既不会保留新值(新值的引用计数加一),也不会释放旧值(旧值的引用计数减一)。唯一的区别就是当属性所指的对象释放的时候,属性不会被置为nil,这就会产生野指针,所以是不安全的


    面试题:

    1.属性关键字有哪些?

    原子性--- atomic nonatomic特质

    读/写权限--- readwrite(读写),readonly (只读)

    内存管理语义--- assign,strong,weak,unsafe_unretained,copy

    2. 如何让自定义类可以用 copy 修饰符?如何重写带 copy 关键字的 setter?

    如果想让自己的类具备copy方法,并返回不可边类型,必须遵循nscopying协议,并且实现

    - (id)copyWithZone:(NSZone *)zone

    如果让自己的类具备mutableCopy方法,并且放回可变类型,必须遵守NSMutableCopying,并实现- (id)mutableCopyWithZone:(nullable NSZone *)zone

    注意:再此说的copy对应不可边类型和mutableCopy对应不可边类型方法,都是遵从系统规则而已。如果你想实现自己的规则,也是可以的。

    3.用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?

    众所周知,我们知道,可变类型(NSMutableArray,NSMutableString等)是不可边类型(NSString,NSArray等)的子类,因为多态的原因,我们可以使用不可边类型去接受可变类型。

    1.当我们使用strong修饰A不可边类型的时候,并且使用B可变类型给A赋值,再去修改可变类型B值的时候,A所指向的值也会发生改变。引文strong只是让创建的对象引用计数器+1,并返回当前对象的内容地址,当我们修改B指向的内容的时候,A指向的内容也同样发生了改变,因为他们指向的内存地址是相同的,是一份内容。

    2.当我们使用copy修饰A不可边类型的时候,并且使用B可变类型给A赋值,再去修改可变类型B值的时候,A所指向的值不会发生改变。因为当时用copy的修饰的时候,会拷贝一份内容出来,并且返回指针给A,当我们修改B指向的内容的时候,A指向的内容是没有发生改变的。因为A指向的内存地址和B指向的内存地址是不相同的,是两份内容

    3.copy修饰不可边类型(NSString,NSArray等)的时候,且使用不可边类型进行赋值,表示浅拷贝,只拷贝一份指针,和strong修饰一样,当修饰的是可变类型(NSMutableArray,NSMutableString等)的时候,且使用可边类型进行赋值,表示深拷贝,直接拷贝新一份内容,到内存中。表示两份内

    4.@synthesize 和 @dynamic 分别有什么作用?

    如果@synthesize和@dynamic都没写,那么替代的就是@syntheszie var = _var;

    @dynamic :修饰的属性,其getter和setter方法编译器是不会自动帮你生成。必须自己是实现的。

    @synthesize:修饰的属性,其getter和setter方法编译器是会自动帮你生成,不必自己实现。且指定与属性相对应的成员变量。

    5.为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?

    代理是使用weak来修饰的。1.使用weak是为了避免循环引用。2.当使用weak修饰的属性,当对象释放的时候,系统会对属性赋值nil,object-c有个特性就是对nil对象发送消息也就是调用方法,不会cash。

    delegate:传递的是事件(even),代理可以让A对象通知B对象,我(A)发生的变化,前提B遵循了A的代理,并且实现了A的代理方法。

    dataSource: 传递的是数据。如果A对象声明了数据源,当我们创建A对象的时候,我们就该实现数据源,来告诉A,他所需要的一些数据。例如:tableView数据源方法,需要告诉它,我要实现几组cell,每组cell多少行cell,实现的cell什么样式,什么内容

    相同点:代理和Block大多是我们都可以用来做倒序传值的。我们都得注意避免循环引用。不然我们去使用代理还是Block的时候,都需要判断它们是否实现

    不同点:代理使用weak修饰,代理必须先声明方法。当我们调用代理的时候要判断是否已经实现。

    block:使用的是copy来修饰,block保存的是一段代码,其实也就是一个函数。并且可以自动捕捉自动变量,如果想修改此自动变量,还必须使用__block修饰。

    6.weak置为nil是如何实现的?

    runtime对注册的类,会进行布局,对于弱对象会加入一个哈希表中。用弱指向的对象内存地址作为键,当此对象的引用计数为0的时候会dealloc,如果弱指向对象的内存地址是a,那么就会以a为键,在这个弱表中搜索,找到所有以a为键的弱对象,从而设置为nil。

    runtime 如何实现 weak 属性具体流程大致分为 3 步:

    1、初始化时:runtime 会调用 objc_initWeak 函数,初始化一个新的 weak 指针指向对象的地址。

    2、添加引用时:objc_initWeak 函数会调用 objc_storeWeak() 函数,objc_storeWeak() 的作用是更新指针指向(指针可能原来指向着其他对象,这时候需要将该 weak 指针与旧对象解除绑定,会调用到 weak_unregister_no_lock),如果指针指向的新对象非空,则创建对应的弱引用表,将 weak 指针与新对象进行绑定,会调用到 weak_register_no_lock。在这个过程中,为了防止多线程中竞争冲突,会有一些锁的操作。

    3、释放时:调用 clearDeallocating 函数,clearDeallocating 函数首先根据对象地址获取所有 weak 指针地址的数组,然后遍历这个数组把其中的数据设为 nil,最后把这个 entry 从 weak 表中删除,最后清理对象的记录

    7.@protocol 和 category 中如何使用 @property?

    在协议中使用属性只会生成setter和getter方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性

    类别使用@property也是也会生成setter和getter方法的声明,如果我们真的需要给类别增加属性的实现,需要生成运行时的两个函数:

    objc_setAssociatedObject

    objc_getAssociatedObject

    8.assign可以修饰OC对象吗?

    不可以 

    9.属性修饰符atomic的内部实现是怎么样的?能保证线程安全吗?

    10.SideTable的结构是什么样的?最好可以画出来?

    11.@peoperty的本质是什么?ivar getter setter是如何生成并添加到类中?

    @property = ivar + getter + setter;

    大致生成了五个东西

    OBJC_IVAR_$类名$属性名称 :该属性的“偏移量”(偏移量),这个替换量是“硬编码”(硬编码),表示该变量距离放置对象的内存区域的起始地址有多远。

    setter与getter方法对应的实现函数

    ivar_list :成员变量列表

    method_list :方法列表

    prop_list :属性列表

    也就是说我们每次在增加一个属性,系统都会在ivar_list中添加一个成员变量的描述,在method_list中增加setter与getter方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的接近量,然后将setter与getter方法对应的实现,在setter方法中从偏移量的位置开始赋值,在getter方法中从偏移量开始取值,以便能够读取正确字节数,系统对象转变量的指针类型进行了类型强转

    12.数组copy后里面的元素会复制一份新的吗?

    不会,数组里面存的是之前对象的地址,不会改变,可以自己测试一下

    13.如何访问私有成员变量?

    通过runtime来获取对象的成员变量Ivar,然后再通过object_getIvar来获取某个对象的成员变量的值

    Ivar userNameIvar = class_getInstanceVariable([model class], "_userName");

    NSString *userName = object_getIvar(model, userNameIvar);

    14.什么情况使用 weak 关键字,相比 assign 有什么不同?

    在ARC中,在有可能出现循环引用的时候,经常要通过让其中一端使用

    自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用弱,自定义IBOutlet中控件属性一般也使用弱;

    assign: 一般用来修饰基本的数据类型,包括基础数据类型和C数据类型。assign声明的属性是不会增加引用计数的,声明的属性释放后,属性就不存在了。但是,指针没有置空过程,成为了野指针,如果新的对象被分配到了这个内存地址上,又会crash,所以一般只用来声明基本的数据类型。

    weak:弱引用,不增加引用计数。防止循环引用时使用,并且在所指向的对象被释放之后,weak指针会被设置的为nil。weak引用通常是用于处理循环引用的问题,如代理及block的使用中,相对会较多的使用到weak。

    只可以修饰对象。如果修饰基本数据类型,编译器会报错-“Property with ‘weak’ attribute must be of object type”。

    当属性用weak修饰时,会将该指针存入一个weak表,该表是一个hash表,obj为键值,存储了一组obj_weak的地址。当属性使用weak修饰时,性能开销较大。

    15.怎么用 copy 关键字?

    NSString,NSArray,NSDictionary等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString,NSMutableArray,NSMutableDictionary;

    块也经常使用copy关键字,

    block使用copy是从MRC遗留下来的“传统”,在MRC中,方法内部的block是在栈区的,使用copy可以把它放到堆区。

    在ARC中写不写都行:

    在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上

    16. 这个写法会出什么问题: @property (copy) NSMutableArray *array?

    两个问题:1,添加,删除,修改复制内部的元素的时候,程序会因为找不到对应的方法而崩溃。因为copy就是复制一个不可变NSArray的对象; 2,使用了atomic属性会严重影响性能

    18.@property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?默认的是哪些

    属性是描述类的特征,也就是具备什么特性。三个部分,带下划线的成员变量,get、setter方法。

    默认关键字:readwrite,assign, atomic; -- 是针对基本类型(NSInteger, BOOL, NSUInteger, int, 等)

    但是针对引用类型, 默认:strong, readwrite, atomic (例如:NSString, NSArray, NSDictory等)

    19. @synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?

    实例变量=成员变量= ivar 

    在类的实现代码里通过@synthesize语法来指定实例变量的名字:

    @实现CYLPerson @synthesize firstName = _myFirstName; @synthesize lastName = _myLastName; @结束

    语法上述会将生成的实例变量命名为_myFirstName与_myLastName,而不再使用默认的名字

    总结下@synthesize合成实例变量的规则,有以下几点:

    1.  如果指定了成员变量的名称,会生成一个指定的名称的成员变量,

    2. 如果这个成员已经存在了就不再生成了。

    3.  如果是@synthesize foo;即将生成一个名称为foo的成员变量,则:

    如果没有指定成员变量的名称会自动生成一个属性同名的成员变量,

    4.  如果是@synthesize foo = _foo;就不会生成成员变量了。

    20. 在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?

    我们要搞清楚一个问题,什么情况下不会autosynthesis(自动合成)?

    1. 同时重组了setter和getter时

    2. 重建了预期属性的getter时

    3. 使用了@dynamic时

    4. 在@protocol中定义的所有属性

    5. 在类别中定义的所有属性

    6. 重写(overridden)的属性

    21. 讲一下atomic的实现机制;为什么不能保证绝对的线程安全(最好可以结合场景来说)?

    atomic会对属性的setter/getter方法进行加锁,这仅仅只能保证在操作setter/getter方法是安全的。不能保证其他线程的安全

    例如:线程1调用了某一属性的setter方法并进行到了一半,线程2调用其getter方法,那么会执行完setter操作后,再执行getter操作,线程2会获取到线程1setter后的完整的值;当几个线程同时调用同一属性的setter、getter方法时,会获取到一个完整的值,但获取到的值不可控

    22.被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构可以画出来么?

    23.关联对象有什么应用,系统如何管理关联对象?其被释放的时候需要手动将所有的关联对象的指针置空么?

    24. assign可以修饰OC对象吗?

    25.具体说一下循环强引用,可以吗?

    26. 属性和成员变量的区别?

    属性对成员变量扩充了存取方法 .

    属性默认会生成带下划线的成员变量 .

    但只声明了变量,是不会有属性的,

    27.深copy和浅copy的区别?

    浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。

    copy方法:如果是非可扩展类对象,则是浅拷贝。如果是可扩展类对象,则是深拷贝。

    mutableCopy方法:无论是可扩展类对象还是不可扩展类对象,都是深拷贝。

    [immutableObject copy] //浅复制

    [immutableObject mutableCopy] //深复制

    [mutableObject copy] //深复制

    [mutableObject mutableCopy] //深复制

    28.如何访问并修改一个类的私有属性?

    有两种方法可以访问私有属性,一种是通过KVC获取,一种是通过runtime访问并修改私有属性

    29.MRC下如何重写retain修饰变量的setter方法?

    @property(nonatiomic,retain)id obj;

    -(void)setObj:(id)obj{

           if(_obj != obj){

               [_obj release];

     _obj = [obj retain];}}

    30. 

    相关文章

      网友评论

          本文标题:属性

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