面试题目都是网上收集,回答是自己尝试回答,网上的很多回答我觉得不一定对,会自己修改,有问题希望可以及时指出来
1.Object-c的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么?
1.OC没有多重集成
2.但可以遵循多个协议也就是protocol
3.Category是类别
4.重写一个类的方式,用继承好还是分类好?为什么?
这个问题中“重写”和“方式”这两个词就很奇怪,这个类如果是你自己写的,对不满意的地方直接修改就可以了,干嘛要继承和分类登场呢?即使这个类你没源码,但是你对这个类各种不满意,你要重写一个类代替,也跟继承和分类没直接关系啊。
我从两个方向去尝试猜测下这道面试题的意思,
猜测1:“方式”改为“”方法“,面试官是想问重写一个类的方法,用继承好还是分类好?这里网上的很多回答已经严重不对了,例如网上大多回答是这样的:
“一般情况用分类好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系”
这个回答如果回答者的意思是说如果其他类没有引入这个分类,就不会调用到分类的实现,还会是这个类原有的实现,就打错特错了,实际上如果只要你分类覆盖了这个类的某个方法的实现,你在工程中即使不引入这个分类,也都会调用到分类的实现。
我们可以写代码试下:我们先新建一个工程,然后写一个Test的类,直接继承NSObject就好,然后我们定义了两个方法,一个方法让分类直接覆盖,一个分类间接覆盖真正的实现
- (NSString *)whoAmI; //让分类直接覆盖
- (void)indirectMethodTest; //分类覆盖间接的方法
实现代码如下
- (NSString *)whoAmI {
return @"Test";
}
- (void)indirectMethodTest {
[self iAmDirect]; //真正调用的是iAmDirect
}
- (void)iAmDirect {
NSLog(@"iAmDirect Test");
}
然后我们让分类分别实现,“whoAmI”和“iAmDirect”
- (NSString *)whoAmI {
return @"Test+Change";
}
- (void)iAmDirect {
NSLog(@"iAmDirect Test+Change");
}
我们在控制其中不引入类别只引入Test.h
控制器viewDidLoad加入如下代码
Test *test = [Test new];
NSString *string = [test whoAmI];
NSLog(@"string = %@", string);
[test indirectMethodTest];
然后控制台打印下这两个方法的结果
可以发现即使你没引入类别,这个类的实现,也会被分类的方法替换掉,具体原因,可以看下分类的实现原理,我以后会详细说下,简单来说就是runtime把categoray实现的方法,放在了原来类方法的前面,这样必定每次都会先找到分类的方法。不过要记住分类只会覆盖这个类的原有实现,对子类是不会覆盖的,这个是msgsend的调用流程决定的。
所以针对猜测1,重写一个类的方法来说,我觉得用继承最好,除非你真的觉得这个原来的方法没救了,你要一劳永逸的替换原有类的实现,而且不想修改使用到这个方法的其他类的代码了,你也已经通知了你在的开发组同事了,你可以去这么做,不过这种方式实在不明智,连苹果都给了警告不要这么做
”Category is implementing a method which will also be implemented by its primary class“
用继承的方式则要好的多:
1.你用子类重写这方法之后,你只需要在不满意的地方,用子类替换即可,不需要改的地方,还可以继续使用以前的类。
2.可以使用super调用这个类的原来的实现前后,子类做一些修改,因为子类的super指向的是这个类,而类别的super,指向的是还是这个类的父类,因为类别毕竟不是继承啊。
猜测2:重写改为扩展,面试官想问:扩展一个类的方式,用继承好还是分类好?为什么?
这个好回答很多,就是问继承和分类的区别,优劣势。
继承:
1.要重写原来类中的方法,但父类的这个方法也想继续使用。
2.增加成员变量,属性
类别:
1.系统特定类没法继承,重点说的就是类簇,比如NSString,NSArray,NSNumber等,这些返回的真实的类,都是一些私有隐藏的类,如果继承,将相当麻烦。相反使用类别去扩展一些功能,将方便很多。
2.用分类,将一些复杂的类,按功能将的方法拆分到单独的文件中,可以提高可维护性,iOS系统中的类就经常这么做。例如把NSObject延时执行的方法例如:
(void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay
等方法放到了runloop文件中,因为这些方法runloop相关的。
2.#import 跟#include 又什么区别,@class呢, #import<> 跟 #import”"又什么区别?
1.#include的作用就是导入头文件并展开,#import是改良版本,只会展开首次遇到头文件,后面再遇到相同的头文件,不会再展开了。
2.@class的作用仅仅是告诉编译器有这么个类,并没有引入这个类的更多信息了。@class的使用场景更多的是避免循环依赖,例如:
//A.h文件
#import"B.h"
@interfaceA : NSObject
@property(strong, nonatomic) B *_b;
@end
//B.h文件
#import“A.h"
@interfaceB : NSObject
@property(strong, nonatomic) A *_a;
@end
这个编译就会报错,@class可以很好的避免这个问题。其次也会提高编译速度,因为不用直接展开了。
3.#import<> 跟 #import”"有什么区别
其实是搜索顺序和位置的的区别,具体可参考这个。
简书有篇文章讲的很好 Objective-C 中的 import 和 Search Paths 和这篇文章clang是如何查找头文件的
我总结起来就是如下:
1.#import<>会查询Header search Path和系统文件夹
2.#import""会先查询本文件夹和 user header search path,然后再寻找Header search Path和系统文件夹
3.NSInteger是什么
NSInteger是基本数据类型Int或者Long的别名(NSInteger的定义typedef long NSInteger),64位系统下为long,其他为int,看下源码就知道了:
#if __LP64__ || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif
4.类别的作用是什么
1.为一个类增加方法,又不想去继承,但最好不要覆盖原有方法
2.将类的实现分散在不同文件或者不同框架中
3.创建对私有方法的前向引用
这个很有意思,就是说有个类如果某些方法没有在接口文件中公开,只要把这个私有方法写到类别中,引入这个类别就可以直接调用了.
5.类别与类扩展的区别
这个直接从网上拿过来了,总结的很好。
①类别中原则上只能增加方法(能添加属性的的原因只是通过runtime解决无setter/getter的问题而已);
②类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型的(
用范围只能在自身类,而不是子类或其他地方);
③类扩展中声明的方法没被实现,编译器会报警,但是类别中的方法没被实现编译器是不会有任何警告的。这是因为类扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中。
④类扩展不能像类别那样拥有独立的实现部分(@implementation部分),也就是说,类扩展所声明的方法必须依托对应类的实现部分来实现。
⑤定义在 .m 文件中的类扩展方法为私有的,定义在 .h 文件(头文件)中的类扩展方法为公有的。类扩展是在 .m 文件中声明私有方法的非常好的方式。
6.为什么说OC是运行时语言?
OC的动态特性表现为了三个方面:动态类型、动态绑定、动态加载。之所以叫做动态,是因为必须到运行时(run time)才会做一些事情。
(1)动态类型
动态类型是跟静态类型相对的,静态类型在 编译的时候就能被识别出来。所以,若程序发生了类型不对应,编译器就会发出警告。而动态类型就编译器编译的时候是不能被识别的,要等到运行时(run time),即程序运行的时候才会根据语境来识别.
(2)动态绑定
动态绑定(dynamic binding)貌似比较难记忆,但事实上很简单,只需记住关键词@selector/SEL即可。先来看看“函数”,对于其他一些静态语言,比如 c++,一般在编译的时候就已经将将要调用的函数的函数签名都告诉编译器了。静态的,不能改变。而在OC中,其实是没有函数的概念的,我们叫“消息机制”,所谓的函数调用就是给对象发送一条消息。这时,动态绑定的特性就来了。OC可以先跳过编译,到运行的时候才动态地添加函数调用,在运行时才决定要调 用什么方法,需要传什么参数进去。这就是动态绑定,要实现他就必须用SEL变量绑定一个方法。最终形成的这个SEL变量就代表一个方法的引用。这里要注意 一点:SEL并不是C里面的函数指针,虽然很像,但真心不是函数指针。SEL变量只是一个整数,他是该方法的ID,@selector()就是取类方法的编号。以前的函数调用,是根据函数名,也就是 字符串去查找函数体。但现在,我们是根据一个ID整数来查找方法,整数的查找字自然要比字符串的查找快得多!所以,动态绑定的特定不仅方便,而且效率更 高。
由于OC的动态特性,在OC中其实很少提及“函数”的概念,传统的函数一般在编译时就已经把参数信息和函数实现打包到编译后的源码中了,而在OC中最常使 用的是消息机制。调用一个实例的方法,所做的是向该实例的指针发送消息,实例在收到消息后,从自身的实现中寻找响应这条消息的方法
(3)动态加载
根据需求加载所需要的资源,这点很容易理解,对于iOS开发来说,基本就是根据不同的机型做适配。最经典的例子就是在Retina设备上加载@2x的图片,而在老一些的普通屏设备上加载原图。
7.说说响应链
Altering the Responder Chain
You can alter the responder chain by overriding the next property of your responder objects. When you do this, the next responder is the object that you return.
Many UIKit classes already override this property and return specific objects, including:
UIView objects. If the view is the root view of a view controller, the next responder is the view controller; otherwise, the next responder is the view’s superview.
UIViewController objects.
If the view controller’s view is the root view of a window, the next responder is the window object.
If the view controller was presented by another view controller, the next responder is the presenting view controller.
UIWindow objects. The window's next responder is the UIApplication object.
UIApplication object. The next responder is the app delegate, but only if the app delegate is an instance of UIResponder and is not a view, view controller, or the app object itself.
总结出来如下:
UIView的nextResponder属性,如果有管理此view的UIViewController对象,则为此UIViewController对象;否则nextResponder即为其superview。
UIViewController的nextResponder属性为其管理view的superview.
UIWindow的nextResponder属性为UIApplication对象。
UIApplication的nextResponder属性为appdelegate。
8.方法和选择器有何不同?
SEL:定义: typedef struct objc_selector *SEL,代表方法的名称。仅以名字来识别。翻译成中文叫做选择子或者选择器,选择子代表方法在 Runtime期间的标识符。为 SEL类型,虽然 SEL是 objc_selector 结构体指针,但实际上它只是一个 C 字符串。在类加载的时候,编译器会生成与方法相对应的选择子,并注册到 Objective-C的 Runtime 运行系统。不论两个类是否存在依存关系,只要他们拥有相同的方法名,那么他们的SEL都是相同的。比如,有n个viewcontroller页面,每个页面都有一个viewdidload,每个页面的载入,肯定都是不尽相同的。但是我们可以通过打印,观察发现,这些viewdidload的SEL都是同一个
SEL sel = @selector(methodName); // 方法名字 NSLog(@"address = %p",sel);// log输出为 address = 0x1df807e29因此类方法定义时,尽量不要用相同的名字,就算是变量类型不同也不行。否则会引起重复
IMP:定义:typedef id (*IMP)(id, SEL, ...),代表函数指针,即函数执行的入口。该函数使用标准的 C调用。第一个参数指向 self(它代表当前类实例的地址,如果是类则指向的是它的元类),作为消息的接受者;第二个参数代表方法的选择子;... 代表可选参数,前面的 id 代表返回值。
Method:定义:typedef struct objc_method *Method,Method对开发者来说是一种不透明的类型,被隐藏在我们平时书写的类或对象的方法背后。它是一个objc_method结构体指针,我们可以看到该结构体中包含一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。有了SEL,我们便可以找到对应的IMP,从而调用方法的实现代码。 objc_method的定义为:
/// Method
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
};
方法名 method_name 类型为 SEL,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
方法类型 method_types 是个 char 指针,其实存储着方法的参数类型和返回值类型,即是 Type Encoding 编码。
method_imp 指向方法的实现,本质上是一个函数的指针,就是前面讲到的 IMP
9.如果我们不创建内存池,是否有内存池提供给我们?
1.首先主线程,会自动创建内存池
2.子线程不会自动创建,但是系统在子线程没检测到autorelease pool会创建一个,然后在子线程要销毁的时候也销毁这个autorelease pool
当有大量的autorelease对象产生,需要即时回收内存的时候。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。