iOS之OC深入理解iOS面试题

[iOS] Property属性相关的一些问题

2018-12-14  本文已影响19人  我阿郑

目录


问题 __block 和 __weak的区别

__block对象在block中是可以被修改、重新赋值的。

使用了__weak修饰符的对象,作用等同于定义为weak的property。
自然不会导致循环引用问题。

因此,__block和__weak修饰符的区别:

更详细说明参看__weak与__block修饰符到底有什么区别

问题: @property中有哪些属性关键字?

属性可以拥有的特质分为四类:

    getter=的样式:
    @property (nonatomic, getter=isOn) BOOL on;
    ( setter=这种不常用,也不推荐使用。故不在这里给出写法。)
    

`assign`表示对属性只进行简单的赋值操作,不更改所赋的新值的引用计数,也不改变旧值的引用计数,常用于基本类型,`如NSInteger,NSUInteger,CGFloat,NSTimeInterval等`。

assign也可以修饰对象如NSString等类型对象,上面说过使用assign修饰不会更改所赋的新值的引用计数,也不改变旧值的引用计数,如果当所赋的新值引用计数为0对象被销毁时属性并不知道,编译器不会将该属性置为nil,指针仍旧指向之前被销毁的内存,这时访问该属性会产生野指针错误并崩溃,因此使用assign修饰的类型一定要为基本类型。

__unsafe _unretained修饰符可以被视为iOS SDK 4.3以前版本的__weak的替代品,不过不会被自动置空为nil。所以尽可能不要使用这个修饰符,容易出现访问野指针的内存造成crash。

__unsafe _unretained__weak 与assign的区别在于,__unsafe _unretained__weak只能修饰对象,不能修饰基本类型,而assign两者均可修饰。

问题:@synthesize和@dynamic分别有什么作用?

1)@property有两个对应的词,一个是@synthesize,一个是@dynamic。如果@synthesize和@dynamic都没写,那么默认的就是@syntheszie var = _var;

2)@synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。

3)@dynamic告诉编译器:属性的setter与getter方法由用户自己实现,不自动生成。(当然对于readonly的属性只需提供getter即可)。假如一个属性被声明为@dynamic var,然后你没有提供@setter方法和@getter方法,编译的时候没问题,但是当程序运行到instance.var = someVar,由于缺setter方法会导致程序崩溃;或者当运行到 someVar = var时,由于缺getter方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。

问题:ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?

问题:OC中的类属性是什么?

Xcode 8开始,LLVM已经支持Objective-C显式声明类属性了,这是为了与Swift中的类属性互操作而引入的。

创建一个类属性主要有以下几个步骤:

需要注意的是编译器不会自动帮我们生成类属性的getter和setter方法,所以2、3步是必须的。 更详细内容参看iOS 中的类属性

问题:什么情况使用 weak 关键字,和 assign 有什么不同?

什么情况使用 weak 关键字?

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

2)自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用weak,自定义IBOutlet控件属性一般也使用weak;当然,也可以使用strong。在下文也有论述:《IBOutlet连出来的视图属性为什么可以被设置成weak?》

和 assign 有什么不同:

1)weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。 而 assign 的“设置方法”只会执行针对“纯量类型” (scalar type,例如 CGFloat 或 NSlnteger 等)的简单赋值操作。

2)assigin 可以用与OC对象、非OC对象的基本数据类型, 而weak只能用于OC对象

问题:对象回收时weak指针自动被置为nil的实现原理

https://www.jianshu.com/p/fe9865814668
https://blog.csdn.net/u013232867/article/details/52766308
https://blog.csdn.net/hherima/article/details/38661803

问题一:为什么block要使用copy而不是strong或者其他属性修饰

解答:

block使用copy是在MRC中延续下来的,在MRC下,方法内部的block是存放在栈区,使用copy会将block拷贝到堆区。

在ARC下编译器会自动对block进行copy,因此我们使用copy或者strong的效果是一样的。但是我们在ARC下继续使用copy可以提醒我们编译器会自动帮我们实现copy的操作。

Note: You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior. For more information, see Blocks Programming Topics.

上面是苹果官方文档里的说明,更详细内容请看Objects Use Properties to Keep Track of Blocks

说到在类中声明一个block为什么要用copy修饰的话,那就要先说block的三种类型。

解释说明:

我们知道函数的声明周期是随着函数调用的结束就终止了。block也是写在函数中的。

如果是全局静态block的话直到程序结束的时候才会被被释放。但是我们实际操作中基本上不会使用到不访问外部变量的block。

【但是在测试三种区别的时候,因为没有很好的理解这种block,(用没有copy修饰和没有访问外部变量的block)试了好多次,以为是放在静态区里面的block没有随函数结束被释放。这是个小坑】

如果是保存在栈中的block会随着函数调用结束被销毁。从而导致我们在执行一个包含block的函数之后,就无法再访问这个block。因为(函数结束,函数栈就销毁了,存在函数里面的block也就没有了),我们再使用block时,就会产生空指针异常。

