人猿星球

iOS 面试遇到 基础知识 总结

2018-11-20  本文已影响22人  _Andy_

最近换工作 面试 发现自己好多基础知识点不牢固,在此总结记录下(随时更新)
自己遇到的面试流程 可能和别人不一样 只说下自己的感觉

大公司和小公司的 面试区别

小公司面试前会让你填个人信息,身份证 ,亲人信息,个人信息泄露严重,感觉特不安全。
大公司 面试前不会要你的信息,技术ok在聊别的,更加注重基础,很多会现场勾画出场景出题

在现实生活中,你和谁在一起共事的确很重要,甚至能改变你生活的轨迹,决定你的人生成败。和什么样的人在一起,就会有什么样的人生。
和勤奋的人在一起,你不会懒惰;
和积极的人在一起,你不会消沉;
与智者同行,你会不同凡响;
与高人为伍,你能登上巅峰。

关键字

static , const, extern ,__weak, __block,typeof ,sizeof,@synthesize,@dynamic

static

  1. 修饰局部变量
    让局部变量只初始化一次
    局部变量在程序中只有一份内存
    并不会改变局部变量的作用域,仅仅是改变了局部变量的生命周期(只到程序结束,这个局部变量才会销毁)
  2. 修饰全局变量
    全局变量的作用域仅限于当前文件

const

宏与const的区别

  1. 编译时刻: 宏:预编译(编译之前处理) const:编译时刻

  2. 编译检查: 宏:不会检查错误,不会报编译错误,只是简单替换 const:会检查错误

  3. 宏的好处:可以定义代码

  4. 宏的坏处:使用大量宏,容易造成编译时间久,每次都需要重新替换,因此常用的字符串通常使用const修饰

const作用

  1. const仅仅用来修饰 右边 的变量(基本数据变量p,指针变量*p)
  2. 被const修饰的变量是只读的,不可以修改。

extern:

也称之为外部变量,是在方法外部定义的变量。它不属于哪个方法,而是属于整个源程序,作用域是整个源程序。如果全局便利和局部变量重名,则在局部变量作用域内,全局变量被屏蔽,不起作用。编程时候尽量不使用全局变量。

__weak

__weak 修饰的指针最重要的特性是其指向的对象销毁后,会自动置为 nil,这个特性的实现完全是依靠运行时的。实现思路是非常简单的,对于下面的语句来说:

id __weak weakObj = strongObj;

便是用 strongObj 当作 key,weakObj 当作 value 存入一个表里。当 strongObj 销毁时,从表里找到所有的 __weak 引用,将其置为 nil。

__block

  1. __block对象在block中是可以被修改、重新赋值的。
  2. __block对象在block中不会被block强引用一次,从而不会出现循环引用问题。
  3. blocks可以访问局部变量,但是不能修改。如果修改局部变量,需要加__block ,所以__block是让修改外部变量的值.

typeof sizeof

typeof和sizeof用法非常类似!
sizeof(exp.)返回的是exp.的数据类型大小;
typeof(exp.)返回的就是exp.的数据类型。exp.可以是任意类型,所以返回的也是和exp.对应的任意类型。

__block typeof(self) bself = self;// MRC
__weak typeof(self) weakself = self;//ARC

@synthesize

@synthesize的语义是如果你没有手动实现setter方法和getter方法,那么在编译的时候编译器会自动为你加上这两个方法。

@dynamic

@dynamic告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成。(当然对于readonly的属性只需提供getter即可)。

深浅拷贝

  1. 不可变对象它的copy出来的对象地址和原对象一样是浅拷贝,而mutableCopy后的对象地址和原对象地址不一样,是深拷贝。
  2. 可变字对象,它的copy和mutableCopy出来的对象地址和原对象地址都不是一样的,是深拷贝。

原对象和拷贝对象都是不可变对象时,为浅拷贝。
其他情况均为深拷贝。

字符串NSString的copy和strong的不同

copy修饰的String1,String1等于String2,String2在重新赋值以后,可变字符串String2发生了变化并不会影响String1的值。因为二者不是一个地址,所以不会相互影响

被strong修饰以后只是强指针引用,并未改变地址,所以String1的值会随着String2进行变化,二者的地址也是相同的。

