Unit 2

@synthesize与@dynamic有什么区别?

前者在Unit1中已有说明,这里就不再赘述。@dynamic@synthesize出现在相同的位置,dynamic是动态的意思。何为动态?也就是系统不默认帮你生成属性的gettersetter方法,甚至于连“_属性名”这一个实例变量都需要你手动添加,下面是一段测试代码:

@implementation Test

@dynamic name;

- (void)setName:(NSString *)name {

}

- (NSString *)name {
    //return _name;// error:Use of undeclared identifier '_name'

    return nil;
}

@end

不过你不手动实现属性的存取方法,要是你在整个程序中均不使用的话(这是不可能的!)程序在编译运行时均正常表现。但如果你访问了对象的属性,在你“骗过”编译器后,你还是躲不掉运行时的审查:啊哦!程序崩溃!

ARC下,不显式指定属性关键字时,默认的关键字是什么?

基本类型(如NSInteger之类的):atomic、readwrite、assign
对象类型:atomic、readwrite、strong

注:所有的属性的读写关键字默认均为readwrite,另一个关键字为readonly(只读)。

self与super的区别是什么?

先贴一段代码:

@implementation Son : Father
   - (id)init {
       self = [super init];
       if (self) {
           NSLog(@"%@", NSStringFromClass([self class]));
           NSLog(@"%@", NSStringFromClass([super class]));
       }
       return self;
   }
   @end

实际上输出结果均为:Son

self是类的隐藏参数,在动态方法中表示调用此方法的实例对象,在静态方法中表示调用此方法的类对象。而super的本质是一个编译器标识符,它与self指向的是同一消息接收者,它们的不同之处在于:当使用super进行方法调用时,编译器会去调用它父类的方法,而self调用时编译器会直接调用当前类中的方法。

现在来分析上述例子,先分析[self class]

<1> 消息的发送。实例的方法调用将通过这样一个函数来传递消息:

//后面的省略参数是指方法调用时传入的实参
void objc_msgSend(id self, SEL op, ... ) 

//在这个例子中将转换为如下形式
objc_msgSend(self, @selector(class));

<2> 方法实现的查找。根据之前说好的,运行时首先会在实例当前的类中去查找class方法,也就是Son类,显然没有!于是立马跑到Son的父类中看看有木有,还是没有!然后通过一系列的查找过程,最终在NSObject类中发现了这个方法。下面是方法的实现:(参见objc Runtime源代码)

- (Class)class {
   return object_getClass(self);
}

即方法的实现是返回self的类,即Son

再来分析[super class]:

<1> 消息的发送。不同于实例方法调用,此时的传递函数为:

void objc_msgSendSuper(struct objc_super *super, SEL op, ...)

这里只需要说明objc_super是什么,其定义如下:

struct objc_super {
      __unsafe_unretained id receiver;
      __unsafe_unretained Class super_class;
};

