注册

iOS 常见面试题总结及答案(3)

一.列举出延迟调用的几种方法?

1.performSelector方法 

[self performSelector:@selector(Delay) withObject:nil afterDelay:3.0f];

2.NSTimer定时器  

[NSTimer scheduledTimerWithTimeInterval:3.0f target:self selector:@selector(Delay) userInfo:nil repeats:NO];

3.sleepForTimeInterval

[NSThread sleepForTimeInterval:3.0f];

4.GCD方式

dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self Delay];
});
- (void)Delay {
NSLog(@"执行");
}

二.NSCache 和NSDictionary 区别?

NSCache可以提供自动删减缓存功能,而且保证线程安全,与字典不同,不会拷贝键。
NSCache可以设置缓存上限,限制对象个数和总缓存开销。定义了删除缓存对象的时机。这个机制只对NSCache起到指导作用,不会一定执行。
NSPurgeableData搭配NSCache使用,可以自动清除数据。
只有那种“重新计算很费劲”的数据才值得放入缓存。

三.NSArray 和 NSSet区别

NSSet和NSArray功能性质一样,用于存储对象,属于集合。
NSSet属于 “无序集合”,在内存中存储方式是不连续
NSArray是 “有序集合” 它内存中存储位置是连续的。
NSSet,NSArray都是类,只能添加对象,如果需要加入基本数据类型(int,float,BOOL,double等),需要将数据封装成NSNumber类型。
由于NSSet是用hash实现的所以就造就了它查询速度比较快,但是我们不能把某某对象存在第几个元素后面之类的有关下标的操作。

四.什么是分类?

分类: 在不修改原有类代码的情况下,可以给类添加方法
Categroy 给类扩展方法,或者关联属性, Categroy底层结构也是一个结构体:内部存储这结构体的名字,那个类的分类,以及对象和类方法列表,协议,属性信息
通过Runtime加载某个类的所有Category数据
把所有Category的方法、属性、协议数据,合并到一个大数组中后面参与编译的Category数据,会在数组的前面
将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

五.为什么说OC是一门动态语言?

动态语言:是指程序在运行时可以改变其结构,新的函数可以被引进,已有的函数可以被删除等在结构上的变化
动态类型语言: 就是类型的检查是在运行时做的。
OC的动态特性可从三方面:

动态类型(Dynamic typing):最终判定该类的实例类型是在运行期间
动态绑定(Dynamic binding):在运行时确定调用的方法
动态加载(Dynamic loading):在运行期间加载需要的资源或可执行代码

六.什么是动态绑定?

动态绑定 将调用方法的确定也推迟到运行时。OC可以先跳过编译,到运行的时候才动态地添加函数调用,在运行时才决定要调用什么方法,需要传什么参数进去,这就是动态绑定。
在编译时,方法的 调用并不和代码绑定在一起,只有在消实发送出来之后,才确定被调用的代码。通过动态类型和动态绑定技术,

七.什么是谓词?

谓词(NSPredicate)是OC针对数据集合的一种逻辑帅选条件,类似一个过滤器,简单实实用代码如下:

Person * p1 = [Person personWithName:@"alex" Age:20];
Person * p2 = [Person personWithName:@"alex1" Age:30];
Person * p3 = [Person personWithName:@"alex2" Age:10];
Person * p4 = [Person personWithName:@"alex3" Age:40];
Person * p5 = [Person personWithName:@"alex4" Age:80];

NSArray * persons = @[p1, p2, p3, p4, p5];
//定义谓词对象,谓词对象中包含了过滤条件
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age < 30"];
//使用谓词条件过滤数组中的元素,过滤之后返回查询的结果
NSArray *array = [persons filteredArrayUsingPredicate:predicate];

八.什么是类工厂方法?

类工厂方法就是用来快速创建对象的类方法, 他可以直接返回一个初始化好的对象,具备以下特征:

一定是类方法
返回值需要是 id/instancetype 类型
规范的方法名说说明类工厂方法返回的是一个什么对象,一般以类名首字母小写开始;
比如系统 UIButton 的buttonWithType 就是一个类工厂方法:

// 类工厂方法
+ (instancetype)buttonWithType:(UIButtonType)buttonType;
// 使用
+ UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];

九.简要说明const,宏,static,extern区分以及使用?

1.const

const常量修饰符,经常使用的字符串常量,一般是抽成宏,但是苹果不推荐我们抽成宏,推荐我们使用const常量。

- const 作用:限制类型
- 使用const修饰基本变量, 两种写法效果一致 , b都是只读变量
const int b = 5;
int const b = 5;
- 使用const修饰指针变量的变量
第一种: const int *p = &a 和 int const *q = &a; 效果一致,*p 的值不能改,p 的指向可以改;
第二种: int * const p = &a; 表示 p 的指向不能改,*p 的值可以改
第三种:
const int * const p = &a; *p 值和 p 的指向都不能改

