iOS @property 用法

@property的作用

当我们写下@property NSObject *foo时,编译器帮我们做了以下几件事:

  • 创建实例变量_foo
  • 声明foo属性的setter、getter方法
  • 实现foo属性的setter、getter方法

@property 的本质是什么?

@property = ivar + getter + setter;

实例变量+get方法+set方法,也就是说使用@property 系统会自动生成setter和getter方法;


@property的属性关键字详解

atomic和nonatomic

  1. atomic和nonatomic用来决定编译器生成的getter和setter是否为原子操作。

  2. atomic:系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。getter 还是能得到一个完好无损的对象(可以保证数据的完整性),但这个对象在多线程的情况下是不能确定的。

    • 举个🌰

    如果线程 A 调了 getter,与此同时线程 B 、线程 C 都调了 setter——那最后线程 A get 到的值,有3种 可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值。同时,最终这个属性 的值,可能是 B set 的值,也有可能是 C set 的值。所以atomic可并不能保证对象的线程安全。

    • 也就是说:如果有多个线程同时调用setter的话,不会出现某一个线程执行完setter全部语句之前,另一个线程开始执行setter情况,相当于函数头尾加了锁一样,每次只能有一个线程调用对象的setter方法,所以可以保证数据的完整性。

    • atomic所说的线程安全只是保证了getter和setter存取方法的线程安全,并不能保证整个对象是线程安全的。

  3. nonatomic:就没有这个保证了,nonatomic返回你的对象可能就不是完整的value。因此,在多线程的环境下原子操作是非常必要的,否则有可能会引起错误的结果。但仅仅使用atomic并不会使得对象线程安全,我们还要为对象线程添加lock来确保线程的安全。

  4. nonatomic的速度要比atomic的快。atomic是Objc使用的一种线程保护技术,这种机制是耗费系统资源的,所以在iPhone这种小型设备上,我们基本上都是使用nonatomic,而对象的线程安全问题则由程序员代码控制。

  5. atomic与nonatomic的本质区别其实也就是在setter方法上的操作不同

nonatomic对象、atomic对象setter和getter方法的实现:

/// nonatomic对象
- (void)setCurrentImage:(UIImage *)currentImage
{
    if (_currentImage != currentImage) {
        [_currentImage release];
        _currentImage = [currentImage retain];
    }
}
- (UIImage *)currentImage
{
    return _currentImage;
}


/// atomic对象
- (void)setCurrentImage:(UIImage *)currentImage
{
    @synchronized(self) {
        if (_currentImage != currentImage) {
            [_currentImage release];
            _currentImage = [currentImage retain];
        }
    }
}

- (UIImage *)currentImage
{
    @synchronized(self) {
        return _currentImage;
    }
}


assign

1.这个修饰词是直接赋值的意思 , 整型/浮点型等数据类型都用这个词修饰 .
2.如果没有使用 weak strong retain copy 修饰 , 那么默认就是使用 assign 了.
3.当然其实对象也可以用 assign 修饰 , 只是对象的计数器不会+1 . ( 与 strong 的区别 )
4.如果用来修饰对象属性 , 那么当对象被销毁后指针是不会指向 nil 的 . 所以会出现野指针错误 . ( 与weak的区别 )


1.在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性。

2.自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。

IBOutlet连出来的视图属性为什么可以被设置成weak?

因为父控件的subViews数组已经对它有一个强引用。

不同点:

assign 可以用非 OC 对象,而 weak 必须用于 OC 对象。

weak 表明该属性定义了一种“非拥有关系”。在属性所指的对象销毁时,属性值会自动清空 nil。

weak

weak是弱引用,用weak描述修饰或者所引用对象的计数器不会加一,并且会在引用的对象被释放的时候自动被设置为nil,大大避免了野指针访问坏内存引起崩溃的情况,另外weak还可以用于解决循环引用。

weak原理概括

weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址数组。weak的底层实现的原理是什么?

Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash表,Key是所指对象的地址,value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。

为什么value是数组?因为一个对象可能被多个弱引用指针指向

weak原理实现步骤

1. 初始化时

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

示例代码:

NSObject *obj = [[NSObject alloc] init];
 id __weak obj1 = obj;

当我们初始化一个weak变量时,runtime会调用 NSObject.mm 中的objc_initWeak函数。