前面也已经说过,superself指向的是同一消息接收者,也就是这个结构体第一个成员receiver将被赋值为self,第二个成员赋值为self的父类,即Father(过程可以表示为:class_getSuperclass(objc_getClass(self))。这个结构体指针变量的构造是自动完成的,姑且把它取名为aSuper,于是消息发送就可表示为:

objc_msgSendSuper(aSuper, @selector(class))

<2> 方法实现的查找。无疑,最终还是在NSObject这个类中找到了该方法。与前者不同的是,此处还将进行一次消息的发送:

objc_msgSend(objc_super->receiver, @selector(class))

最后调用返回结果。可见前者和后者的最终调用形式是一毛一样的,自然它们的打印结果也是相同的。

为了加深对它们的理解,这里再试图分析一个例子:

//main.m

ChenPerson *person = [[ChenPerson alloc] init];
person.lastName = @"haha";


//Person.m

- (instancetype)init {
    self = [super init];
    if (self) {
        self.lastName = @"";
        NSLog(@"调用者所属类:%@", [self class]);
    }
    return self;
}

- (void)setLastName:(NSString *)lastName {
    NSLog(@"类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, @"根本不会调用此方法");
    _lastName = lastName;
}

//ChenPerson.m    ChenPerson: Person

@synthesize lastName = _lastName;//在父类中属性合成的实例变量是不可见的,是父类私有的。子类需要手动为属性添加实例变量才能单独访问"_属性名",或者直接使用self.属性名

- (instancetype)init {
    self = [super init];
    if (self) {
        NSLog(@"类名与方法名:%s(在第%d行) 描述:%@", __PRETTY_FUNCTION__, __LINE__, [self class]);
        NSLog(@"类名与方法名:%s(在第%d行) 描述:%@", __PRETTY_FUNCTION__, __LINE__, [super class]);
    }
    return self;
}

- (void)setLastName:(NSString *)lastName {
    NSLog(@"lastName:%@", lastName);
    _lastName = lastName;

    NSLog(@"类名与方法名:%s(在第%d行) 描述:%@", __PRETTY_FUNCTION__, __LINE__, @"会调用这个方法,想一下为什么");
}

打印结果为:

lastName:
类名与方法名:-[ChenPerson setLastName:](在第29行) 描述:会调用这个方法,想一下为什么
ChenPerson
类名与方法名:-[ChenPerson init](在第19行) 描述:ChenPerson
类名与方法名:-[ChenPerson init](在第20行) 描述:ChenPerson
lastName:haha
类名与方法名:-[ChenPerson setLastName:](在第29行) 描述:会调用这个方法,想一下为什么

分析打印内容过程如下:

<1> 在main函数中构造ChenPerson对象。此时自然而然进入到ChenPerson.m文件中执行init方法,需要注意到的是它调用了父类(Person)的init方法,由之前所解释的可以知道,此时方法的实际调用者是ChenPerson对象,所以在父类的初始化方法中使用setter方法给lastName赋值实际上执行的是子类所覆写的方法体,由下面的打印类的信息可以得到验证。随后父类的init方法执行完毕返回一个实例对象,继续执行子类的init方法,自然而然会有接下来的两行打印信息。

<2> 在main函数中给对象属性赋值。毋庸置疑,调用的是子类所覆写的setter方法。

以上就是不推荐在init方法中使用点语法的原因,即该类的子类有可能会覆写setter,导致父类的一些属性不能正常的赋初值。因此建议在这个方法中直接通过对应的实例变量(_ivar)初始化,而不是self.ivar。

程序运行时什么时候会抛出unrecognized selector异常

这个问题的答案很直白:当一个对象接收到一个没有自己没有实现的消息时,就会发生这个异常,导致程序运行崩溃。

然而事实却并不是这么简单。原来当发生上述情况时,Objective-C的运行时会让我们有机会拯救这种糟糕的形势,即消息转发,而且我们会有三次机会来挽回。

1. Method resolution

如果在当前对象的顶层父类依旧找不到方法的实现,objc运行时就会调用当前对象所属类的如下方法:(前提是你已经实现了下列方法,如果没有,将直接进入第二步。这就是为什么默认情况就直接抛出了异常,因为这三步的解决方法你均没有实现。)

//关于对象方法的处理
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    //如果你决定在第一步中处理转发来的消息,在你确认之前,你需要给出具体的消息处理实现,下同。这里的sendMessage:只是在接口文件中给出了声明,没有给出具体的方法实现体
    if (sel == @selector(sendMessage:)) {
        class_addMethod([self class], sel, imp_implementationWithBlock(^(id self, NSString *word) {
            NSLog(@"method resolution way : send message = %@", word);
        }), "v@*");
    }
    return YES;
}

//关于类方法的处理
+ (BOOL)resolveClassMethod:(SEL)sel {
    //消息的具体处理

    return YES;
}

如果运行时检测到你给出了相关实现,运行时系统就会重新启动一次该消息的发送过程。如果你只是简单返回了YES,而并没有任何实质的行动,运行时将进行下一步:消息转发(Message Forwarding)

2. Fast forwarding

第二步,运行时就会调用当前对象所属类的如下方法:(给你把消息转发给其他对象的机会,前提是转发对象不为nilself。)

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(sendMessage:)) {
        return [[MessageFastForwarding alloc] init];
    }

    return nil;
}

当你返回了一个对象之后,整个的消息发送过程又会重启一次,只是这次的消息接受者变成了你返回的那个对象。为了区分下一步的转发,这里称它为快速转发,因为在这一步中没有创建NSInvocation对象,相对于第三步会快些。

3. Normal forwarding

值得提醒的是,这是你最后一次拯救程序的机会!如果前面两步你都没有给出响应的措施,运行时就会首先调用当前类的如下方法,来获取正在被转发处理的消息的参数和返回值类型:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
    if (!methodSignature) {
        methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
    }

    return methodSignature;//返回函数签名成功后,系统会创建一个NSInvocation对象,并调用forwardInvocation:方法
}

如果你返回的是nil,运行时就会发出-doesNotRecognizeSelector:消息,至此拯救程序之路就以失败告终,程序崩溃,并抛出异常:unrecognized selector send to XXX!如果运行时获得了一个方法签名,它就会创建一个NSInvocation对象并调用如下方法来完成最终消息的处理:

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    MessageNormalForwarding *normal = [[MessageNormalForwarding alloc] init];
    if ([normal respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:normal];
    }
}

那么现在更为详尽的回答这个问题:当一个对象接收到一个没有自己没有实现的消息时,同时它所属类没有用Method resolution、Fast forwarding、Normal forwarding来拯救程序时,就会发生这个异常,导致程序运行崩溃。

另:以上方法是为了解决紧急情况而使用的,通常情况下我们不会用到它,因为可以明确看到一些性能消耗。

runtime如何通过selector找到对应的IMP地址?

每一个类对象中都一个方法列表,方法列表中记录着方法的名称、方法实现以及参数类型,其实selector本质就是方法名称,runtime通过这个方法名称就可以在方法列表中找到对应的方法实现。

本节更多参见这里

参考并整理自:

微博@iOS程序犭袁:iOSInterviewQuestions
sunnyxx:招聘一个靠谱的iOS

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值