各种循环引用解决方案

NSTimer循环引用

由于循环引用的起因是target,则可以包装一个target,让target是另一个对象,而不是ViewController即可.
创建一个集成NSObject的类TimerWeakTarget,创建类方法---开启定时器的方法

1.重写开启定时器方法,在内部对target进行替换,换成本类(TimerWeakTarget)的对象即可
2.不会造成循环引用了,原控制器属性有timer对timer强应用,timer内部对self强引用,但是self在此方法内部被替换成了本类的对象(TimerWeakTarget *),而本类的对象不会对控制器r强引用,则不会造成循环引用,也就不会造成内存泄露

#import <Foundation/Foundation.h>
@interface TimerWeakTarget : NSObject
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer *timer;
@property (nonatomic, weak) id target;

+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      target:(id)aTarget
                                    selector:(SEL)aSelector
                                    userInfo:(id)userInfo
                                     repeats:(BOOL)repeats;
@end

#import "TimerWeakTarget.h"
@implementation TimerWeakTarget
+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      target:(id)aTarget
                                    selector:(SEL)aSelector
                                    userInfo:(id)userInfo
                                     repeats:(BOOL)repeats{

    TimerWeakTarget * timer = [TimerWeakTarget new];
    timer.target = aTarget;
    timer.selector = aSelector;
    //此处的target已经被换掉了不是原来的VIewController而是TimerWeakTarget类的对象timer
    timer.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:timer selector:@selector(fire:) userInfo:userInfo repeats:repeats];
    return timer.timer;
}

-(void)fire:(NSTimer *)timer{
    if (self.target) {
        [self.target performSelector:self.selector withObject:timer.userInfo];
    } else {

        [self.timer invalidate];
    }
}

@end

wkWebView js调oc循环引用

由于循环引用的起因代理强引用了,新建一个NSObject类
对WKScriptMessageHandler 协议重新用weak定义为属性Delegate
在重写init 方法并传参味WKScriptMessageHandler,在init方法内 传进来的参数等于弱引用的属性Delegate,NSObject类内 在实现协议方法userContentController:didReceiveScriptMessage
使用弱引用属性Delegate 调用此方法

#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
@interface MyWeakScriptMessageDelegate : NSObject<WKScriptMessageHandler>

@property (nonatomic, weak) id<WKScriptMessageHandler> scriptDelegate;

- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate;

@end

#import "MyWeakScriptMessageDelegate.h"
@implementation MyWeakScriptMessageDelegate
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate {
    self = [super init];
    if (self) {
        _scriptDelegate = scriptDelegate;
    }
    return self;
}

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
}

@end

ViewController里调用

 [self.wkWebView.configuration.userContentController addScriptMessageHandler:[[MyWeakScriptMessageDelegate alloc]initWithDelegate:self] name:@"约定的方法"];

- (void)dealloc{
    [self.wkWebView.configuration.userContentController removeScriptMessageHandlerForName:@"约定的方法"];  
}

@synchronized

 @synchronized (self) {
        //Safe
    }

该同步方法的优点就是我们不需要在代码中显式的创建锁对象,便可以实现锁的机制,synchronized是互斥锁。然而,滥用@synchronized (self)则会降低代码效率,因为公用同一个锁的那些同步块,都必须按顺序执行。若是在self对象上频繁加锁,程序可能要等另一段与此无关的代码执行完毕,才能继续执行当前代码,这样效率就低了。

- (void)synchronizedAMethod {
    @synchronized (self) {
        //Safe
    }
}
- (void)synchronizedBMethod {
    @synchronized (self) {
        //Safe
    }
}
- (void)synchronizedCMethod {
    @synchronized (self) {
        //Safe
    }
}

以上代码,如果当前synchronizedAMethod方法正在执行,则synchronizedBMethod和synchronizedCMethod方法需要等待synchronizedAMethod完毕后才能执行,不能达到并发的效果。

互斥锁优缺点

优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的CPU资源
互斥锁的使用前提:多条线程抢夺同一块资源的时候使用

堆栈