如果是堆中的block(也就是copy修饰的block)。它的生命周期是随着引用计数为 0 时才会被销毁。只要引用计数不为0,我们就可以调用的到在堆中的block。

这就是为什么我们要用copy来修饰block。因为不用copy修饰的访问外部变量的block,只在它所在的函数被调用的那一瞬间可以使用。之后就消失了。


问题二:@property 的本质是什么?ivar(实例变量 也叫成员变量)、getter、setter 是如何生成并添加到这个类中的。

@property = ivar + getter + setter; // 本质

解释说明:

“属性(property)有两大概念” :ivar(实例变量)、存取方法(getter + setter)。

OC 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”来访问。在正规的 Objective-C 编码风格中存取方法有着严格的命名规范。 正因为有了这种严格的命名规范,所以 OC 这门语言才能根据名称自动创建出存取方法(编译器会自动写出一套存取方法,访问指定类型的实例变量)。

ivar、getter、setter 是如何自动生成并添加到这个类中的?

“自动合成”( autosynthesis)

完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”( autosynthesis)。需要强调的是,这个过程由编译 器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。

除了生成方法代码 getter、setter 之外,编译器还要自动向类中添加下划线的实例变量。(也可以在类的实现代码里通过 @synthesize语法来指定实例变量的名字).

@implementation Person
 
@synthesize firstName = _myFirstName; 
@synthesize lastName = myLastName; 

@end

我为了搞清属性是怎么实现的,曾经反编译过相关的代码,大致生成了五个东西:

也就是说每次在增加一个属性

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

问题三:为什么我们自定义的对象一般总用strong修饰,而很少用copy ??

因为若想令自己所写的对象具有拷贝功能,则需实现NSCopying协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopyiog与NSMutableCopying协议。实现了相应的协议就可以用copy修饰了。

问题四:用@property定义一个 NSMutableArray、NSMutableDictionary时用copy修饰会有什么问题??

当我们在下面对property定义的这个变量进行相关的添加、修改、删除的操作时会crash,报错方法找不到。因为copy操作后返回的是不可变对象,然后调用可变对象的方法是就crash了

问题四:对一个NSMutableArray、NSMutableDictionary对象进行copy操作会开辟新内存吗??

会。因为对可变的集合进行copy操作,都是单层深复制。下面的会详细说明

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

为确保不可变对象不受可变子类对象影响,需要copy一份备份,如果不使用copy修饰,使用strong或assign等修饰则会因为多态导致属性值被修改。

举例说明

@property (nonatomic, copy) NSString *stringCopy;
@property (nonatomic, strong) NSString *stringStrong;

####################

NSMutableString *stringM = [NSMutableString stringWithString:@"hello"];
self.stringCopy = stringM;
self.stringStrong = stringM;
[stringM appendString:@"world!"];

NSLog(@"stringCopy: %@  stringStrong:%@",self.stringCopy,self.stringStrong);
// 打印出来的结果
stringCopy: hello  stringStrong:helloworld!

####################

 NSMutableArray *arrayM = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
self.arrayCopy = arrayM;
self.arrayStrong = arrayM;
[arrayM addObject:@"xxx"];

// 打印出来的结果
arrayCopy:(
a,
b,
c
) arrayStrong: (
a,
b,
c,
xxx
)

发现当stringM的值被修改后、用copy修饰的stringCopy值并没有受影响仍然打印hello,用strong修饰的stringStrong的值被改变成了helloworld!

同样发现当arrayM的改变后、用copy修饰的arrayCopy值并没有受影响仍,而用strong修饰的arrayStrong的发生了改变

接着另外一个测试,对stringM进行copy复制给stringStrong会是什么结果

NSMutableString *stringM = [NSMutableString stringWithString:@"hello"];
self.stringCopy = stringM;
self.stringStrong = [stringM copy]; // copy
[stringM appendString:@"world!"];

NSLog(@"stringCopy: %@  stringStrong:%@",self.stringCopy,self.stringStrong);
NSLog(@"内存地址 %p--%p--%p",stringM,self.stringCopy,self.stringStrong);

    // 打印结果 
stringCopy: hello  stringStrong:hello
内存地址 0x604000259290--0xa00006f6c6c65685--0xa00006f6c6c65685

我们发现当对可变字符串stringM进行copy操作之后,即使后来改变了stringM的值,通过strong修饰的stringStrong也没有改变

其实,self.stringCopy = stringM这句代码,用copy修饰的stringCopy就相当于给[stringM copy]执行了copy操作

但是我们打印内存地址发现,stringM的内存地址和self.stringCopy、self.stringCopy的内存地址不一样,为什么会这样那?? 接下来就看看系统对象的copy与mutableCopy方法

首先看看系统对象的copy与mutableCopy方法

不管是集合类对象,还是非集合类对象,接收到copy和mutableCopy消息时,都遵循以下准则:

1、非集合类对象的copy与mutableCopy

非集合类对象指的是 NSString, NSMutableString, NSNumber ... 之类的对象