const 在*左边, 指向可变, 值不可变
const 在*的右边, 指向不可变, 值可变
const 在*的两边, 都不可变

2.

* 基本概念:宏是一种批量处理的称谓。一般说来,宏是一种规则或模式,或称语法替换 ,用于说明某一特定输入(通常是字符串)如何根据预定义的规则转换成对应的输出(通常也是字符串)。这种替换在预编译时进行,称作宏展开。编译器会在编译前扫描代码,如果遇到我们已经定义好的宏那么就会进行代码替换,宏只会在内存中copy一份,然后全局替换,宏一般分为对象宏和函数宏。 宏的弊端:如果代码中大量的使用宏会使预编译时间变长。

const与宏的区别?

* 编译检查 宏没有编译检查,const有编译检查;
* 宏的好处 定义函数,方法 const不可以;
* 宏的坏处 大量使用宏,会导致预编译时间过长

3.static

* 修饰局部变量: 被static修饰局部变量,延长生命周期,跟整个应用程序有关,程序结束才会销毁,被 static 修饰局部变量,只会分配一次内存
* 修饰全局变量: 被static修饰全局变量,作用域会修改,也就是只能在当前文件下使用

4.extern

声明外部全局变量(只能用于声明,不能用于定义)

常用用法(.h结合extern联合使用)
如果在.h文件中声明了extern全局变量,那么在同一个类中的.m文件对全局变量的赋值必须是:数据类型+变量名(与声明一致)=XXXX结构。并且在调用的时候,必须导入.h文件。代码如下:

.h
@interface ExternModel : NSObject
extern NSString *lhString;
@end
.m
@implementation ExternModel
NSString *lhString=@"hello";
@end

调用的时候:例如:在viewController.m中调用,则可以引入:ExternModel.h,否则无法识别全局变量。当然也可以通过不导入头文件的方式进行调用(通过extern调用)。

十.id类型, nil , Nil ,NULL和NSNULL的区别?

id类型: 是一个独特的数据类型,可以转换为任何数据类型,id类型的变量可以存放任何数据类型的对象,在内部处理上,这种类型被定义为指向对象的指针,实际上是一个指向这种对象的实例变量的指针; id 声明的对象具有运行时特性,既可以指向任意类型的对象
nil 是一个实例对象值;如果我们要把一个对象设置为空的时候,就用nil
Nil 是一个类对象的值,如果我们要把一个class的对象设置为空的时候,就用Nil
NULL 指向基本数据类型的空指针(C语言的变量的指针为空)
NSNull 是一个对象,它用在不能使用nil的场合

十一.C和 OC 如何混编&&Swift 和OC 如何调用?

1.xcode可以识别一下几种扩展名文件:

.m文件,可以编写 OC语言 和 C 语言代码
.cpp: 只能识别C++ 或者C语言(C++兼容C)
.mm: 主要用于混编 C++和OC代码,可以同时识别OC,C,C++代码

2.Swift 调用 OC代码

需要创建一个 Target-BriBridging-Header.h 的桥文件,在乔文件导入需要调用的OC代码头文件即可

3.OC 调用 Swift代码
直接导入 Target-Swift.h文件即可, Swift如果需要被OC调用,需要使用@objc 对方法或者属性进行修饰

十二.OC与 JS交互方式有哪些?

1.通过拦截URL

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSString *url = request.URL.absoluteString;
if ([url rangeOfString:@"需要跳转源生界面的URL判断"].location != NSNotFound) {
//跳转原生界面
return NO;
}
return YES;
}

2.使用MessageHandler(WKWebView)

当JS端想传一些数据给iOS.那它们会调用下方方法来发送.
window.webkit.messageHandlers.<方法名>.postMessage(<数据>)上方代码在JS端写会报错,导致网页后面业务不执行.可使用try-catch执行.
那么在OC中的处理方法如下.它是WKScriptMessageHandler的代理方法.name和上方JS中的方法名相对应.

- (void)addScriptMessageHandler:(id )scriptMessageHandler name:(NSString *)name;

3.JavaScriptCore (UIWebView)
使用三方库WebViewJavascriptBridge,可提供 js 调OC,以及OC掉JS

1. 设置 webViewBridge
_bridge = [WKWebViewJavascriptBridge bridgeForWebView:self.webView];
[_bridge setWebViewDelegate:self];
2. 注册handler方法,需要和 前段协商好 方法名字,是供 JS调用Native 使用的。
[_bridge registerHandler:@"scanClick" handler:^(id data, WVJBResponseCallback responseCallback) {
// OC调用
NSString *scanResult = @"http://www.baidu.com";
// js 回调传参
responseCallback(scanResult);
}];
3. OC掉用JS
[_bridge callHandler:@"testJSFunction" data:@"一个字符串" responseCallback:^(id responseData) {
NSLog(@"调用完JS后的回调:%@",responseData);
}];

