本人参考GitHub《招聘一个靠谱的iOS》面试题参考答案(上)
16. @synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?
17. 在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?
18. objc中向一个nil对象发送消息将会发生什么?
19. objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系?
20. 什么时候会报unrecognized selector的异常?
16. @synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?
科普一个概念:
实例变量 = 成员变量 = ivar
如果使用了属性的话,那么编译器就会自动编写访问属性所需的方法,此过程叫做“自动合成”(auto synthesis)。这个过程由编译器在编译期执行,除了生成方法代码之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。
@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
在上例中,会生成两个实例变量,其名称分别为_firstName和_lastName。也可以在类的实现代码中通过@synthesize语法来指定实例变量的名字。
@implementation
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
上述语法会将生成的实例变量命名为_myFirstName和_myLastName,而不再是默认的_firstName和_lastName。
总结一下,@synthesize合成实例变量的规则,有以下几点:
- 如果没有指定成员变量的名称就会自动生成一个属性同名的加_的成员变量;
即,@synthesize foo;
则生成名称为_foo的成员变量。 - 如果指定了成员变量的名称,会生成一个指定名称的成员变量;
即,'@synthesize foo = _myFoo'则生成一个名称为_myFoo的成员变量。 - 如果这个成员变量已经存在了,就不再生成新的变量了;
@interface Person : NSObject
{
NSString *_firstName;
}
@property (nonatomic, copy) NSString *firstName;
此时,由于已存在名为_firstName的成员变量,就不再为firstName属性合成新的成员变量了。
17. 在有了自动合成属性实例变量后,@synthesize还有哪些使用场景?
在回答这个问题前,先搞清楚一个问题:什么时候不会autosynthesis(自动合成),即不自动生成成员变量?
1.同时重写了setter和getter时;
2.重写了只读属性的getter时;
3.使用了@dynamic时;
4.@protocol中定义的所有属性;
5.在category中定义的所有属性;
6.重载的属性。
所以,使用@synthesize场景:
1.同时重写了setter和getter时,系统不再自动合成,需使用@synthesize合成;
2.重写了只读属性的getter时,系统不再自动合成,需使用@synthesize合成;
3.不使用默认名称的实例变量进行合成,而是使用自定义名称的成员变量进行合成;
4.遵守协议的类,实现协议中的成员变量。
18. objc中向一个nil对象发送消息将会发生什么?
在Objective-C中向nil发送消息是完全有效的,只是在运行时不会有任何作用:
Person *mother = [[Person spouse] mother];
如果spouse对象是nil,那么发送给nil(spouse)的消息mother也将放回nil。
向nil发送消息分为以下几种情况:
- 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil);
- 如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void *),float,double,long double或者long long的整型标量,发送给nil的消息将返回0;
- 如果方法返回值为结构体,发送给nil的消息将返回0。结构体中各个字段的值都将是0;
- 如果方法的返回值不是上述提到的几种情况,那么发送给nil的消息的返回值将是未定义的。
类在runtime中的源代码:
struct objc_class
{
Class isa OBJC_ISA_AVAILABILITY; // 类的isa指针指向Meta Class,因为Objc的类的本身也是一个Object,为了处理这个关系,runtime就创造了Meta Class,当给类发送[NSObject alloc]这样的消息时,实际上是把这个消息发给了Class Object
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC_UNAVAILABLE; // 方法缓存,对象接到一个消息会根据isa指针查找消息对象,这时会在method Lists中遍历,如果cache了,常用的方法调用时就能够提高调用的效率
struct objc_protocol_list *portocols OBJC2_UNAVAILABLE;// 协议链表
#endif
} OBJC2_UNAVAILABLE;
Objective-C在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找该方法进行运行。在发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用时执行的。如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。
19. objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系?
[objc foo]在编译之后就是objc_msgSend()函数调用。
20. 什么时候会报unrecognized selector异常?
当调用该对象的某个方法,而该对象没有实现这个方法的时候会报unrecognized selector异常。
Objective-C是动态语言,每个方法在运行时会被动态转为消息发送,即:Objc_msgSend(receiver, selector);
Objective-C在向一个对象发送消息时,runtime会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类的方法列表中寻找该方法运行。如果,在最顶层的父类中依然找不到相应的方法,程序会在运行时挂掉并抛出异常unrecognized selector sent to XXX。但是在这之前,Objective-C的运行时有三次拯救程序崩溃的机会。
- Method resolution(方法解析)
Objective-C运行时会调用+ (BOOL)resolveInstanceMethod:(SEL)sel
或者+ (BOOL)resolveClassMethod:(SEL)sel
,让开发者有机会提供一个函数实现。如果开发者添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则,就会进行下一步,消息转发(Method Forwarding)。 - Fast Forwarding
如果目标对象实现了- (id)forwardingTargetForSelector:(SEL)aSelector
,Runtime这时就会调用这个方法,把这个消息转发给其他对象处理,只要这个方法的返回不是nil或self,整个消息发送的过程就会被重启,发送的对象就会变成返回的对象。否则,就会继续Normal Forwarding。
这里叫Fast,是为了区别下一步的转发机制,因为这一步不会创建任何新的对象,但是下一步Normal Forwarding会创建一个NSInvocation对象,所以这一步相对快。 - Normal Forwarding
这一步是runtime最后一次给开发者的挽救机会。
首先,它会发送-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
(方法签名:Objective-C对方法的参数个数、参数类型以及返回值类型的描述)
消息获得函数的参数个数、参数类型和返回值类型。如果- methodSignatureForSelector:
返回nil,runtime则会发出- (void)doesNotRecognizeSelector:(SEL)aSelector
消息,程序这时也挂掉了。如果返回了一个函数签名,runtime就会创建一个NSInvocation对象并发送- forwardInvocation:
消息给目标对象。
假设有方法签名为"@@:@",第一个@表示返回值类型为id,第二个@表示的是函数的调用者类型,第三个表示SEL,第四个@表示需要一个id类型的参数。
例子:
- (void)hello;对应的方法签名:v16@0:8,v16表示返回值类型为空,@0表示receiver(调用者)类型为id,:8表示SEL 方法标识。
- (id)hello:(id)x对应的方法签名:@24@0:8@16,@24表示返回值类型为id类型,@0表示receiver(调用者)类型为id,:8表示SEL方法标识,@16标识参数返回值类型为id类型。
-(void)hello:(id)x :(id)e对应的方法签名:v32@0:8@16@24,v32表示返回值类型为空,@0表示receiver(调用者)类型为id,:8表示SEL方法标识,@16和@24表示返回值参数为id类型。
总结:
v16,v32等表示返回类型为空,@16,@24,@32等表示返回值类型或参数类型为id类型,@0表示receiver类型为id,:8表示SEL方法标识。
网友评论