内容简介:如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。也不会崩溃。详解: 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil);如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*) ,float,double,long double 或者long long的整型标量,发送给nil的消息将返回0;
如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。也不会崩溃。
详解: 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil);
如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*) ,float,double,long double 或者long long的整型标量,发送给nil的消息将返回0;
如果方法返回值为结构体,发送给nil的消息将返回0。结构体中各个字段的值将都是0;
如果方法的返回值不是上述提到的几种情况,那么发送给nil的消息的返回值将是未定义的。
3.objc中向一个对象发送消息[obj foo]和 objc_msgSend() 函数之间有什么关系?
在objc编译时,[obj foo] 会被转意为: objc_msgSend(obj, @selector(foo)); 。
详解:请看 Runtime在工作中的运用 第二章Runtime消息机制;
4.什么时候会报unrecognized selector的异常?
objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,会进入消息转发阶段,如果消息三次转发流程仍未实现,则程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。
详解:请看 Runtime在工作中的运用 第三章Runtime方法调用流程;
5.能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
不能向编译后得到的类中增加实例变量;
能向运行时创建的类中添加实例变量;
1.因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表和 instance_size 实例变量的内存大小已经确定,同时runtime会调用 class_setvarlayout 或 class_setWeaklvarLayout 来处理strong weak 引用.所以不能向存在的类中添加实例变量。 2.运行时创建的类是可以添加实例变量,调用class_addIvar函数. 但是的在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上.
6.给类添加一个属性后,在类结构体里哪些元素会发生变化?
instance_size :实例的内存大小;objc_ivar_list *ivars:属性列表
7.一个objc对象的isa的指针指向什么?有什么作用?
指向他的类对象,从而可以找到对象上的方法
详解:下图很好的描述了对象,类,元类之间的关系:
图中实线是 super_class指针,虚线是isa指针。
- Root class (class)其实就是NSObject,NSObject是没有超类的,所以Root class(class)的superclass指向nil。
- 每个Class都有一个isa指针指向唯一的Meta class
- Root class(meta)的superclass指向Root class(class),也就是NSObject,形成一个回路。
- 每个Meta class的isa指针都指向Root class (meta)。
8.[self class] 与 [super class]
下面的代码输出什么?
@implementation Son : Father
- (id)init
{
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
复制代码
NSStringFromClass([self class]) = Son NSStringFromClass([super class]) = Son
详解:这个题目主要是考察关于 Objective-C 中对 self 和 super 的理解。
self 是类的隐藏参数,指向当前调用方法的这个类的实例;
super 本质是一个编译器标示符,和 self 是指向的同一个消息接受者。不同点在于:super 会告诉编译器,当调用方法时,去调用父类的方法,而不是本类中的方法。
当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;而当使用 super 时,则从父类的方法列表中开始找。然后调用父类的这个方法。
在调用 [super class] 的时候,runtime会去调用 objc_msgSendSuper 方法,而不是 objc_msgSend ;
OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained Class class;
#else
__unsafe_unretained Class super_class;
#endif
/* super_class is the first class to search */
};
复制代码
在objc_msgSendSuper方法中,第一个参数是一个objc_super的结构体,这个结构体里面有两个变量,一个是接收消息的receiver,一个是当前类的父类super_class。
objc_msgSendSuper的工作原理应该是这样的: 从objc_super结构体指向的superClass父类的方法列表开始查找selector,找到后以objc->receiver去调用父类的这个selector。注意,最后的调用者是objc->receiver,而不是super_class!
那么objc_msgSendSuper最后就转变成:
// 注意这里是从父类开始msgSend,而不是从本类开始
objc_msgSend(objc_super->receiver, @selector(class))
/// Specifies an instance of a class. 这是类的一个实例
__unsafe_unretained id receiver;
// 由于是实例调用,所以是减号方法
- (Class)class {
return object_getClass(self);
}
复制代码
由于找到了父类NSObject里面的class方法的IMP,又因为传入的入参objc_super->receiver = self。self就是son,调用class,所以父类的方法class执行IMP之后,输出还是son,最后输出两个都一样,都是输出son。
9.runtime如何通过selector找到对应的IMP地址?
每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.
10._objc_msgForward函数是做什么的,直接调用它将会发生什么?
_objc_msgForward 是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候, _objc_msgForward 会尝试做消息转发。
详解: _objc_msgForward 在进行消息转发的过程中会涉及以下这几个方法:
-
resolveInstanceMethod:方法 (或resolveClassMethod:)。 -
forwardingTargetForSelector:方法 -
methodSignatureForSelector:方法 -
forwardInvocation:方法 -
doesNotRecognizeSelector:方法
具体请见:请看 Runtime在工作中的运用 第三章Runtime方法调用流程;
11. runtime如何实现weak变量的自动置nil?知道SideTable吗?
runtime 对注册的类会进行布局,对于 weak 修饰的对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
更细一点的回答:
1.初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2.添加引用时:objc_initWeak函数会调用objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3.释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
SideTable结构体是负责管理类的引用计数表和weak表,
详解:参考自《Objective-C高级编程》一书 1.初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
{
NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;
}
复制代码
当我们初始化一个weak变量时,runtime会调用 NSObject.mm 中的objc_initWeak函数。
// 编译器的模拟代码 id obj1; objc_initWeak(&obj1, obj); /*obj引用计数变为0,变量作用域结束*/ objc_destroyWeak(&obj1); 复制代码
通过 objc_initWeak 函数初始化“附有weak修饰符的变量(obj1)”,在变量作用域结束时通过 objc_destoryWeak 函数释放该变量(obj1)。
2.添加引用时:objc_initWeak函数会调用objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
objc_initWeak 函数将“附有weak修饰符的变量(obj1)”初始化为0(nil)后,会将“赋值对象”(obj)作为参数,调用 objc_storeWeak 函数。
obj1 = 0; obj_storeWeak(&obj1, obj); 复制代码
也就是说:
weak 修饰的指针默认值是 nil (在Objective-C中向nil发送消息是安全的)
然后 obj_destroyWeak 函数将0(nil)作为参数,调用 objc_storeWeak 函数。
objc_storeWeak(&obj1, 0); 复制代码
前面的源代码与下列源代码相同。
// 编译器的模拟代码 id obj1; obj1 = 0; objc_storeWeak(&obj1, obj); /* ... obj的引用计数变为0,被置nil ... */ objc_storeWeak(&obj1, 0); 复制代码
objc_storeWeak 函数把第二个参数的赋值对象(obj)的内存地址作为键值,将第一个参数__weak修饰的属性变量(obj1)的内存地址注册到 weak 表中。如果第二个参数(obj)为0(nil),那么把变量(obj1)的地址从weak表中删除。
由于一个对象可同时赋值给多个附有__weak修饰符的变量中,所以对于一个键值,可注册多个变量的地址。
可以把 objc_storeWeak(&a, b) 理解为: objc_storeWeak(value, key) ,并且当key变nil,将value置nil。在b非nil时,a和b指向同一个内存地址,在b变nil时,a变nil。此时向a发送消息不会崩溃:在Objective-C中向nil发送消息是安全的。
3.释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?当释放对象时,其基本流程如下:
1.调用objc_release
2.因为对象的引用计数为0,所以执行dealloc
3.在dealloc中,调用了_objc_rootDealloc函数
4.在_objc_rootDealloc中,调用了object_dispose函数
5.调用objc_destructInstance
6.最后调用objc_clear_deallocating
对象被释放时调用的objc_clear_deallocating函数:
1.从weak表中获取废弃对象的地址为键值的记录
2.将包含在记录中的所有附有 weak修饰符变量的地址,赋值为nil
3.将weak表中该记录删除
4.从引用计数表中删除废弃对象的地址为键值的记录
总结:
其实Weak表是一个hash(哈希)表,Key是weak所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。
12.isKindOfClass 与 isMemberOfClass
下面代码输出什么?
@interface Sark : NSObject
@end
@implementation Sark
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
NSLog(@"%d %d %d %d", res1, res2, res3, res4);
}
return 0;
}
复制代码
1000
详解:
在 isKindOfClass 中有一个循环,先判断 class 是否等于 meta class ,不等就继续循环判断是否等于 meta class 的 super class ,不等再继续取 super class ,如此循环下去。
[NSObject class] 执行完之后调用 isKindOfClass ,第一次判断先判断 NSObject 和 NSObject 的 meta class 是否相等,之前讲到 meta class 的时候放了一张很详细的图,从图上我们也可以看出, NSObject 的 meta class 与本身不等。接着第二次循环判断 NSObject 与 meta class 的 superclass 是否相等。还是从那张图上面我们可以看到: Root class(meta) 的 superclass 就是 Root class(class) ,也就是NSObject本身。所以第二次循环相等,于是第一行res1输出应该为YES。
同理, [Sark class] 执行完之后调用 isKindOfClass ,第一次for循环,Sark的 Meta Class 与 [Sark class] 不等,第二次for循环, Sark Meta Class 的 super class 指向的是 NSObject Meta Class , 和 Sark Class 不相等。第三次for循环, NSObject Meta Class 的 super class 指向的是 NSObject Class ,和 Sark Class 不相等。第四次循环, NSObject Class 的 super class 指向 nil, 和 Sark Class 不相等。第四次循环之后,退出循环,所以第三行的res3输出为NO。
isMemberOfClass 的源码实现是拿到自己的isa指针和自己比较,是否相等。 第二行isa 指向 NSObject 的 Meta Class ,所以和 NSObject Class 不相等。第四行,isa指向Sark的 Meta Class ,和 Sark Class 也不等,所以第二行res2和第四行res4都输出NO。
13.使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?
无论在MRC下还是ARC下均不需要,被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被 NSObject -dealloc 调用的object_dispose()方法中释放。
详解:
1、调用 -release :引用计数变为零 对象正在被销毁,生命周期即将结束. 不能再有新的 __weak 弱引用,否则将指向 nil. 调用 [self dealloc] 2、 父类调用 -dealloc 继承关系中最直接继承的父类再调用 -dealloc 如果是 MRC 代码 则会手动释放实例变量们(iVars) 继承关系中每一层的父类 都再调用 -dealloc >3、NSObject 调 -dealloc 只做一件事:调用 Objective-C runtime 中object_dispose() 方法 >4. 调用 object_dispose() 为 C++ 的实例变量们(iVars)调用 destructors 为 ARC 状态下的 实例变量们(iVars) 调用 -release 解除所有使用 runtime Associate方法关联的对象 解除所有 __weak 引用 调用 free() 复制代码
14. 什么是method swizzling(俗称黑魔法)
简单说就是进行方法交换
详解:请看 Runtime在工作中的运用 第五章Runtime方法交换;
在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。
每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP。
换方法的几种实现方式
- 利用 method_exchangeImplementations 交换两个方法的实现
- 利用 class_replaceMethod 替换方法的实现
- 利用 method_setImplementation 来直接设置某个方法的IMP
15.Compile Error / Runtime Crash / NSLog…?
下面的代码会? Compile Error / Runtime Crash / NSLog… ?
@interface NSObject (Sark)
+ (void)foo;
- (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo {
NSLog(@"IMP: -[NSObject (Sark) foo]");
}
@end
// 测试代码
[NSObject foo];
[[NSObject new] performSelector:@selector(foo)];
复制代码
IMP: -[NSObject(Sark) foo] ,全都正常输出,编译和运行都没有问题。
详解:
这道题和上一道题很相似,第二个调用肯定没有问题,第一个调用后会从元类中查找方法,然而方法并不在元类中,所以找元类的 superclass 。方法定义在是 NSObject 的 Category ,由于 NSObject 的对象模型比较特殊,元类的 superclass 是类对象,所以从类对象中找到了方法并调用。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Building Social Web Applications
Gavin Bell / O'Reilly Media / 2009-10-1 / USD 34.99
Building a social web application that attracts and retains regular visitors, and gets them to interact, isn't easy to do. This book walks you through the tough questions you'll face if you're to crea......一起来看看 《Building Social Web Applications》 这本书的介绍吧!
HEX HSV 转换工具
HEX HSV 互换工具
HSV CMYK 转换工具
HSV CMYK互换工具