美文网首页
面试问题(杂记)

面试问题(杂记)

作者: 深海时代 | 来源:发表于2020-03-04 08:35 被阅读0次

main函数之前执行什么?

1.dyld 读取APP可执行文件,分析mach-header

2.load dylibs 开启缓存,创建线程,加载依赖库,启动runtime

3.rebase bind,runtime初始化类结构,Load

4.返回主函数地址

一个NSObject对象占多少内存?

16个字节,(isa指针占8个,但是对象在allocWithZone时留的空间是16个)

initialize和init以及load方法的区别与使用以及什么时候调用

initialize不是init

initialize在这个类第一次被调用的时候比如[[class alloc]init]会调用一次initialize方法,不管创建多少次这个类,都只会调用一次这个方法,我们用它来初始化静态变量,而init方法是只要这个类被调用,就会调用这个init方法,这个类被调用几次,这个init方法就会被调用几次,当有一个类继承这个类,是这个类的子类的时候,当子类被调用的时候比如子类被[[class alloc]init]的时候,父类的initialize和init方法都会被调用一次,

init继承于NSObject这个根类,所有的子类可以不用重写这个实例方法函数,当然也可以在自己的类里重写init实例方法。

1 - (id) init

2 {

3 if ( self = [super init] )

4 {

5 // Class-specific initializations

6 }

7 return self;

8 }

首先得调用父类的init函数方法,然后在if的语句块里写一些自己特色的初始化操作(第5行)。

可以在你实例化的时候提供更多参数以便实现对对象的快速赋值。重写init可以让你创建的对象出来就是成品。而不重写的话,还需要你对该对象的属性进行赋值。

load

load 方法会在加载类的时候就被调用,也就是 ios 应用启动的时候,就会加载所有的类,就会调用每个类的 + load 方法。

在没有对类做任何操作的情况下,+load 方法会被默认执行,并且是在 main 函数之前执行的。程序启动之前会调用

load方法的调用时机在dyld映射image时期,这也符合逻辑,加载完调用load。

调用顺序:

1、类要优先于分类调用+load方法;

2、子类调用+load方法时,要先要调用父类+load方法;(父类优先与子类,与继承不同);

3、不同的类按照编译先后顺序调用+load方法(先编译,先调用);

4、分类顺序按照编译先后顺序调用+load(先编译,先调用);

这里有个问题,本类load方法为什么优于分类。因为这其实是一个表达问题,当分类的load方法放入方法列表之后,每次调用仍然是调用分类load方法跟其他方法没有不同。只不过load作为runtime构建本类成功的标志,在分类方法合并前会调用一次,这一次调用本类是不知道分类方法的,所以调用的是本类的load。

换句话说第一次执行load的时候任何分类方法都是调不到的。

渲染优化处理

1 各种圆角阴影渲染避免用drawrect(CPU)、cornerRadius等 用CAShapeLayer(GPU)和UIBezierPath(GPU)来替代。

2 利用runloop来实现在scrollview滑动的时候不加载图片,从而优化滑动帧数。

3 使用异步进行layer渲染(Facebook开源的异步绘制框架AsyncDisplayKit)

4 设置layer的opaque值为YES(GPU不用考虑多色图层层叠混色的问题),减少复杂图层合成尽量使用不包含透明(alpha)通道的图片资源(opaque=YES:GPU将不会做任何的计算合成,不需要考虑它下方的任何东西(因为都被它遮挡住了),而是简单从这个层拷贝。这节省了GPU相当大的工作量。由此看来,opaque属性的真实用处是给绘图系统提供一个性能优化开关!,)

5 尽量设置layer的大小值为整形值

6 直接让美工把图片切成圆角进行显示,这是效率最高的一种方案

7 很多情况下用户上传图片进行显示,可以让服务端处理圆角

8 使用代码手动生成圆角Image设置到要显示的View上,利用UIBezierPath(CoreGraphics框架)画出来圆角图片

9 适当的时候使用shouldRasterize开启光栅化、当一个图像混合了多个图层,每次移动时,每一帧都要重新合成这些图层,十分消耗性能。当我们开启光栅化后,会在首次产生一个位图缓存,当再次使用时候就会复用这个缓存。

自动释放池