4.OC调用JS代码

// 直接运行 使用 
NSString *jsStr = @"执行的JS代码";
[webView stringByEvaluatingJavaScriptFromString:jsStr];

// 使用JavaScriptCore框架
#import
- (void)webViewDidFinishLoad:(UIWebView *)webView {
//获取webview中的JS内容
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
NSString *runJS = @"执行的JS代码";
//准备执行的JS代码
[context evaluateScript:runJS];
}

十三.编译过程做了哪些事情

Objective,Swift都是编译语言。编译语言在执行的时候,必须先通过编译器生成机器码,机器码可以直接在CPU上执行,所以执行效率较高。Objective,Swift二者的编译都是依赖于Clang + LLVM. OC和Swift因为原理上大同小异,知道一个即可!
1.iOS编译 不管是OC还是Swift,都是采用Clang作为编译器前端,LLVM(Low level vritual machine)作为编译器后端。
2.编译器前端 :编译器前端的任务是进行:语法分析,语义分析,生成中间代码(intermediate representation )。在这个过程中,会进行类型检查,如果发现错误或者警告会标注出来在哪一行
3.编译器后端 :编译器后端会进行机器无关的代码优化,生成机器语言,并且进行机器相关的代码优化。LVVM优化器会进行BitCode的生成,链接期优化等等,LLVM机器码生成器会针对不同的架构,比如arm64等生成不同的机器码。

十四.Category的实现原理&&使用场合&&Class Extension的区别

1.Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
2.在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)

使用场合:

在不修改原有类代码的情况下,为类添对象方法或者类方法
或者为类关联新的属性
分解庞大的类文件

添加实例方法
添加类方法
添加协议
添加属性
关联成员变量

区别

Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中。

十五.Category能否添加成员变量?如果可以,如何给Category添加成员变量?

不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果
Category是发生在运行时,编译完毕,类的内存布局已经确定,无法添加成员变量(Category的底层数据结构也没有成员变量的结构)
可以通过 runtime 动态的关联属性

十六.Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?

有load方法
load方法在runtime加载类、分类的时候调用
load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用

十七.initialize方法如何调用,以及调用时机

当类第一次收到消息的时候会调用类的initialize方法
是通过 runtime 的消息机制 objc_msgSend(obj,@selector()) 进行调用的
优先调用分类的 initialize, 如果没有分类会调用 子类的,如果子类未实现则调用 父类的

十八.load、initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?

load 是类加载到内存时候调用, 优先父类->子类->分类
initialize 是类第一次收到消息时候调用,优先分类->子类->父类
同级别和编译顺序有关系
load 方法是在 main 函数之前调用的

十九.什么是Runtime?平时项目中有用过么?

Objective-C runtime是一个运行时库,它为Objective-C语言的动态特性提供支持,我们所写的OC代码在运行时都转成了runtime相关的代码,类转换成C语言对应的结构体,方法转化为C语言对应的函数,发消息转成了C语言对应的函数调用。通过了解runtime以及源码,可以更加深入的了解OC其特性和原理

OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行

OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数

平时编写的OC代码,底层都是转换成了Runtime API进行调用

具体应用

利用关联对象(AssociatedObject)给分类添加属性
遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
交换方法实现(交换系统的方法)
利用消息转发机制解决方法找不到的异常问题

二十.讲一下 OC 的消息机制

1.OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
2.objc_msgSend底层有3大阶段   消息发送(当前类、父类中查找)、动态方法解析、消息转发

消息发送流程

当我们的一个 receiver(实例对象)收到消息的时候, 会通过 isa 指针找到 他的类对象, 然后在类对象方法列表中查找 对应的方法实现,如果 未找到,则会通过 superClass 指针找到其父类的类对象, 找到则返回,未找打则会一级一级往上查到,最终到NSObject 对象, 如果还是未找到就会进行动态方法解析
类方法调用同上,只不过 isa 指针找到元类对象;

动态方法解析机制&&消息转发机制流程

当我们发送消息未找到方法实现,就会进入第二步,动态方法解析: 代码实现如下

//  动态方法绑定- 实例法法调用
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(run)) {
Method method = class_getInstanceMethod(self, @selector(test));
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
return YES;
}
return [super resolveInstanceMethod:sel];
}
// 类方法调用
+(BOOL) resolveClassMethod:(SEL)sel....

未找到动态方法绑定,就会进行消息转发阶段

// 快速消息转发- 指定消息处理对象
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(run)) {
return [Student new];
}
return [super forwardingTargetForSelector:aSelector];
}

// 标准消息转发-消息签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if(aSelector == @selector(run))
{
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
//内部逻辑自己处理
}

答案摘自作者:iOS猿_员

原贴链接:https://www.jianshu.com/p/4aaf45c11082

0 个评论

要回复文章请先登录注册