IOS 属性及关键词汇总

最近统一整理了一下ios中属性和关键词的一些知识点,这些问题不管是面试题还是面试官都是经常会问到的,很多人都是会用,但是被问到的时候答不上来,这就会造成一个尴尬的局面,所以这些东西还是需要我们认真理解和掌握的,这样我们才能更好的使用。
我还整理的一些其他方面的,我会陆续在简书中更新。如果有写得不对的地方和理解不到位的地方,欢迎大家指正,跟大家一块学习(欢迎评论区交流)。(整理来自一些书籍、博客等)

问题汇总目录(先看看自己能答出几道):
1.0 属性的关键字
2.0 浅拷贝和深拷贝的区别
3.0 copy和strong的区别
4.0 为什么不可变对象要用copy?
5.0 assign可以用于OC对象吗?(野指针)
6.0 weak如何实现自动赋nil
7.0 swift中如何理解copy-on-wirte?
8.0 swift中,如何在结构体、enum、extension实例方法中修改成员变量?
9.0 请你讲讲@proprety关键字的作用
10.0 这个写法会出什么问题: @property (copy) NSMutableArray *array?
11.0 @synthesize合成实例变量的规则是什么?假如property名为person,存在一个名为_ person的实例变量,那么还会自动合成新变量么?
12.0 在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?
13.0 @synthesize和@dynamic分别有什么作用?
14.0 @protocol 和 category 中如何使用 @property
15.0 __block和 __weak的区别和使用
16.0 __block的实现原理你知道吗?
17.0 block的实质是什么?有几种block?分别是怎样产生的?
18.0 代理和block的比较

1.0 属性的关键字

属性的关键字不管是在笔试还是面试的过程中经常会被问到,但很多人只是会使用,关于它的定义和使用方式的理解是不到位的,我觉得要想更好的使用这些关键字,这些是需要完全理解和掌握的。接下来我们就详细的说一下属性的关键字都有哪一些。

属性的关键字分为三类:
  • 表示原子性的(也就是线程安全的)
    atomic:修饰的对象会保证setter和getter的完整性,任何线程访问它都可以得到一个完整的初始化的对象。因为要保证线程的安全,所以速度会比较慢。atomic比nonatomic安全,但也不是绝对安全的,他有一个特点是单写多读,就是保证同一时间只有一个线程执行setter方法,但是可以有多个线程执行getter方法,因为在它的setter有一把自旋锁。所以它的getter方法不是线程安全的。
    nonatomic:修饰的对象不保证setter和getter的完整性,所以,当多个线程访问它时,它可能返回未初始化的对象。但是我们一般都用的nonatomic,因为atomic的线程安全开销太大,影响性能,即使需要保证线程安全,我们也可以通过自己的代码控制,而不用atomic。
  • 表示引用计数的
    strong:表示指向并拥有该对象。其修饰的对象引用计数会增加1。该对象引用计数为零时才会被销毁。当然,强行将其设为nil也可以销毁它。
    weak:表示指向但不拥有该对象。其修饰的对象引用计数不会增加。无须手动设置,该对象会自行在内存中销毁。
    assign:修饰基本数据类型,例如CGFloat等,这些类型不是对象,是有栈进行进行内存管理的。
    copy:建立一个引用计数为1的新对象,赋值时对传入值进行一份拷贝,所以使用copy关键字的时候,你将一个对象复制给该属性,该属性并不会持有那个对象,而是会创建一个新对象,并将那个对象的值拷贝给它。而使用copy关键字的对象必须要实现NSCopying协议。
    unsafe_unretained(IOS5前):跟 weak 类似,声明一个弱引用,但是当引用计数为 0 时,变量不会自动设置为 nil,现在基本都用weak了。
@property (nonatomic, strong)             NSString *str1;
@property (nonatomic,__unsafe_unretain)   NSString *str2;

执行如下的代码

self.str1 = @"A";
self.str2 = self.str1;
self.str1 = nil;
NSLog(@"self.str2 = %@",self.str2);

这次根本就不用输出,在没有输出前,程序已经崩溃了,其实就是野指针造成的,为何会造成野指针呢?同于用unsafe_unretained声明的指针,由于 self.str1=nil已将内存释放掉了,但是str2并不知道已被释放了,所以是野指针。然后访问野指针的内存就造成crash. 所以尽量少用unsafe_unretained关键字。

  • 读写权限
    readwrite(默认):可读可写
    readonly:只读,当你希望暴露出来的属性不能被外界修改时就需要申明为readonly。