自动释放池是一个个 AutoreleasePoolPage 组成的一个page是4096字节大小,每个 AutoreleasePoolPage 以双向链表连接起来形成一个自动释放池

AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成的栈结构(分别对应结构中的parent指针和child指针)

当对象调用 autorelease 方法时,会将对象加入 AutoreleasePoolPage 的栈中

pop 时是传入边界对象,然后对page 中的对象发送release 的消息

RunLoop

RunLoop 就是一个事件处理的循环,用来不停的调度工作以及处理输入事件。使用 RunLoop 的目的是让你的线程在有工作的时候忙于工作,而没工作的时候处于休眠状态。 runloop 的设计是为了减少 cpu 无谓的空转。

1.runloop保证线程的长久存活,多线程开发在我们的工作工程中是常常用到,一个子线程当它的任务执行完毕之后都会销毁,所以每次执行异步任务都会频繁去创建和销毁线程,这样无疑是耗费资源的。这种情况下我们可以利用runloop来保证线程在执行完任务后不背销毁而进入“休眠”状态,等待下一个任务的执行再被唤醒。

2、保证NSTimer正常运转。mode改为NSRunLoopCommonModes 即可在滚动的时候计时器也计时。

3、滚动视图流畅性优化,在我们的开发过程中经常遇到列表型上面有图片的,一般下载图片用异步,setimage则使用同步。为imageView设置image,是在UITrackingRunLoopMode中也可以进行的,如果图片很大,图片解压缩和渲染肯定会很耗时,那么卡顿就是必然的。我们可以再setImage的时候手动设置runloop的mode:

4、监测iOS卡顿。 //创建Run loop observer对象//开辟线程监听延时  //死循环监听 通过控制信号量 来实现 mainrunloop循环或者超时的时候才会执行, 累计延迟超过250ms包含 (设置连续5次超时50ms认为卡顿(当然也包含了单次超时250ms))是一个信号量机制,信号量到达、或者 超时会继续向下进行,否则等待,如果超时则返回的结果必定不为0,信号量到达结果为0。信号量为小于等于0的时候会阻塞当前线程。

5、阻止一次崩溃

@property 会自动生成set get 与变量,但是手动实现set与get时,变量将必须手动生成。

签名

数字签名

在传输数据的过程中对原始数据求哈希值,用来校验数据的完整性,通过RSA非对称加密来加密哈希值。

验签时,接收方拿到原始数据求哈希值,然后根据非对称解密解出传来的哈希值,进行对比。

苹果的代码签名

xcode内部生成一对公私钥,公钥和个人信息发给苹果后台,用苹果的私钥签名,成为证书下发给开发者,同时苹果会给出包含设备ID列表,AppID与授权文件的描述文件(同样用苹果私钥签名)。向手机装软件时,用xcode私钥签名APP(P12文件),并将其与APP和证书一起打包,手机端通过自己的公钥解密,验证证书中的公钥,再用证书中的公钥验证APP签名,同时解密描述文件来验证设备与APP是否正确,并用授权文件给出权限。

(第 1 步对应的是 keychain 里的 “从证书颁发机构请求证书”,这里就本地生成了一对公私钥,保存的 CertificateSigningRequest 里面就包含公钥,私钥保存在本地电脑里.

第 2 步向苹果申请对应把 CSR 传到苹果后台生成证书.

第 3 步证书下载到本地.这时本地有两个证书.一个是第 1 步生成的私钥,一个是这里下载回来的证书,keychain 会把这两个证书关联起来,因为他们公私钥是对应的,在XCode选择下载回来的证书时,实际上会找到 keychain 里对应的私钥去签名.这里私钥只有生成它的这台 Mac 有,如果别的 Mac 也要编译签名这个 App 怎么办?答案是把私钥导出给其他 Mac 用,在 keychain 里导出私钥,就会存成 .p12 文件,其他 Mac 打开后就导入了这个私钥.

第 4 步都是在苹果网站上操作,配置 AppID / 权限 / 设备等,最后下载 Provisioning Profile 文件。

第 5 步 XCode 会通过第 3 步下载回来的证书(存着公钥),在本地找到对应的私钥(第一步生成的),用本地私钥去签名 App,并把 Provisioning Profile 文件命名为 embedded.mobileprovision 一起打包进去。所以任何本地调试的APP,都会有一个embedded.mobileprovision(描述文件)从App Store下载的没有.)