第一、以下代码验证对不可变对象进行copy和mutableCopy操作:

NSString *string = @"hello";
NSString *stringCopy = [string copy];
NSMutableString *stringMCopy = [string mutableCopy];
NSLog(@"%p--%p--%p",string,stringCopy,stringMCopy);

// 打印地址的结果
test[14989:2001728] 0x102f520b0--0x102f520b0--0x604000445040

发现 string、stringCopy 内存地址一样,而stringMCopy的内存地址与它们不一样,说明此时stringMCopy做的是内容拷贝(深拷贝)。

第二、以下代码验证对可变对象进行copy和mutableCopy操作:

NSMutableString *string = [NSMutableString stringWithString: @"hello"];
NSString *stringCopy = [string copy];// 可变字符串进行copy赋值给不可变
NSMutableString *mStringCopy = [string copy];// 可变字符串进行copy赋值给可变
NSMutableString *stringMCopy = [string mutableCopy];// 可变字符串进行mutableCopy赋值给可变

//change value
[mStringCopy appendString:@"world!"]; //crash
[string appendString:@" haha"];
[stringMCopy appendString:@"!!"];

NSLog(@"stringCopy: %@  mStringCopy:%@ stringMCopy:%@",stringCopy,mStringCopy,stringMCopy);
NSLog(@"stringCopy: %p  mStringCopy:%p stringMCopy:%p",stringCopy,mStringCopy,stringMCopy);

// 打印结果
stringCopy: hello  mStringCopy:hello stringMCopy:hello!!
string:0x60000024a530 stringCopy: 0xa00006f6c6c65685  mStringCopy:0xa00006f6c6c65685 stringMCopy:0x60000024a410

程序运行到[mStringCopy appendString:@"world!"]会crash,因为此时mStringCopy实际是不可变字符串了,所以调用可变字符串的appendString方法就会报方法找不到的错误;这也说明了为什么我们在定义可变字符串时用strong去修饰而不用copy

把这句注释掉运行,发现内存地址stringCopy、stringMCopy和开始的string的内存地址都不一样,说明stringCopy、stringMCopy做的都是内容的拷贝(深拷贝);而stringCopy和mStringCopy的内存地址一样,因为它们都是对string进行了copy操作,只是赋值的对象名称不同而已

最终结论

    [immutableObject copy] // 浅复制
    [immutableObject mutableCopy] //深复制
    [mutableObject copy] //深复制
    [mutableObject mutableCopy] //深复制

2、集合类对象的copy与mutableCopy

集合对象指的是NSArray,NSDictionary,NSSet等类的对象。

第一、以下代码验证集合类对不可变对象进行copy和mutableCopy操作:

NSArray *array = @[@[@"a", @"b"], @[@"c", @"d"]];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];

NSLog(@"array:%p copyArray: %p  mCopyArray:%p ",array,copyArray,mCopyArray);

// 打印结果
array:0x60000022ab40 copyArray: 0x60000022ab40  mCopyArray:0x60000025ba20

可以看到copyArray和array的地址是一样的,而mCopyArray和array的地址是不同的。说明copy操作进行了指针拷贝,mutableCopy进行了内容拷贝(单层深拷贝)。

注意特别说明:
此处的内容拷贝仅仅是拷贝array这个对象,array集合内部的元素仍然是指针拷贝。

NSLog(@"array第一个元素的内存地址:%p array第二个元素的内存地址:%p ",array[0],array[1]);
NSLog(@"copyArray第一个元素的内存地址:%p copyArray第二个元素的内存地址:%p ",array[0],array[1]);
NSLog(@"mCopyArray第一个元素的内存地址:%p mCopyArray第二个元素的内存地址:%p ",array[0],array[1]);

// 打印结果
array第一个元素的内存地址:0x60000022c540 array第二个元素的内存地址:0x60000022c380 
copyArray第一个元素的内存地址:0x60000022c540 copyArray第二个元素的内存地址:0x60000022c380 
mCopyArray第一个元素的内存地址:0x60000022c540 mCopyArray第二个元素的内存地址:0x60000022c380 

第二、以下代码验证集合类对可变对象进行copy和mutableCopy操作:

NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];

NSLog(@"array:%p copyArray: %p  mCopyArray:%p ",array,copyArray,mCopyArray);
// 打印结果
array:0x604000256710 copyArray: 0x604000256740  mCopyArray:0x604000256770

如我们所料,copyArray、mCopyArray和array的内存地址都不一样,说明copyArray、mCopyArray都对array进行了内容拷贝(单层深拷贝)

最终结论:

深浅复制概念

对象拷贝有两种方式:浅复制和深复制。顾名思义,浅复制,并不拷贝对象本身,仅仅是拷贝指向对象的指针;深复制是直接拷贝整个对象内存到另一块内存中。

深复制、浅复制

关于深复制、浅复制更详细请看《iOS 集合的深复制与浅复制》 这篇文章

上一篇下一篇

猜你喜欢

热点阅读