在oc中基本数据类型的默认关键字是atomic,readwrite和assign,普通属性的默认关键字是atomic,readwrite和strong。

2.0浅拷贝和深拷贝的区别

  • 浅拷贝:简单来说就是指针的拷贝,也就说是对于内存地址的复制,所以目标对象和源对象是指向同一块内存空间的。
  • 深拷贝:简单来说就是内容拷贝,也就是说重新产生了一个新的对象,内存地址发生了变化。
copy和mutableCopy在使用中的总结:
  • 对于NSMutableString、NSMutableArray这样的可变对象来说,使用copy和mutableCopy出来的对象都是深拷贝,而且都是单层拷贝,举个例子有字典a和字典b,让b = [a copy]和b = [a mutableCopy],创建出来的b字典跟a字典的地址是不一样的,但是b字典里面的字典元素和a字典里面的字典元素的地址是一样的,也就是说目标字典和源字典的元素指向的内存地址是一样的。
  • 对于NSString、NSArray这样的不可变对象来说,使用copy方法是浅拷贝,使用mutableCopy方法是深拷贝。
  • 使用copy方法返回的对象都是不可变的对象。
  • 如果我们想要实现数组里面的对象也可以实现深拷贝,可以利用归档。

如果我们让一个类对象进行复制的操作,我们需要遵守NSCopying或者NSMutableCopying协议,没有遵守就会出现异常。对于我们自定义的类,我们需要实现copyWithZone: 和mutableCopyWithZone: 方法,系统的类不需要,因为系统已经帮我们实现了。
iOS开发——深拷贝与浅拷贝详解

3.0 copy和strong的区别

  • strong赋值是多个指针指向同一个地址,而copy对于可变对象的复制是在内存中重新创建了一个对象,指针指向不同的地址。
  • 使用strong修饰的属性可能会指向一个可变的对象,设置完属性之后,可变实例的值可能会在对象不知情的情况下遭到更改。但是使用copy返回的都是不可变的对象,不会出现这种情况。

4.0 为什么不可变对象要用copy

因为该对象可能指向一个可变的对象,若用strong的话,设置完属性之后,可变实例的值可能会在对象不知情的情况下遭到更改。用copy的话就会重新生成一个新的对象,新的对象不会受源对象值改变的影响。

5.0 assign可以用于OC对象吗?

assign可以修饰OC对象如NSString等类型对象,使用assign修饰不会更改所赋新值的引用计数,也不会改变旧值的引用计数,如果所赋新值的引用计数为零对象被销毁时,属性并不知道,编译器不会将其置为nil,指针仍然指向被销毁的内存,造成“野指针”,在堆上容易造成崩溃。所以一般用assign来修饰基本数据类型,基本数据类型在栈上,而栈上的内存系统会自动处理,不会造成“野指针”。

扩展

野指针:指针指向了一个已经被回收的对象。
僵尸对象: 一个已经被释放的对象。

  • 野指针可以访问僵尸对象吗?
    当野指针指向的僵尸对象所占用的空间还没有分配给别人的时候,这个时候其实是可以访问的。因为对象的数据还在。当野指针指向的对象所占用的空间分配给了别人的时候 这个时候访问就会出问题。所以,你不要通过1个野指针去访问1个僵尸对象。
  • 如何避免僵尸对象报错?
    当一个指针变成野指针之后,将野指针的值设为nil。

6.0 weak如何实现自动赋nil

通过weak表。runtime维护了一个weak表,用来存储所有的weak指针。weak表其实就是一个哈希表,key代表对象的地址,value代表一个数组,数组里面存放的是weak指针的地址,此地址的值其实就是所指对象的地址。
释放时调用clearDeallocating函数,clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

问题点

1.0 从weak指针的创建到释放存在哈希表的增删改查,所以存在一定的性能开销。
2.0 使用 Weak 指针的时候,应首先获取一个 Strong 指针再使用。倒不是为了防止在使用过程中,对象被回收,形成野指针。 这个不用担心,因为你使用了 Weak 指针,对象就会被加入到 autoreleasepool 中,可以放心使用。但是要注意的是,如果在一个代码块中频繁使用 Weak 指针,还是应首先获取一个 Strong 指针,否则这个对象会被一次又一次的加入 autoreleasepool 中,也存在一定的性能开销。
详细请看--底层解析weak的实现原理

7.0 swift中如何理解copy-on-wirte?