Objective-C的对象在内存中是以堆的方式分配空间的,并且堆内存是由你释放的,就是release
OC对象存放于堆里面(堆内存要程序员手动回收)
非OC对象一般放在栈里面(栈内存会被系统自动回收)
堆里面的内存是动态分配的,所以也就需要程序员手动的去添加内存、回收内存

  1. 按管理方式分
    对于栈来讲,是由系统编译器自动管理,不需要程序员手动管理
    对于堆来讲,释放工作由程序员手动管理,不及时回收容易产生内存泄露
  2. 按分配方式分
    堆是动态分配和回收内存的,没有静态分配的堆
    栈有两种分配方式:静态分配和动态分配
    静态分配是系统编译器完成的,比如局部变量的分配
    动态分配是有alloc函数进行分配的,但是栈的动态分配和堆是不同的,它的动态分配也由系统编译器进行释放,不需要程序员手动管理

事件传递以及响应者链条 简单总结

事件传递:

Application -> UIWindow -> 父控件view -> 子控件view
view 判断
不允许与用户交互userInteractionEnabled = NO
这个控件隐藏了hidden = YES
透明度太小了alpha = 0.0 ~ 0.01

事件响应:

子控件view -> 父控件view -> UIWindow->Application

总结

事件传递:自上而下
事件响应:自下而上

CPU GPU 关系

一个app的展示会包含很多内容,诸如,label,imageview,button等等。这些控件的位置,大小,颜色则都是由CPU来计算,计算完成后CPU会将这些数据提交给GPU来进行渲染,只有经过GPU的渲染才能显示在屏幕上

卡顿优化-CPU

1.尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用CAlayer取代UIView;能用基本数据类型,就别用NSNumber类型。
2.不要频繁地跳用UIVIew的相关属性,比如frame、bounds、transform等属性,尽量减少不必要的修改
3.尽量提前计算好布局,在有需要时一次性调整对应的布局,不要多次修改属性
4.Autolayout会比直接设置frame消耗更多的CPU资源
5.图片的size最好刚好跟UIImageView的size保持一致
6.控制一下线程的最大并发数量
7.尽量把耗时的操作放到子线程
8.文本处理(尺寸的计算,绘制)
9.图片处理(解码、绘制)

卡顿优化-GPU

1.尽量减少视图数量和层次
2.GPU能处理的最大纹理尺寸是4096x4096,一旦超过这个尺寸,就会占用CPU资源进行处理,所以纹理尽量不要超过这个尺寸
3.尽量避免段时间内大量图片的显示,尽可能将多张图片合成一张图片显示
4.减少透明的视图(alpha<1),不透明的就设置opaque为yes
5.尽量避免出现离屏渲染

离屏渲染

在OpenGL中,GPU有2种渲染方式:
1.On-SCreen Rendering:当前屏幕渲染,在当前用语显示的屏幕缓冲区进行渲染操作。
2.Off-Screen Rendring: 离屏渲染,在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。
离屏渲染消耗性能的原因:
1.需要创建新的缓冲区;
2离屏渲染的整个过程,需要多次切换上下文环境,先是从当前屏幕切换到离屏;等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕
哪些操作会出发离屏渲染?
1.光栅化,layer.shouldRasterize = YES
2.遮罩,layer.mask
3.圆角,同时设置layer.maskToBounds = Yes,Layer.cornerRadis 大于0
考虑通过CoreGraphics绘制裁剪圆角,或者美工提供圆角图片
4.阴影,layer.shadowXXX
如果设置了layer.shadowPath就不会产生离屏渲染

优化离屏渲染

只要你提前告诉CoreAnimation你要渲染的View的形状Shape,就会减少离屏渲染计算

[myView.layer setShadowPath:[[UIBezierPath bezierPathWithRect:myView.bounds] CGPath];

加上这行代码,就减少离屏渲染时间,大大提高了性能

CPU 线程的关系

多线程的原理

同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
思考:如果线程非常非常多,会发生什么情况?
CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源
每条线程被调度执行的频次会降低(线程的执行效率降低)

多线程的优缺点

  1. 多线程的优点
    能适当提高程序的执行效率
    能适当提高资源利用率(CPU、内存利用率)

  2. 多线程的缺点
    开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
    线程越多,CPU在调度线程上的开销就越大
    程序设计更加复杂:比如线程之间的通信、多线程的数据共享

上一篇 下一篇

猜你喜欢

热点阅读