super

super并非指向父类,也并非指向父类class对象,他是一个特殊的self,一个编译修饰符。self调用方法会调用objc_msgSend(self,@selector(class)),直接从当前实例里找class的实现。而super会调用objc_msgSendSuper(<#struct objc_super *super#>, <#SEL op, ...#>)。在第一个结构体中传入当前实例和父类class对象。父类依然按照查找树查找方法,无法继续调用本类方法。

UIScrollView使用autoLyout的注意事项

子视图约束要给到大于scrollView的contentsize才能滑动,说起来挺简单的,主要容易不知道面试官在问什么。

URLConnection与URLSession区别

connection下载资源到内存,会出现内存暴涨的情况,session写入本地。

connection无法暂停网络连接,只能终止,session可以暂停和恢复。

重写与重载

重写是重新实现方法。

重载是方法名相同时,根据输入参数类型与个数的不同调用不同的方法的机制。

线程间通信常用方法

(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

进程与线程

进程是面向CPU的事件处理对象。线程是进程的子对象,当一个进程只有一个线程的时候这个线程就是这个进程。

CPU的运算速度很快,硬件远远无法跟上CPU的运算速度,所以我们将CPU分时使用,让CPU连续负责多个工作,由此进程策略应运而生。CPU需要处理的每个事务就是一个进程对象,他包含cpu需要处理的事情和相应的资源(上下文),CPU在执行某个事务时,将事务的上下文切进来,执行,保存运算过的上下文。因为进程的粒度太大,同一个进程可能也需要同时进行多个子事务,我们启用了线程策略,多个线程在一个进程中共享相同的上下文并执行不同的任务。

NSUserDefaults如何储存model对象

将model转换为NSData存储。

归档:储存data类型 NSData*data=[NSKeyedArchiver archivedDataWithRootObject:model];

解档:解为model类型 UserDefaultsModel*resultModel=[NSKeyedUnarchiver unarchiveObjectWithData:data1];

swizing

1.我们本意交换的是子类方法,但是子类没有实现,父类实现了class_getInstanceMethod(target, swizzledSelector);执行的结果返回父类的Method,那么后续交换就相当于和父类的方法实现了交换。

一般情况下也不会出问题,可是埋下了一系列隐患。如果其它程序员也继承了这个父类。其他子类会调到这个子类的方法,有可能崩溃。

解决的方法是要替换的子类用addMethod添加目标交换方法,然后目标交换方法可以用replace替换成原方法,这样在第一步就保证不会改变父类方法,其次第二步子类取不到的方法可以父类取,不会取不到替换方法。

情况2:父类也没有实现

尴尬了,都没有实现方法,那还交换个锤子???

先说结果吧,交换函数执行后,方法不会被交换,但是手动调用下面这些,同样会死循环。

- (void)swiprintObj {NSLog(@"swi1:%@",self);    [selfswiprintObj];}

所以我们要加判断,然后返回给方法调用者一个bool值,或者更直接一点,抛出异常。

/// 交换类方法的注意获取meta class, object_getClass。class_getClassMethod+ (void)swizzleInstanceMethod:(Class)targetoriginal:(SEL)originalSelectorswizzled:(SEL)swizzledSelector {    Method originMethod = class_getInstanceMethod(target, originalSelector);    Method swizzledMethod = class_getInstanceMethod(target, swizzledSelector);if(originMethod && swizzledMethod) {if(class_addMethod(target, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {            class_replaceMethod(target, swizzledSelector, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));        }else{            method_exchangeImplementations(originMethod, swizzledMethod);        }    }else{@throw@"originalSelector does not exit";    }}

再加上 dispatch_once 上面已经算是比较完美了,但是并没有完美,主要是场景不同,情况就不同。我们只有理解原理,不同场景不同对待。

在写SDK时,分类有重名覆盖问题,编译选项还要加-ObjC。出问题编译阶段还查不出来。那么我们可以用新建一个私有类实现交换,类重名则直接编译报错。交换方法和上面的分类交换稍不一样

比如hook viewDidload,代码如下:

@interfaceSwizzleClassTest:NSObject@end@implementationSwizzleClassTest+ (void)load {/// 私有类,可以不用dispatch_onceClass target = [UIViewControllerclass];    Method swiMethod = class_getInstanceMethod(self,@selector(swi_viewDidLoad));    Method oriMethod = class_getInstanceMethod(target,@selector(viewDidLoad));if(swiMethod && oriMethod) {if(class_addMethod(target,@selector(swi_viewDidLoad), method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod))) {// 这里获取给UIViewController新增的methodswiMethod = class_getInstanceMethod(target,@selector(swi_viewDidLoad));            method_exchangeImplementations(oriMethod, swiMethod);        }    }}- (void)swi_viewDidLoad {// 不能调用,这里的self是UIViewController类或者子类的实例,调用test的话直接崩溃。或者做类型判断 [self isKindOfClass:[SwizzleClassTest class]],然后再调用// [self test];[selfswi_viewDidLoad];}- (void)test {NSLog(@"Do not do this");}@end

这里也用到class_addMethod,给UIViewController新增了一个swi_viewDidLoad sel及其imp实现,共享了SwizzleClassTest 的imp实现。

另外系统发送viewdidload消息进而调用swi_viewDidLoad方法,里面的self是UIViewController,所以不能再[self test],否则崩溃。也不能在其它地方手动[self swi_viewDidLoad];会死循环,因为这时候self是SwizzleClassTest,而它的method是没有被交换的,好处是我们可以通过self的类型判断来避免。

可以比较下交换前后,

交换前:

SwizzleClassTest_swi_viewDidLoadSel -> SwizzleClassTest_swi_viewDidLoadImp

UIViewController_viewDidLoadSel -> UIViewController_viewDidLoadImp

交换后:

SwizzleClassTest_swi_viewDidLoadSel -> SwizzleClassTest_swi_viewDidLoadImp

UIViewController_swi_viewDidLoadSel -> UIViewController_viewDidLoadImp

UIViewController_viewDidLoadSel -> UIViewController_swi_viewDidLoadImp

可以看出 SwizzleClassTest 没有受影响,映射关系不变。

这种想取消的话,也很简单method_exchangeImplementations

类别为什么不能添加属性

类别可以添加属性但是不能用

1.类别不提供成员变量列表,所以无法为本类提供对应成员变量

2.类中生成对应属性的成员变量是在Load前,而类别合成到类是在Load后,所以类别同样无法利用本类生成对应属性的成员变量。

所以类别无法产出成员变量,只能利用原有的成员变量,或用runtime关联。有成员变量之后,在类别中扩展set get方法,就可以使用类别的扩展属性了

相关文章

  • 面试问题(杂记)

    main函数之前执行什么? 1.dyld 读取APP可执行文件,分析mach-header 2.load dyli...

  • 面试内容杂记

    ThreadLocal 内置N个ThreadLocalMap 根据当前Thread对象获取到Map,key是当前T...

  • iOS面试杂记

    1. 函数局部变量的return R:一般的来说,函数是可以返回局部变量的。 局部变量的作用域只在函数内部,在函...

  • java 面试杂记

    git 和 svn 的区别 1.git 是分布式的,svn不是,每个开发人员从中心版本库/服务器上chect ou...

  • iOS面试杂记

    1. 函数局部变量的return R:一般的来说,函数是可以返回局部变量的。 局部变量的作用域只在函数内部,在函数...

  • 问题杂记

    Controller 被提前释放 get 管理 controller 的生命周期,bottomSheet 弹出,当...

  • iOS经典面试题集整理

    1、Runtime面试中问题整理 2、Runloop面试中问题整理 3、KVO面试中问题整理 4、Block面试中...

  • android面试:handler、杂记

    面向对象的编程,要提高程序的复用率,增加程序的可维护性,可扩展性 static修饰的静态属性、方法可以被子类继承,...

  • ios 面试指南思维导图

    1.UI视图相关面试问题 2.Runtime相关面试问题 3.内存管理相关面试问题 4.Block相关面试问题 5...

  • 前端问题杂记

    1.bootstrap响应式代码在移动端未生效原因 未在head中添加以下代码: 2.如何让title属性中内容进...

网友评论

      本文标题:面试问题(杂记)

      本文链接:https://www.haomeiwen.com/subject/mmfgzftx.html