在swift中当值类型(例如struct)在进行复制时,复制后的对象和原对象实际上在内存中指向的是同一个对象,只有当复制后的对象进行修改时,才会重新创建一个新的对象。在swift中我们平时使用的Int、Double、Dictionary、Array等都是使用结
构体(struct)实现的,我们用Array举个例子:

        // arr1是一个值类型
        let arr1 = ["tianyao", "ningfei", "tian"]
        // 此时arr2和arr1在内存中是同一个数组,并没有产生新的数组
        var arr2 = arr1
        // 此时arr2追加了一个元素,被修改,那么arr2在内存中重新产生了一个新的数组,而不是原来的arr1
        arr2.append("change")

从上面的代码我们可以看得出来,复制的数组和源数组指向的是同一个地址,只有当两者中的一方发生修改时,才会重新开辟一个新的地址。这样的设计使得值类型可以多次复制而不需要开辟新的内存,只有当一方有变化时才会开辟新的内存。这样使得内存有更高效的使用。

8.0 swift中,如何在结构体、enum、extension实例方法中修改成员变量?

在官方文档中有这么一句话:

“Structures and enumerations are value types. By default, the properties of a value type cannot be modified from within its instance methods.”

大概的意思是:虽然结构体和枚举中能够定义自己的方法,但是默认情况下,实例方法中是不可以修改值类型的属性。

这个问题的关键点就是一个关键词mutating(可变化,转变)。使用mutating来修饰结构体、枚举和extension定义的方法,就可以修改成员变量了。

  • 注意
    在设计协议的时候,由于protocal可以被class和struct或enum实现,故而要考虑是否用mutating来修饰方法。
    类中不存在这个问题,因为勒种可以随意修改自己的成员变量。

9.0 请你讲讲@proprety关键字的作用

  • 快速的为实例变量创建存储器,并允许使用点语法使用存储器,提供了一个外接访问成员变量的接口。
  • 存储器是用于获取和设置是实例变量的方法,getter是用于获取实例变量的存取器,setter是用于设置实例变量的存取器。

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

  • 使用copy其实是复制一个不可变的NSArray的对象,所以在添加、删除和修改数组内元素的时候,会因为找不到对应的方法程序崩溃。
  • 因为默认是atomic,所以会存在性能上面的问题。
    默认的情况下,由编译器所合成的方法会通过锁定机制确保其原子性,如果属性具备 nonatomic 特质,则不使用同步锁。开发中,几乎所有属性都声明为 nonatomic。一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全” ,若要实现“线程安全”的操作,还需采用更为深层的锁定机制才行。例如,一个线程在连续多次读取某属性值的过程中有别的线程在同时改写该值,那么即便将属性声明为 atomic,也还是会读到不同的属性值。

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

如果使用了属性的话,那么编译器就会自动编写访问属性所需的方法,此过程叫做“自动合成”。需要强调的是,这个过程由编译器在编译期执行,所以编辑器里看不到这些“合成方法” 的源代码。除了生成方法代码之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。

@synthesize合成实例变量的规则如下:
  • 如果指定了成员变量的名称,则生成一个指定名称的成员变量。
    例如:
@interface TYPerson : NSObject 

    @property NSString *firstName; 
@end

上面的例子会生成一个_firstName的成员变量,如果想让成员变量重新命名,也可以在类的实现方法里面通过@synthesize语法来指定成员变量的名字,如下

@implementation TYPerson 

    @synthesize firstName = _tianYao; 
@end 

此时就会生成一个_tianYao的成员变量。

  • 如果是@synthesize firstName;会生成一个_firstName的成员变量。即:没有指定成员变量的名称,就会生成一个同属性名相同的有下划线的成员变量。(@synthesize foo = _foo)
  • 如果这个成员已经存在了,就不会再生成了。
假如property名为person,存在一个名为_ person的实例变量,那么还会自动合成新变量么? 答案是不会的

如果存在_person的实例变量,那么你再创建一个_person的变量,系统就会报错。

附加:成员变量 = 实例变量 = Ivar

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

  • 重写了 setter 和 getter 时
  • 重写了只读属性的 getter 时
  • 使用了 @dynamic 时
  • 在 @protocol 中定义的所有属性
  • 在 category 中定义的所有属性
  • 重载的属性
  • 通过 @synthesize 语法来指定实例变量的名字(不建议使用)

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

