美文网首页
iOS —— 属性及其特质

iOS —— 属性及其特质

作者: Hsusue | 来源:发表于2019-01-07 23:45 被阅读14次

【从历年weak看iOS面试】
2013年
面试官:代理用weak还是strong?
我 :weak 。
面试官:明天来上班吧。

2014年
面试官:代理为什么用weak不用strong?
我 : 用strong会造成循环引用。
面试官:明天来上班吧。

2015年
面试官:weak是怎么实现的?
我 :weak其实是 系统通过一个hash表来实现对象的弱引用
面试官:明天来上班吧。

2016年
面试官:weak是怎么实现的?
我 :runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。
面试官:明天来上班吧。

2017年
面试官:weak是怎么实现的?
我 : 1 初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2 添加引用时:objc_initWeak函数会调用 storeWeak() 函数, storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3 释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
面试官:明天来上班吧。

2018年
面试官:weak是怎么实现的?
我 :跟2017年说的一样,还详细补充了objc_initWeak, storeWeak, clearDeallocating的实现细节。
面试官:小伙子基础不错。13k ,996干不干?干就明天来上班。。 下一个。

2019年
面试官:weak是怎么实现的?
我 : 别说了,拿纸来,我动手实现一个。
面试官:等写完后,面试官慢悠悠的说,小伙子不错,我考虑考虑,你先回去吧。


property的特质分五类@property(是否可空, 原子性, 读写权限, 内存管理语义, 存取方法名) ,本文将逐一探究。

@property 本质

@property = ivar + setter + getter

例如如下代码

  @property NSString *name;

编译器会自动编写访问这些属性所需的方法,并且添加一个_name的实例变量。这个过程称为“自动合成”(autosynthesis)。

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

如果想改实例变量的名字,可以采用@synthesize name = _changeName;,用_changeName来代替_name,但一般情况下无需改变。

如果不想让编译器自动合成存取方法和实例变量,可以打如下代码。

@interface ClassName () {
    NSString *_name;
}

@property NSString *name;

@end

@implementation ClassName

@dynamic name;
@end

既然写上@dynamic,实例变量及存取方法都失效,那么@property不就多此一举了。并不是这样的,属性的特质还会生效。


属性的特质

@property(是否可空, 原子性, 读写权限, 内存管理语义, 存取方法名)

这些特质会影响到系统自动生成的存取方法。

是否可空

有四个关键字:

  • nullable:可空。
  • nonnull:不可空。
  • null_resettable:get方法不能返回空,set方法可以为空。
  • null_unspecified:不确定是否为空。

这几个关键字是苹果在iOS 9.0引入的一个Objective-C的新特性:nullability annotations。利用这种特性,我们能减少同事之间的沟通成本,提前避免空崩溃的情况。

注意它只能修饰对象,不能修饰基本数据类型。

其有三种写法(null_resettable只有一种),但笔者习惯第一种写在括号里。方法的参数写法类似。

@property(nullable) NSString *name;
@property NSString *_Nullable name;
@property NSString *__nullable name;

 @property (null_resettable) NSString * name;


如果需要每个属性或每个方法的参数和返回值都去指定nonnull和nullable,是一件非常繁琐的事。苹果为了减轻我们的工作量,专门提供了两个宏:NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END。在这两个宏之间定义的所有对象属性和方法参数默认都是nonnull

image.png

原子性

有两个关键字,默认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 属性

读写权限

有两个关键字,默认readwrite

  • readwrite:读写。
  • readonly:只读。

在《Effective Objective-C 2.0》第27条,有这么一句。

把某个属性对外公开为只读属性,然后在"class-continuation分类"中将其重新定义为读写属性。

相关代码如下:

@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@end

@interface EOCPerson () 
@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;
@end

内存管理语义

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

  • assign:“设置方法”只会执行针对“纯量类型”(基本数据类型)的简单赋值操作。
  • strong:此特质表明该属性定义了一种“拥有关系”。为这种属性设置新值时,设置方法会先保留新值,并释放旧值,然后再将新值设置上去。
  • weak:此特质表明该属性定义了一种“非拥有关系”。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同aasign类似,然而再属性所指的对象遭到摧毁时,属性值也会清空。
  • unsafe_unretained:此特质的语义和assign相同,但是它适用于“对象类型”,该特质表达一种“非拥有关系”,当目标对象遭到摧毁时,属性值不会自动清空,这一点与weak有区别。
  • copy:此特质所表达的所属关系与strong类似。然而设置方法并不保留新值,而是将其“拷贝”。

ARC(自动引用计数)

要理解内存管理语义,就必须和ARC联系起来。

对象操作 Objective-C 方法
生成并持有对象 alloc/new/copy/mutableCopy等方法
持有对象 retain方法
释放对象 release方法
废弃对象 dealloc方法

苹果是通过引用计数表管理引用计数的,这图将帮助我们更好理解。


autorelease

NSAutoreleasePool对象:暂时持有放进池中的对象,当自身废弃时,池中的对象都释放一次。(底层通过栈放进对象,入栈时持有,池废弃前出栈且释放)

  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  
  id obj = [[NSObject alloc] init];

  [obj autorelease];
  // obj被最近的一个池持有
  
  [pool drain];
  // 释放池中暂时持有的对象

在开发中,我们很少用到NSAutoreleasePool,因为主循环NSRunLoop会对NSAutoreleasePool对象进行生成、持有和废弃处理。

image.png

例如以下代码,读入大量图像的同时改变其尺寸,系统会自动生成。

  for (int i = 0; i < 图像数; ++i) {
    /*
     * 读入图像
     * 大量产生 autorelease 的对象
     * 由于没有废弃NSAutoreleasePool 对象
     * 最终导致内存不足!
    */
  }

