《招一个靠谱的iOS》6-10
本人参考GitHub《招聘一个靠谱的iOS》面试题参考答案(上)
6. @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的
7. @protocol 和 category 中如何使用 @property
8. runtime 如何实现 weak 属性
9. @property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?
10. weak属性需要在dealloc中置nil么?
6. @property的本质是什么?ivar、getter、setter是如何生成并添加到这个类中的?
(1)@property的本质是什么?
属性(property)有两大概念:ivar(实例变量)、存取方法(access method = getter + setter),即@property = ivar + setter + getter。
属性(property)作为Objective-C的一项特性,主要的作用就在于封装对象中的数据。Objective-C对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法”(getter)用于读取变量值,而“设置方法”(setter)用于写入变量值。这个概念已经定型,并且经由“属性”这一特性而成为Objective-C 2.0的一部分。而在正规的Objective-C编码风格中,存取方法有着严格的命名规范。正因为有了这种严格的命名规范,所以Objective-C这门语言才能根据名称自动创建出存取方法。
属性也可以当做一种关键字,其表示:编译器会自动写出一套存取方法,用以访问给定类型中具有给定名称的变量。
property在runtime中是objc_property_t定义如下:
typedef struct objc_property *objc_property_t;
而objc_property是一个结构体,包括name和attributes,定义如下:
struct property_t {
const char *name;
const char *attributes;
};
attributes本质是objc_property_attribute_t,定义了property的一些属性,定义如下:
// Defines a property attribute
typedef struct {
const char *name; /**The name of the attribute**/
const char *value; /**The value of the attribute (usually empty) */
} objc_property_attribute_t;
attribute的具体内容包括:类型,原子性,内存语义和对应的实例变量。
例如:我们定义一个string的property@property (nonatomic, copy) NSString *string;
,通过property_getAttributes(property)
获取到attributes并打印出来之后的结果为T@"NSString",C,N,V_string
,其中T就代表类型,可参阅Type Encodings,C就代表Copy,N代表nonatomic,V就代表对应的实例变量。
objc_property_t中尾部_t的来源:
_t的意思显然就是type。关于为什么要加_t。一个类型后面加了_t说明了这是一个POSIX或GNU保留类型,防止namespace pollution的。不然标准库里新加了什么类型说不定就和用户已经定义的类型重名了。所以POSIX规定自己扩展的类型都加_t,这样只要用户定义类型的时候不加_t就不会冲突。
见General Information(B.2.12)
Defined Types
The requirement that additional types defined in this section end in "_t" was prompted by the problem of name space pollution. It is difficult to define a type (where that type is not one defined by POSIX.1-2008) in one header file and use it in another without adding symbols to the name space of the program. To allow implementors to provide their own types, all conforming applications are required to avoid symbols ending in "_t", which permits the implementor to provide additional types. Because a major use of types is in the definition of structure members, which can (and in many cases must) be added to the structures defined in POSIX.1-2008, the need for additional types is compelling.
The types, such as ushort and ulong, which are in common usage, are not defined in POSIX.1-2008 (although ushort_twould be permitted as an extension). They can be added to sys/types.husing a feature test macro (see POSIX.1 Symbols). A suggested symbol for these is _SYSIII. Similarly, the types like u_short would probably be best controlled by _BSD.
ivar、getter、setter是如何生成并添加到这个类中的?
“自动合成”(autosynthesis)
完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,这个过程由编译器在编译期执行,所以编译器里看不到“合成方法的源代码”。除了生成方法代码getter、setter之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线作为实例变量的名字。
@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
上述代码写出来的类与下面这种写法等效:
@interface Person : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end
在本例中,会生成两个实例变量,其名称分别为_firstName与_lastName。也可以在类的实现代码里通过@synthesis语法来指定实例变量的名字。
@implementation Person
@synthesis firstName = _myFirstName;
@synthesis lastName = _myLastName;
@end
自动合成的工作原理是大致生成了五个东西:
1.OBJC_IVAR_ $类名$属性名
:该属性的“偏移量”(offset),这个偏移量是“硬编码”(hardcode),表示该变量距离存放对象的内存区域的起始地址有多远。
2.setter与getter方法对应的实现函数
3.ivar_list:实例变量(成员变量)列表
4.method_list:方法列表
5.prop_list:属性列表
也就是说,我们每次添加一个属性,系统都会在ivar_list中添加一个实例变量(成员变量)的描述,在method_list中增加setter与getter方法的描述,在prop_list中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出stter于getter方法对应的实现。在setter方法中从偏移量的位置开始赋值,在getter方法中从偏移量开始取值,为了能够读取正确字节数,系统对对象偏移量的指针进行了类型强转。
7. @protocol和category中如何使用@property
1.在protocol中使用property只会生成setter和getter方法声明,我们使用属性的目的,是希望遵守协议的对象能实现该属性;
2.category使用@property只会生成setter和getter方法的声明,如果需要给category增加属性的实现,需要借助于运行时(runtime)的两个函数:objc_setAssociatedObject()和objc_getAssociatedObject()。
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
四个参数分别为:源对象, 关键字, 关联对象, 关联策略。
- 源对象:指要给哪个对象设置关联对象;
- 关键字:给源对象的添加的属性名称(关联的key),类型为静态指针,在实际应用时,会传一个静态变量的地址或get方法。
- 关联对象:属性的值
- 关联策略:是一组枚举(enum)。对应属性特指的assign,weak,strong,_unsafe_unretain。
关联策略 | 等价属性 | 说明 |
---|---|---|
OBJC_ASSOCIATION_ASSIGN = 0 | @property(assign) or @property(unsafe_unretained) | 弱引用关联对象 |
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1 | @property(strong, nonatomic) | 强引用关联对象,且未非原子操作 |
OBJC_ASSOCIATION_COPY_NONATOMIC = 3 | @property(copy, nonatomic) | 复制关联对象,且为非原子操作 |
OBJC_ASSOCIATION_RETAIN = 01401 | @property(strong, atomic) | 强引用关联对象,且为原子操作 |
OBJC_ASSOCIATION_COPY = 01403 | @property(copy, atomic) | 复制关联对象,且未原子操作 |
(1)在protocol中使用@property:
Person.h:
// 设置协议,添加property
#import <Foundation/Foundation.h>
@protocol PersonDelegate<NSObject>
@property (nonatomic, copy) NSString *name;
@end
@interface Person:NSObject
@end
Student.h:
// 遵守Person协议 并声明实例变量
#import <Foundation/Foundation.h>
#import <Person.h>
@interface Student : Person <PersonDelegate>
{
NSString *_name;
}
@end
Student.m:
//实现setter和getter
// 有两种方式实现setter和getter方法:自动实现和手动实现
//---自动实现setter和getter方法,用synthesize---//
@implementation Student
@synthesize name;
@end
//--------手动-----//
@implementation Student
- (void)setName:(NSString *)name
{
_name = name;
}
- (NSString *)name
{
return _name;
}
@end
(2)在category中使用@property:
@interface NSObject (Extension)
@property (nonatomic, strong) NSString *title;
@end
#import <objc/runtime.h>
@implementation NSObject (Extension)
//-------@selector(title)可以换成_cmd,_cmd 代指当前方法的选择子,也就是 @selector(title)---/
- (NSString *)title
{
return objc_getAssociatedObject(self, @selector(title));
}
- (void)setTitle:(NSString *)title
{
objc_setAssociatedObject(self, @selector(title), title, OBJC_ASSOCIATION_RETAIN);
}
8. runtime如何实现weak属性
runtime对于weak对象会放入一个hash表中。
(1)初始化时:runtime会调用objc_initWeak(&weak, obj)
函数初始化一个新的weak指针,指向weak修饰的属性变量(weak)的地址,并将该变量初始化为0(nil)。
(2)添加引用时:初始化后,会将“赋值对象(obj)”作为参数,调用objc_storeWeak函数,该函数的作用是更新指针指向,创建对应的弱引用表,objc_storeWeak(&weak, obj);用weak指向的对象(obj)内存地址作为key,将weak修饰的属性变量(weak)的内存地址作为value。
(3)释放时,此对象的引用计数器为0的时候会dealloc,调用objc_destroyWeak(&weak);
释放weak修饰的属性变量(weak),该函数将0作为参数,调用objc_storeWeak(&weak, 0);
函数,当objc_storeWeak第二个参数为0(nil)时,调用clearDeallocating函数将weak的地址从weak表中删除。
objc_storeWeak函数会把第二个参数——赋值对象obj的内存地址作为键值key,将第一个参数——weak修饰的属性变量weak的内存地址&weak作为value,注册到weak表中,如果第二个参数obj为0(nil),那么把变量weak的内存地址&weak从weak表中删除。即,objc_storeWeak(value, key)
,并且当key变为nil,将value置nil。
9. @property中有哪些属性关键字?||@property后面可以有哪些修饰符?
属性可以拥有的特质分为五类(第五类不常见):
- 原子性——atomic/nonatomic特质
默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(atomic),如果属性具备nonatomic特质,则不适用自旋锁。属性特质默认为atomic。 - 读写权限——readwrite、readonly
readwrite可读可写,系统为属性生成setter和getter方法;readonly只读,系统为属性生成getter方法。 - 内存管理语义——assign、strong、weak、unsafe_unretained、copy
属性默认为assign(基本数据类型)或strong(对象类型)
assign一般用于修饰基本数据类型,也可以用于修饰对象属性,该对象被销毁时,属性不自动置为nil,易造成野指针;
strong用于强引;
weak用于修饰对象类型,不能修饰基本数据类型,对象被释放时,属性自动置为nil;
copy用于修饰对象类型,不能修饰基本数据类型,一般用于有可变类型子类的父类赋值,防止旧值无意间变动;
unsafe_unretained在ARC中与assign相同。 - 方法名——getter=name,setter=name
getter的用法:
@property (nonatomic, getter = isOn) BOOL on;
on的getter方法不再是- (BOOL)on,而是- (BOOL)isOn。
setter的用法:
setter一般用于特殊情况下,比如:
在数据转模型的过程中,服务器返回的字段如果以init开头,则需要定义一个init开头的属性,此时默认生成的setter和getter方法也会以init开头,而编译器会把所有以init开头的方法当成初始化方法,而初始化方法只能返回self类型,因此编译器会报错。
此时就可以使用下面的方式,来避免编译器报错:
@property (nonatomic, strong, getter = P_initBy, setter = setP_initBy:) NSString *initBy;
- 不常用的nonnull,null_resettable,nullable
默认为nullable
nullable表示修饰的属性或参数可以为空;
nonnull表示修饰的属性或参数不能为空;
null_resettable表示get方法不能返回为空,set方法可以为空。
null_resettable用法:
@property (nonatomic, strong, null_resettable) NSNumber *number;
- (NSNumber *)number
{
if(_number == nil){
_number1 = @11;
}
return _number;
}
10. weak属性需要在dealloc中置nil么?
不需要。
在ARC环境无论是强指针还是弱指针都无需在dealloc设置为nil,ARC会自动帮我们处理。