这个函数在Clang中的声明如下:

id objc_initWeak(id *object, id value);

而对于 objc_initWeak() 方法的实现如下:

id objc_initWeak(id *location, id newObj) {
// 查看对象实例是否有效,无效对象直接导致指针释放
    if (!newObj) {
        *location = nil;
        return nil;
    }
    // 这里传递了三个 bool 数值
    // 使用 template 进行常量参数传递是为了优化性能
    return storeWeakfalse/*old*/, true/*new*/, true/*crash*/>
    (location, (objc_object*)newObj);
}

这个函数仅仅是一个深层函数的调用入口,而一般的入口函数中,都会做一些简单的判断(例如 objc_msgSend 中的缓存判断),这里判断了其指针指向的类对象是否有效,无效直接释放,不再往深层调用函数。否则,object将被注册为一个指向value的__weak对象。而这事应该是objc_storeWeak函数干的。

注意:objc_initWeak函数有一个前提条件:就是object必须是一个没有被注册为__weak对象的有效指针。而value则可以是null,或者指向一个有效的对象。

2. 添加引用时:

objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。

objc_storeWeak的函数声明如下:

id objc_storeWeak(id *location, id value);

objc_storeWeak() 的具体实现,请参考weak弱引用实现的方式

3. 释放时:

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

当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?当释放对象时,其基本流程如下:

  1. 调用objc_release

  2. 因为对象的引用计数为0,所以执行dealloc

  3. 在dealloc中,调用了 _ objc _ rootDealloc函数

  4. 在 _ objc _ rootDealloc 中,调用了object _ dispose函数

  5. 调用objc_destructInstance

  6. 最后调用objc _ clear _ deallocating

objc _ clear _ deallocating该函数的动作如下:

  1. 从weak表中获取废弃对象的地址为键值的记录

  2. 将包含在记录中的所有附有 weak修饰符变量的地址,赋值为nil

  3. 将weak表中该记录删除

  4. 从引用计数表中删除废弃对象的地址为键值的记录


strong

在ARC环境下,只要某一对象被一个strong指针指向,该对象就不会被销毁。如果对象没有被任何strong指针指向,那么就会被销毁。在默认情况下,所有的实例变量和局部变量都是strong类型的。可以说strong类型的指针在行为上跟非ARC下的retain是比较相似的


copy

预备知识

  • 浅拷贝

    • 只是将对象内存地址多了一个引用,也就是说,拷贝结束之后,两个对象的值不仅相同,而且对象所指的内存地址都是一样的。
  • 深拷贝

    • 拷贝一个对象的具体内容,拷贝结束之后,两个对象的值虽然是相同的,但是指向的内存地址是不同的。两个对象之间也互不影响,互不干扰。

copy的作用

  • 在非集合类对象中,对不可变对象进行copy操作,只仅仅是指针复制——浅复制,进行mutableCopy操作,是内容复制——深复制。

  • 对于不可变的集合类对象进行copy操作,只是改变了指针,其内存地址并没有发生变化;进行mutableCopy操作,内存地址发生了变化,但是其中的元素内存地址并没有发生变化。

  • 对于可变集合类对象,不管是进行copy操作还是mutableCopy操作,其内存地址都发生了变化,但是其中的元素内存地址都没有发生变化,属于单层深拷贝。

使用注意:

  1. 当将一个可变对象分别赋值给两个使用不同修饰词的属性后,改变可变对象的内容,使用strong修饰的会跟随着改变,但使用copy修饰的没有改变内容。

那么,是不是NSMutableString等这些可变对象是不是也需要copy来修饰呢?答案是千万不要这么干,我们可以测试一下:

@interface test()
 
@property (nonatomic, copy) NSMutableString *strCopy;
 
@end
 
/********************* test.m **********************/
NSMutableString *string = [NSMutableString stringWithFormat:@"abc"];
self.strCopy = string;
[self.strCopy appendString:@"def"]; // 在这一行会crash

因为copy是复制出一个不可变的对象,在不可变对象上运行可变对象的方法,就会找不到执行方法

 


作者:Jt_Self
链接:https://www.jianshu.com/p/3709cf8f8937
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

posted on 2019-07-02 09:18  唐伟明  阅读(567)  评论(0编辑  收藏  举报

导航