面试题

作者: Super宗Sir | 来源:发表于2020-06-26 16:45 被阅读0次

    @property相关

    @property的本质是什么?

    @property = ivar + getter + setter;
    “属性” (property)有两大概念:ivar(实例变量)、存取方法(access method = getter + setter)。
    编译器会自动写出一套存取方法,用以访问给定类型中具有给定名称的变量。 所以你也可以这么说:
    @property = getter + setter;

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

    “自动合成”( autosynthesis)
    编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,这个过程由编译 器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码 getter、setter 之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。在前例中,会生成两个实例变量,其名称分别为 _firstName与 _lastName。也可以在类的实现代码里通过 @synthesize语法来指定实例变量的名字.

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

    OBJC_IVAR_类名属性名称 :该属性的“偏移量” (offset),这个偏移量是“硬编码” (hardcode),表示该变量距离存放对象的内存区域的起始地址有多远。
    setter 与 getter 方法对应的实现函数
    ivar_list :成员变量列表
    method_list :方法列表
    prop_list :属性列表
    也就是说我们每次在增加一个属性,系统都会在 ivar_list 中添加一个成员变量的描述,在 method_list 中增加 setter 与 getter 方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出 setter 与 getter 方法对应的实现,在 setter 方法中从偏移量的位置开始赋值,在 getter 方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转.

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

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

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

    不同点:

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

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

    怎么用 copy 关键字?

    用途:

    1. NSString、NSArray、NSDictionary 等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary;
    2. block 也经常使用 copy 关键字,具体原因见官方文档:Objects Use Properties to Keep Track of Blocks

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

    在 ARC 中写不写都行:

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

    • block 作为函数返回值时

    • 将 block 赋值给 __strong 指针时(property 的 copy 属性对应的是这一条)

    • block 作为 Cocoa API 中方法名含有 using Block 的方法参数时

    • block 作为 GCD API 的方法参数时
      其中, block 的 property 设置为 copy, 对应的是这一条:将 block 赋值给 __strong 指针时。

    对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 也无伤大雅,还能时刻提醒我们:编译器自动对 block 进行了 copy 操作。如果不写 copy ,该类的调用者有可能会忘记或者根本不知道“编译器会自动对 block 进行了 copy 操作”,他们有可能会在调用之前自行拷贝属性值。这种操作多余而低效。你也许会感觉我这种做法有些怪异,不需要写还依然写。如果你这样想,其实是你“日用而不知”,你平时开发中是经常在用我说的这种做法的,比如下面的属性不写copy也行,但是你会选择写还是不写呢?

    下面做下解释: copy 此特质所表达的所属关系与 strong 类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。 当属性类型为 NSString 时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString 类的实例。这个类是 NSString 的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。

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

    两个问题:
    1、添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃.因为 copy 就是复制一个不可变 NSArray 的对象;
    2、使用了 atomic 属性会严重影响性能 ;(该属性使用了互斥锁(atomic 的底层实现,老版本是自旋锁,iOS10开始是互斥锁--spinlock底层实现改变了。),会在创建时生成一些额外的代码用于帮助编写多线程程序,这会带来性能问题,通过声明 nonatomic 可以节省这些虽然很小但是不必要额外开销。)

    如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?

    若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。
    具体步骤:

    需声明该类遵从 NSCopying 协议
    实现 NSCopying 协议。该协议只有一个方法:

    • (id)copyWithZone:(NSZone *)zone;
    @protocol 和 category 中如何使用 @property

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

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

    3、objc_setAssociatedObject

    4、objc_getAssociatedObject

    runtime 如何实现 weak 属性

    weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同 assign 类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。

    那么 runtime 如何实现 weak 变量的自动置nil?

    runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
    而如果a是由 assign 修饰的,则: 在 b 非 nil 时,a 和 b 指向同一个内存地址,在 b 变 nil 时,a 还是指向该内存地址,变野指针。此时向 a 发送消息会产生崩溃。
    weak 修饰的指针默认值是 nil (在Objective-C中向nil发送消息是安全的)

    @property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?

    属性可以拥有的特质分为四类:
    1、原子性--- nonatomic 特质

    在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(atomicity)。如果属性具备 nonatomic 特质,则不使用互斥锁(atomic 的底层实现,老版本是自旋锁,iOS10开始是互斥锁--spinlock底层实现改变了。)。请注意,尽管没有名为“atomic”的特质(如果某属性不具备 nonatomic 特质,那它就是“原子的” ( atomic) ),但是仍然可以在属性特质中写明这一点,编译器不会报错。若是自己定义存取方法,那么就应该遵从与属性特质相符的原子性。

    2、读/写权限---readwrite(读写)、readonly (只读)

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

    4、方法名---getter=<name> 、setter=<name>

    weak属性需要在dealloc中置nil么?

    在ARC环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理

    @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下,不显式指定任何属性关键字时,默认的关键字都有哪些?

    1 对应基本数据类型默认关键字是
    atomic
    readwrite
    assign

    2 对于普通的 Objective-C 对象
    atomic
    readwrite
    strong

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

    iOS 集合的深复制与浅复制

    1、因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.

    2、如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.

    copy 此特质所表达的所属关系与 strong 类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。 当属性类型为 NSString 时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString 类的实例。这个类是 NSString 的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。

    1. 对非集合类对象的copy操作:(系统非集合类对象指的是 NSString, NSNumber ... 之类的对象)
      在非集合类对象中:对 immutable 对象进行 copy 操作,是指针复制,mutableCopy 操作时内容复制;对 mutable 对象进行 copy 和 mutableCopy 都是内容复制。用代码简单表示如下:

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

    2、集合类对象的copy与mutableCopy
    集合类对象是指 NSArray、NSDictionary、NSSet ... 之类的对象。下面先看集合类immutable对象使用 copy 和 mutableCopy 的一个例子:
    [immutableObject copy] // 浅复制
    [immutableObject mutableCopy] //单层深复制
    [mutableObject copy] //单层深复制
    [mutableObject mutableCopy] //单层深复制

    • 浅复制(shallow copy):在浅复制操作时,对于被复制对象的每一层都是指针复制。
    • 深复制(one-level-deep copy):在深复制操作时,对于被复制对象,至少有一层是深复制。
    • 完全复制(real-deep copy):在完全复制操作时,对于被复制对象的每一层都是对象复制。

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

    • copy返回imutable对象;所以,如果对copy返回值使用mutable对象接口就会crash;
    • mutableCopy返回mutable对象;
    集合对象的完全复制

    方法一:使用 initWith***: copyItems:YES 方法
    自定义集合对象使用这个方法,对象必须遵守NSCopying协议,并重写- (id)copyWithZone:(NSZone *)zone方法。(系统类方法已经实现)。

    方法二:先将集合进行归档,然后再解档。
    通常我们对模型数组完全复制,先将模型数组转换为data数组,再将data数组转换为模型数组,即可。

    最后说个题外的东西,在搜集资料的过程中,发现一个有可能犯错的点
    NSString *str = @"string";
    str = @"newString";
    上面这段代码,在执行第二行代码后,内存地址发生了变化。乍一看,有点意外。按照 C 语言的经验,初始化一个字符串之后,字符串的首地址就被确定下来,不管之后如何修改字符串内容,这个地址都不会改变。但此处第二行并不是对 str 指向的内存地址重新赋值,因为赋值操作符左边的 str 是一个指针,也就是说此处修改的是内存地址。

    所以第二行应该这样理解:将@"newStirng"当做一个新的对象,将这段对象的内存地址赋值给str。

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

    实例变量 = 成员变量 = ivar

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

    @interface CYLPerson : NSObject
    @property NSString *firstName;
    @property NSString *lastName;
    @end

    在上例中,会生成两个实例变量,其名称分别为 _firstName 与 _lastName。也可以在类的实现代码里通过 @synthesize 语法来指定实例变量的名字:

    @implementation CYLPerson
    @synthesize firstName = _myFirstName;
    @synthesize lastName = _myLastName;
    @end

    上述语法会将生成的实例变量命名为 _myFirstName 与 _myLastName ,而不再使用默认的名字。一般情况下无须修改默认的实例变量名,但是如果你不喜欢以“下划线”来命名实例变量,那么可以用这个办法将其改为自己想要的名字。笔者还是推荐使用默认的命名方案,因为如果所有人都坚持这套方案,那么写出来的代码大家都能看得懂。

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

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

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

    3 如果是 @synthesize foo; 还会生成一个名称为foo的成员变量,也就是说:

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

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

    假如 property 名为 foo,存在一个名为 _foo 的实例变量,那么还会自动合成新变量么?
    不会。

    相关文章

      网友评论

          本文标题:面试题

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