简单的来说一个是自动的,一个是手动的。
当你定义了一个属性,使用@synthesize的时候,如果你没有手动的实现getter和setter的方法,那么编译器会自动的帮你添加这两个方法。那么使用@dynamic,你需要手动实现它的getter和setter的方法,编译器不会帮你做这件事情。假如你没有手动实现这两个方法的话,程序在编译的过程中没有问题,但当程序运行到someVar = var的时候,由于缺少getter方法,会导致系统崩溃。同样当运行到instance.var = someVar的时候,由于缺少setter方法,也会导致系统崩溃。
此过程编译时候是没有问题的,运行时才执行相应的方法,这就是动态绑定。

14.0 @protocol 和 category 中如何使用 @property?

a.在@protocol中使用@property:

在@protocol中添加@property,其实就是声明了getter和setter的方法。我们需要在实现这个协议的类中,手动添加实例变量,并实现getter和setter的方法。

b.category中使用@property:

在category中添加property时, 在@implentation添加 getter 和 setter方法时, 由于category不能添加实例变量,我们可以通过两个方法来实现:

  • 使用临时全局变量来替代成员变量;
  • 使用runtime 关联对象,实现成员变量(方法:objc_getAssociatedObject())

代码请看这里

15.0 __block和 __weak的区别和使用

  • __block用于修饰变量,它是引用修饰,所以,其修饰的值是可以动态变化的,即可以被重新赋值的。__block用于修饰某些block中将要修改的外部变量。
  • __weak也是用于修饰变量的,主要是用于防止循环引用。

附加:__block和__weak的使用都跟block有关。

16.0 __block的实现原理你知道吗?

block是不允许修改外部变量的值得,这里的外部变量的值,其实就是栈中指针的内存地址,__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。

Block不允许修改外部变量的值Apple这样设计,应该是考虑到了block的特殊性,block也属于“函数”的范畴,变量进入block,实际就是已经改变了作用域。在几个作用域之间进行切换时,如果不加上这样的限制,变量的可维护性将大大降低。又比如我想在block内声明了一个与外部同名的变量,此时是允许呢还是不允许呢?只有加上了这样的限制,这样的情景才能实现。

深入研究Block捕获外部变量和__block实现原理

17.0 block的实质是什么?有几种block?分别是怎样产生的?

  • block本质上也是一个OC对象,它内部也有个isa指针,封装了函数调用以及函数调用环境的OC对象,以及封装函数及其上下文的OC对象。
常见的block有三种类型:
  • _NSConcreteStackBlock:访问了auto变量的block是__NSStackBlock __,放在栈区。
  • _NSConcreteMallocBlock:[__NSStackBlock __ copy]操作就变成了__NSMallocBlock __,放在堆区。
  • _NSConcreteGlobalBlock:没有访问auto变量的block是__NSGlobalBlock __ ,放在数据段

在GC环境下还有3种使用的_NSConcreteFinalizingBlock,_NSConcreteAutoBlock,_NSConcreteWeakBlockVariable,可以看看官方文档。

附加:

GC(garbage collection):一个跟踪过程,它传递性地跟踪指向当前使用的对象的所有指针,以便找到可以引用的所有对象,然后重新使用在此跟踪过程中未找到的任何堆内存。公共语言运行库垃圾回收器还压缩使用中的内存,以缩小堆所需要的工作空间 。

18.0 代理和block的比较

  • block是集中代码块,而代理是分散代码块,所以block的话更加适合轻便、简单的回调操作,比如网络传输的回调。代理则更加适合公共接口比较多的情况,这样做更加易于解耦代码的架构。
  • 关于运行成本的区别,block运行的成本高。block出栈时候,需要将数据从栈区复制到堆区,当然,如果是对象就是加计数,使用完成或者block置为nil后才消除;delegate只是保存了一个对象指针,直接回调,并没有额外消耗。相对于c的函数指针,只多了个查表动作。
注意:block容易出现循环引用。

附加:通知跟block和delegate相比,它可以一对多,也可以跨控制器进行传值,它是基于kvo来实现的。(kvo的话后续会说)

未完待续(网络、多线程、数据库等方面的我也会单独整理出来)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,050评论 8 265
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,042评论 1 32
  • 目录 1、入门 [Linux]入门[Linux]目录结构[Linux]vi,vim[Linux]开机、重启和用户登...
    瑾兰阅读 620评论 0 0
  • 暖风清溪,动了凡心却心静如水。 雾雨蒙蒙,开了杏花又湿了花蕊。 古道旁,烟花巷,断不了的青丝,思念那么凉。
    深巷酒肆阅读 182评论 0 0
  • 在javascript的学习中,执行环境、作用域是2个非常非常重要和基本的概念,理解了这2个概念对于javsacr...
    howell5阅读 147评论 0 0