但我们处理完每张图像后,可以释放掉,所以这时我们应该手动创建NSAutoreleasePool对象。

   for (int i = 0; i < 图像数; ++i) {

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    /*
     * 读入图像
     * 大量产生 autorelease 的对象
    */

    [pool drain];

    /*
     * 通过[pool drain],
     * autorelease 的对象被一齐release。
    */
  }

注意 : 在ARC下, 不能使用NSAutoreleasePool这个类来创建自动释放池, 而应该用@autoreleasepool { } 这个block。官方文档说明, 使用@autoreleasepool这个block比NSAutoreleasePool更高效!并且在MRC环境下同样适用。

NSAutoreleasePool pool = [[NSAutoreleasePool alloc] init];
[pool release];

// 用以下代码代替上述代码 :

@autoreleasepool {
}

内存管理语义关键字的使用及区别

先来了解堆和栈。

  • 栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
  • 堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。需要程序员自己申请,并指明大小(iOS生成对象时系统底层会算好大小)。

由此可见,基本数据类型放在栈中,对象放在堆中。

assign

基本数据类型用 assign 修饰。

对象通常不能用assign修饰,虽然编译器不会报错。

  • 为什么基本数据类型能用assign,对象不能用?

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

strong 和 weak

strong的对象会持有对象,而weak不会持有对象。举个例子。

@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);
}

对于weakViewA,指向初始化的对象,由于引用数为0,且没有强指针,所以马上就会被废弃,故为空。

对于weakViewB,由于有tempView引用着生成的对象,所以weakViewB当时还是有值的。当过了viewDidLoad方法后,tempView这个局部变量将被释放,对象也同时废弃。所以到了touchBegan后,又变回空了。

对于strongView,一直都又强指针引用着生成的对象,当然不会被释放。

  • 这里再引申一下,为什么IBOutlet属性是weak。

原因:在xib中,控件会被加进控制器的view的subviews中,已经有强引用了。

类似的,对上面提到的weakViewB,笔者看过很多旧代码会这么写,道理是一样的。

    UIView *tempView = [[UIView alloc] init];
    [self.view addSubview:tempView];
    self.weakViewB = tempView;

unsafe_unretained

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

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

copy

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

  • copy和strong有什么区别?
  1. 对于不可变字符串
@property (nonatomic, strong) NSString *sStr;
@property (nonatomic, copy) NSString *cStr;

    NSString *tempStr = @"str";
    self.sStr = tempStr;
    self.cStr = tempStr;
    NSLog(@"tempStr = %p", tempStr);
    NSLog(@"sStr = %p", self.sStr);
    NSLog(@"cStr = %p", self.cStr);
  1. 对于可变字符串
    NSMutableString *tempStr = [NSMutableString stringWithString:@"mutableStr"];
    self.sStr = tempStr;
    self.cStr = tempStr;
    NSLog(@"tempStr = %p", tempStr);
    NSLog(@"sStr = %p", self.sStr);
    NSLog(@"cStr = %p", self.cStr);

可以看出对于不可变字符串,都是一样的,都持有原对象;对于可变字符串,strong持有原对象,copy的字符串新生成了一个可变对象。这也就是深浅拷贝的原理,不理解的可以看这篇文章iOS 图文并茂的带你了解深拷贝与浅拷贝

即存的方法是这样的。

- (void)setCStr:(NSString *)cStr {
    _cStr = [cStr copy];
}

总之,当我们声明属性时,如果不希望它因为源对象(当源对象为可变对象时)的改变而改变,则用copy修饰。

存取方法名

  • getter=<name>
  • setter=<name>

可以重设方法名,但存方法名不建议修改。修改取方法名常用在BOOL类型属性中,如下:

  @property (nonatomic, getter=isOn) BOOL on;

参考

相关文章

  • iOS —— 属性及其特质

    【从历年weak看iOS面试】2013年面试官:代理用weak还是strong?我 :weak 。面试官:明天来上...

  • iOS 属性特质

    * assign 设置方法只会执行针对“纯量类型” 的简单赋值操作,如CGFloat,NSInteger * st...

  • Hi小姐复盘Day84―得到.永远幸福的方法

    一、特质的属性及其预测功能 1.特质是指人身上独有和固有的品性 能力方面的特质包括迟钝还是聪明。性格方面的特质包括...

  • iOS开发属性特质

    原子性 atomic (原子性)(系统默认): 使用同步锁。能够保证赋值和获取是线程安全的。但不能保证操作和访问...

  • 【已解决】IOS/Mac OS X开发中nonatomic代表什

    问题 网上流传的IOS或者Mac OS X源码内,某些属性特质会被声明为nonatomic,nonatomic代表...

  • 属性特质

    使用属性时还有一个问题要注意,就是其各种特质设定也会影响编译器所生成的存取方法,属性可以拥有的特质分为四类: 原...

  • runtime 如何实现 weak 属性?

    原文:iOS面试题大全 weak 此特质表明该属性定义了一种「非拥有关系」(nonowning relations...

  • iOS中的数据存储(上)

    摘要 本文介绍iOS中常用的应用数据存储方式及其详细用法;本章介绍: XML属性列表(plist)归档 , Pre...

  • 编写高质量iOS与OS X代码的52个有效方法(二)

    对象、消息、运行时 理解“属性”的概念 属性的基础用法就不多叙述了属性特质 属性拥有的特质分为4类 1、原子性 a...

  • 细读属性特质

    “属性”(property)是Objective-C的一项特性,用于封装对象中的数据。使用属性之后,编译器会自动写...

网友评论

      本文标题:iOS —— 属性及其特质

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