未经本人允许,不得转载!违者必究!
未经本人允许,不得转载!违者必究!
未经本人允许,不得转载!违者必究!
本面试题为个人使用版本,答案正确性有待商榷,本人的答案不代表权威,仅仅是个人理解,可以做个参照供各位朋友看看思路。 文章内部有写混乱,将就着看吧。
一、硬技术篇
1.对象方法和类方法的区别?
- 对象方法能个访问成员变量。
- 类方法中不能直接调用对象方法,想要调用对象方法,必须创建或者传入对象。
- 类方法可以和对象方法重名。
引伸1. 如果在类方法中调用self 会有什么问题?
- 在 实例方法中self不可以调用类方法,此时的self不是Class。
- 在类方法中self可以调用其他类方法。
- 在类方法中self不可以调用实例方法。
- 总结:类方法中的self,是class/ 实例方法中self是对象的首地址。
引申2. 讲一下对象,类对象,元类,跟元类结构体的组成以及他们是如何相关联的?
- 对象的结构体当中存放着isa指针和成员变量,isa指针指向类对象
- 类对象的isa指针指向元类,元类的isa指针指向NSObject的元类
- 类对象和元类的结构体有isa,superClass,cache等等
引申3. 为什么对象方法中没有保存对象结构体里面,而是保存在类对象的结构体里面?
- 方法是每个对象相互可以共用的,如果每个对象都存储一份方法列表太浪费内存,由于对象的isa是指向类对象的,当调用的时候, 直接去类对象中去查找就可以了,节约了很多内存空间。
引申4. 类方法存在哪里? 为什么要有元类的存在?
- 所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。
为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念,元类中保存了创建类对象以及类方法所需的所有信息。
引申5. 什么是野指针?
- 野指针就是指向一个被释放或者被收回的对象,但是对指向该对象的指针没有做任何修改,以至于该指针让指向已经回收后的内存地址。
- 其中访问野指针是没有问题的,使用野指针的时候会出现崩溃Crash!样例如下
__unsafe_unretained UIView *testObj = [[UIView alloc] init];
NSLog(@"testObj 指针指向的地址:%p 指针本身的地址:%p", testObj, &testObj);
[testObj setNeedsLayout];
// 可以看到NSlog打印不会闪退,调用[testObj setNeedsLayout];会闪退
引申6. 如何检测野指针?
这是网友总结的,有兴趣的可以看下://www.greatytc.com/p/9fd4dc046046?utm_source=oschina-app
本人,也就是看看乐呵,其原理啥的,见仁见智吧。开发行业太j8难了!
引申7. 导致Crash的原因有哪些?
1、找不到方法的实现unrecognized selector sent to instance
2、KVC造成的crash
3、EXC_BAD_ACCESS
4、KVO引起的崩溃
5、集合类相关崩溃
6、多线程中的崩溃
7、Socket长连接,进入后台没有关闭
8、Watch Dog超时造成的crash
9、后台返回NSNull导致的崩溃,多见于Java做后台服务器开发语言
引申8. 不使用第三方,如何知道已经上线的App崩溃问题, 具体到哪一个类的哪一个方法的?
大致实现方式如下。
- 使用NSSetUncaughtExceptionHandler可以统计闪退的信息。
- 将统计到的信息以data的形式 利用网络请求发给后台
- 在后台收集信息,进行排查
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NSSetUncaughtExceptionHandler(&my_uncaught_exception_handler);
return YES;
}
static void my_uncaught_exception_handler (NSException *exception) {
//这里可以取到 NSException 信息
NSLog(@"***********************************************");
NSLog(@"%@",exception);
NSLog(@"%@",exception.callStackReturnAddresses);
NSLog(@"%@",exception.callStackSymbols);
NSLog(@"***********************************************");
}
iOS中内省的几个方法?
- isMemberOfClass //对象是否是某个类型的对象
- isKindOfClass //对象是否是某个类型或某个类型子类的对象
- isSubclassOfClass //某个类对象是否是另一个类型的子类
- isAncestorOfObject //某个类对象是否是另一个类型的父类
- respondsToSelector //是否能响应某个方法
- conformsToProtocol //是否遵循某个协议
引申 2. ==、 isEqualToString、isEqual区别?
- == ,比较的是两个指针的值 (内存地址是否相同)。
- isEqualToString, 比较的是两个字符串是否相等。
- isEqual 判断两个对象在类型和值上是否都一样。
引申 3. class方法和object_getClass方法有什么区别?
- 实例class方法直接返回object_getClass(self)
- 类class直接返回self
- 而object_getClass(类对象),则返回的是元类
3.深拷贝和浅拷贝
- 所谓深浅指的是是否创建了一个新的对象(开辟了新的内存地址)还是仅仅做了指针的复制。
- copy和mutableCopy针对的是可变和不可变,凡涉及copy结果均变成不可变,mutableCopy均变成可变。
- mutableCopy均是深复制。
- copy操作不可变的是浅复制,操作可变的是深赋值。
4.NSString类型为什么要用copy修饰 ?
- 主要是防止NSString被修改,如果没有修改的说法用Strong也行。
- 当NSString的赋值来源是NSString时,strong和copy作用相同。
- 当NSString的赋值来源是NSMutableString,copy会做深拷贝,重新生成一个新的对象,修改赋值来源不会影响NSString的值。
5.iOS中block 捕获外部局部变量实际上发生了什么?__block 中又做了什么?
block 捕获的是当前在block内部执行的外部局部变量的瞬时值, 为什么说瞬时值呢? 看一下C++源码中得知, 其内部代码在捕获的同时
其实block底层生成了一个和外部变量相同名称的属性值如果内部修改值,其实修改的是捕获之前的值,其捕获的内部的值因代码只做了一次捕获,并没有做再一次的捕获,所以block里面不可以修改值。
如果当前捕获的为对象类型,其block内部可以认为重新创建了一个指向当前对象内存地址的指针(堆),操控内部操作的东西均为同一块内存地址,所以可以修改当前内部的对象里面的属性,但是不能直接修改当前的指针(无法直接修改栈中的内容)(即重新生成一个新的内存地址)。其原理和捕获基本数据类型一致。
说白了, block内部可以修改的是堆中的内容, 但不能直接修改栈中的任何东西。
- 如果加上__block 在运行时创建了一个外部变量的“副本”属性,把栈中的内存地址放到了堆中进而在block内部也能修改外部变量的值。
6.iOS Block为什么用copy修饰?
- block 是一个对象
- MRC的时候 block 在创建的时候,它的内存比较奇葩,非得分配到栈上,而不是在传统的堆上,它本身的作用于就属于创建的时候(见光死,夭折),一旦在创建时候的作用于外面调用它会导致崩溃。
- 所以,利用copy把原本在栈上的复制到堆里面,就保住了它。
- **ARC的时候 由于ARC中已经看不到栈中的block了。用strong和copy 一样 随意, 用copy是遵循其传统, **
7. 为什么分类中不能创建属性Property(runtime除外)?
分类的实现原理是将category中的方法,属性,协议数据放在category_t结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。 Category可以添加属性,但是并不会自动生成成员变量及set/get方法。因为category_t结构体中并不存在成员变量。通过之前对对象的分析我们知道成员变量是存放在实例对象中的,并且编译的那一刻就已经决定好了。而分类是在运行时才去加载的。那么我们就无法再程序运行时将分类的成员变量中添加到实例对象的结构体中。因此分类中不可以添加成员变量。
在往深一点的回答就是 类在内存中的位置是编译时期决定的, 之后再修改代码也不会改变内存中的位置,class_ro_t 的属性在运行期间就不能再改变了, 再添加方法是会修改class_rw_t 的methods 而不是class_ro_t 中的 baseMethods
引伸:分类可以添加那些内容?
- 实例方法,类方法,协议,属性
引伸:Category 的实现原理?
- Category 在刚刚编译完成的时候, 和原来的类是分开的,只有在程序运行起来的时候, 通过runtime合并在一起。
引申 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?
- 不需要,被关联的对象的生命周期内要比对象本身释放晚很多, 它们会在被 NSObject -dealloc 调用的 object_dispose() 方法中释放。
引申 能否向编译后得到的类中增加实例变量, 能否向运行时创建的类中添加实力变量?
- 不能再编译后得到的类中增加实例变量。因为编译后的类已经注册在runtime中, 类结构体中objc_ivar_list 实例变量的链表和objc_ivar_list 实例变量的内存大小已经确定,所以不能向存在的类中添加实例变量
- 能在运行时创建的类中添加实力变量。调用class_addIvar 函数
引申 主类执行了foo方法,分类也执行了foo方法,在执行的地方执行了foo方法,主类的foo会被覆盖么? 如果想只想执行主类的foo方法,如何去做?
- 主类的方法被分类的foo覆盖了,其实分类并没有覆盖主类的foo方法,只是分类的方法排在方法列表前面,主类的方法列表被挤到了后面, 调用的时候会首先找到第一次出现的方法。
- 如果想要只是执行主类的方法,可逆序遍历方法列表,第一次遍历到的foo方法就是主类的方法
- (void)foo{
[类 invokeOriginalMethod:self selector:_cmd];
}
+ (void)invokeOriginalMethod:(id)target selector:(SEL)selector {
uint count;
Method *list = class_copyMethodList([target class], &count);
for ( int i = count - 1 ; i >= 0; i--) {
Method method = list[I];
SEL name = method_getName(method);
IMP imp = method_getImplementation(method);
if (name == selector) {
((void (*)(id, SEL))imp)(target, name);
break;
}
}
free(list);
}