一、图片缩放功能的实现
1.需要一个scrollview或者继承于scrollview的视图(如collectionview)。
2.实现代理方法- (UIView*)viewForZoomingInScrollView:(UIScrollView*)scrollView{return 需要缩放的view}
3.使imgView始终处于视图中心
- (void)scrollViewDidZoom:(UIScrollView*)scrollView
{
self.imgView.center = CGPointMake(self.view.center.x, self.view.center.y);
}
二、零散知识
1.自动布局无法获取view的frame时,调用以下两个方法
[redView setNeedsLayout];
[redView layoutIfNeeded];
2.移除控制器
NSMutableArray *vcArray = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
intindex = (int)[vcArrayindexOfObject:self];
[vcArrayremoveObjectAtIndex:index];
[self.navigationController setViewControllers:vcArray];
3.@synthesize关键字
synthesize语意为如果你没有手动实现setter和getter方法,系统会自动给你添加以上两个方法。一般的使用场景为当你想同时重写一个属性的setter和getter方法的时候,需要定义@synthesize var = _var;
4.@dynamic关键字
dynamic告诉编译器,属性的setter和getter方法由用户自己生成,不自动生成。
比如当你定义了一个属性:@property (nonatomic,strong) UIColor *textColor;
如果你要自己生成setter和getter方法,你要定义 @dynamic textColor并且定一个实例变量_textColorTow要在存取方法中用到;然后实现:
- (UIColor*)textColor
{
if (_testColor2) {
_testColor2 = [UIColor redColor];
}
return _testColor2;
}
- (void)setTextColor:(UIColor*)textColor
{
_testColor2= textColor;
}
dynamic一般用不到,会增加代码量
5.Objective-C是一门什么样的额语言?
Objective-C作为一种动态消息型语言,它将数据类型的确定工作推迟到了运行期间来执行,并且它调用方法的实质是向对象发送消息,根据selector在对象的本类以及父类的方法列表里找,如果找不到就启动消息转发机制。
6.什么是多态
相同类型调用同一个方法呈现不同的行为特征就是多态。
当子类对象直接赋值给父类指针变量,父类 *p = [子类 new];运行类型hi父类编译类型是子类,所以p不能调用子类单独实现的方法,如果子类重写了父类方法,p调用的方法将会是子类重写的方法
7.OC和Swift的区别
(1).import的类
OC:某个只要要使用某个类就要将该类import。
swift:如果是用户自己创建类,其他类无需import可以直接使用。pod的一些三方类和系统的一些类,在使用的时候需要import
(2)Swift没有地址和指针的概念
(3)
8.调用系统声音和震动
(1)引入#import <AudioToolbox/AudioToolbox.h>
(2) AudioServicesPlaySystemSound(1007); //系统的通知声音
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);//震动
9.NSCache
NSCache是一个继承NSObjec的可变集合,是苹果提供的一套缓存机制,用键值(key-value)对来临时存储只需要短暂存储在内存中的数据,并且当内存空间很少的时候会可以自动释放一些资源。
10.setNeedsDisplay和setNeedsLayout
首先两个方法都是异步执行的。而setNeedsDisplay会调用自动调用drawRect方法,setNeedsLayout会默认调用layoutSubviews.setNeedDisplay方便绘图,setNeedsLayout方便重新布局
11.layoutSubviews什么情况下会被调用
(1)初始化frame
(2)addSubview
(3)滚动一个UIScrollView会触发layoutSubviews。
(4)直接调用setLayoutSubviews
12.成员变量和属性
成员变量不会自动生成set和get方法,需要自己手动实现。成员变量不能用点语法调用,因为没有set和get方法,只能使用->调用。
属性会自动生成set和get方法。属性用点语法调用,点语法实际上调用的是set和get方法。
13.Int和Integer的区别
NSInteger与int的区别是NSInteger会根据系统的位数(32or64)自动选择int的最大数值(int or long)
Integer和NSUInteger的区别是后者是无符号的,没有负数,前者有符号
14.Category
分类一般用于给现有系统类或者自定义类添加方法,以减少类的代码量,比如我们要计算label的宽高,可以给UILabel类添加一个分类,里面定义两个类方法,返回值为宽度和高度。
分类的特点:
(1)分类是用于给原有类添加方法的,因为分类的结构体指针中,没有属性列表,只有方法列表
(2)如果分类中有和原有类同名的方法, 会优先调用分类中的方法, 就是说会忽略原有类的方法,同名方法调用的优先级为 分类 > 本类 > 父类;
(3)如果要给分类添加属性,需要用到runtime的关联对象,使用关联对象添加属性。比如我以前写过一个简单的下拉刷新控件,要给继承于Scrollview的类添加一个方法,还要给这些类加属性,比如header footer等等。
15.分类和继承的区别
(1)继承
a. 当需要扩展的方法与原方法同名时,并且需要调用父类的同名方法,则需要继承。若此时使用分类,则分类的方法的实现会覆盖原方法的实现,不会访问到原方法。
b. 当需要扩展属性时。
(2)分类
a. 针对系统的一些类进行扩展。例如,NSString, NSArray, NSNumber等类,系统本身不提倡使用继承去扩展方法,因为这些类内部实现对继承有所限制,所以最好用分类的方式扩展。
b.类别支持开发人员针对自己构建的类,把相关的方法分组到多个单独的文件中,针对大型而复杂的类,可以提高维护性和可读性,并简化单个源文件的管理。
16.isa指针的作用
(1)当我们调用某个对象的对象方法的时候,它会首先在自身isa指针指向的objc_class的methodLists中查找该方法,如果找不到的话就会通过objc_class的super_class指针找到其父类,然后从父类的methodLists中查找该方法,如果仍然找不到,则继续通过super_class向上一级结构体中查找,直至根class
(2)当我们调用某个类方法的时候,它会首先通过自己的isa指针找到metaclass元类,并从其methodLists中查找该类方法,如果找不到则会通过metaclass的super_class指针找到父类的metaclass结构体,然后接着找。
(3)有个细节是运行的时候编译器会将代码转化为objc_msgSend(obj,@selector(test)),在class中先去cache中查找SEL查找对应method,如果cache里面没有找到,那就去methodLists中查找。
17.如何避免循环引用
(1)block引发的循环引用,在block中使用对自身对象的弱引用来替换self,__weak typeof(self) weakSelf = self
(2)delegate引发的循环引用,对代理使用弱引用
(3)使用了NSTimer没有销毁,定时器对当前控制器持有强引用,如果定时器不销毁,则控制器无法释放。解决方法位在viewWillDisappear或者viewDidDisappear或者其他原因离开该控制器的方法中销毁定时器
18.建立一次TCP协议需要三次握手
(1)第一次握手,建立连接时,客户端发送syn包到服务器,并进入syn_sent状态,等待服务器确认。
(2)第二次握手,服务器收到syn包,同时自己也发送一个syn+ack包,等待客户端确认
(3)第三次握手,客户端收到syn+ack包,向服务器发送确认包ack,此包发送完毕客户端和服务器进入ESTABLISHED状态,tcp连接成功,完成三次握手
四次挥手:
(1)第一次客户端请求关闭客户端到服务端的连接,这时客户端就要发送一个FIN=1,表示要关闭连接。
(2)第二次服务端收到后需要确认一下,返回ACK=1,并且带上自己的序列号,服务端进入了CLOSE-WAIT状态,TCP服务区通知高层的应用进程,客户端向服务端的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务端发送的数据客户端还是要接受。
(3)第三次第二次挥手完毕后之关闭了客户端向服务端的方向,另一个方向也需要关闭,所以服务端也向客户端发送FIN=1 ACK=1
(4)第四次客户端收到并且发送ACK=1,一次TCP连接结束
19.loadView viewDidLoad viewDidUnload
loadView方法负责创建Controller的view,view创建完毕后调用viewDidLoad,应用程序占用的内存过多的话,系统会对应用发出内存警告,Controller就会收到didReceiveMemoryWarning,如果当前UIViewController的view不在应用程序的视图层次结构(View Hierarchy)中,即view的superview为nil的时候,就会将view释放,并且调用viewDidUnload方法。
viewDidUnload和dealloc方法的区别在于dealloc是在控制器被释放的时候调用的,而viewDidUnload在收到警告时调用,并且只是释放了view
20.内存分区情况
(1)堆,堆是存放进程运行中被动态分配的内存段,它的大小不固定,可以动态扩张或者缩减,当进程调用alloc
函数的时候新分配的内存就被添加到堆上面,release后就被释放。
(2)栈,栈是用户存放程序临时创建的局部变量,也就是说函数括弧中的变量,当函数执行完毕,栈区的数据被清理
21.iOS系统架构
(1)UI层,为应用程序开发提供了各种常用的框架并且大部分框架与界面有关,本质上来说它负责用户在iOS设备上的触摸交互操作。最常用的就是UIKit
(2)媒体层,提供应用中视听方面的技术,如图形图像相关的CoreGraphics,CoreImage,GLKit,OpenGL ES,CoreText,ImageIO等等。声音技术相关的CoreAudio,OpenAL,AVFoundation,视频相关的CoreMedia,Media Player框架,音视频传输的AirPlay框架等等
(3)核心服务层,提供应用所需要的基础的系统服务,比如Accounts账户框架、CoreData数据存储框架等等
(4)操作系统核心,包含大多数低级别接近硬件的功能,它所包含的框架常常被其它框架所使用。常用的就是CoreBluetooth框架利用蓝牙和外设交互。
22.视图生命周期
(1)loadView,控制器被创建首先调用loadView
(2)viewDidLoad,视图加载完成后调用此函数,一般在此方法中添加一些控件,设置视图的初始属性,类似于初始化
(3)viewWillAppear,即将加载到窗口时调用此方法。一般在此方法做一些较为耗时的。这样就可以先显示基本的视图,呈现给用户(让用户感觉不是那么卡),然后再显示比较耗时的。以免显示一个白屏给用户
(4)viewDidAppear,视图已加载完毕
(5)viewWillDisappear
(6)viewDidDisappear
23.App项目的生命周期
执行main函数 -> 调用UIApplicationMain函数 -> 初始化UIApplication对象并为他设置代理对象 -> 程序退出
UIApplication代理对象:
(1)applicationDidFinishLanching,程序载入后
(2)applicationWillResignActive 将要进入非活动状态
(3)applicationDidBecomeActive程序进入活动状态
(4)applicationDidEnterBackground程序进入后台
(5)applicaitonWillEnterForeground程序将要进入前台
(6)applicationDidReceiveMemoryWarning内存警告,程序将要终止
(7)applicatioWillTerminate程序将要结束
24.Cocoa Touch提供了哪几种Core Animation过度类型
过渡动画通过 type 设置不同的动画效果, CATransition 有多种过渡效果, 但其实 Apple 官方的SDK只提供了四种:fade(淡出)、movein(覆盖原图)、push(推出)、reveal(底部显示出来),私有api提供了替他很多非常炫的动画,如cube、pageCurl
25.UIView和CALayer有什么区别
最明显的区别UIView继承与UIResponder,可以响应事件,CALayer继承与NSObject,不能响应事件.
两者的关系:每一个view内部都有一个CALayer在背后提供内容的绘制和显示,并且view的尺寸都由内部的layer提供,两者都有树状层级结构,layer内部有sublayer,view内部有subview.
26.什么时候用Delegate?什么时候用Notification?
delegate是一种一对一的设计模式,谁遵循代理协议,谁才可以调用代理方法。一般在自定义控件的中常用delegate,比如UITableView,UICollectionView、UITextField等都有代理协议,你要实现一个列表就要遵循tableview的uitableviewdelegate。notification采用的是单例设计模式,是一种多对多的设计模式,因为实现一个delegate协议的代码比较繁琐,尤其是向多个对象同时传值的时候delegate的易用性就比较差了,所以向多个对象发送消息或者传值的时候通知比较好用。
27.如何定义一个delegate
(1)创建代理
@protocol TestDelegate <NSObject>
- (void)loadNewData;
@end
(2)声明属性
@property(nonatomic,weak) id<TestDelegate>delegate
(3)遵循代理,并实现代理方法
28.如何定义一个NSNotification
发送通知 -> 接受通知 -> 页面消失的方法里移除通知
29.全局变量和局部变量在内存中是否有区别?如果有,是什么区别?
有区别,全局变量保存在内存的全局存储区中,占用静态的存储单元。局部变量保存在栈中,只有在所在函数被调用的时候才会动态的为变量分配存储单元。
全局变量声明在函数外面,可以跨文件访问也可以在声明的时候赋上初始值。
30.static关键字的作用
当static修饰局部变量时可以延长局部变量的生命周期,如果局部变量不佳static那么当该函数结束的时候局部变量的内存就会被释放,如果加static那么该局部变量将一直存在于全局静态存储区里面,知道控制器销毁该变量才会被回收。statis关键字修饰的局部变量只会初始化一次,内存地址也只有一份,
static修饰全局变量的时候可以把全局变量的作用域缩小到当前文件,保证外部类无法访问。
31.extern关键字的作用
在A类里面定义一个全局变量NSInteger age = 10,如果B类没有引入A类头文件时想得到A类的age变量,就可以使用extern关键字,extern NSInteger age;
32.const关键字的作用
const修饰的变量的读写权限变为只读,
33.Volatile关键字的作用
volatile告诉编译器他修饰的变量是随时可能发生变化的,每次使用他的时候必须从i的地址中读取。
34.iOS进程间通信的几种途径
(1)URL Scheme
这个事iOS通信最常用到的通信方式,App1通过openURL的方法跳转到App2,并且在URL上带上想要的参数,有点类似于http中get请求那样进行参数传递。使用方法为在App1的info.plist中配置LSApplicationQueriesSchemes,指定App2的scheme。典型的使用场景就是各开放平台SDK的分享功能,比如分享到微信朋友圈微博等等。
(2)UIPasteboard
剪切板功能,每个app都能使用剪切板功能,可以通过这一功能进行进程间的通信。最典型的就是淘宝。
(3)UIDocumentInteractionController
UIDocumentInteractionController主要用来实现同设备上app之间的共享文档,以及文档预览、打印、发邮件和复制功能
(4)AirDrop
通过AirDrop实现不同设备的App之间文档和数据的分享
(5)UIActivityViewController
iOS SDK中封装好的类在App之间发送数据、分享数据和操作数据
35.进程死锁的原因
所谓死锁,通常指有两个线程T1和T2都卡住了,并等待对方完成某些操作。T1不能完成是因为它在等待T2完成,但是T2也不能完成,因为它在等待T1完成。于是大家都完不成,所以就造成了死锁。具体原因就是在一个串行队列的任务里,再向这个队列同步添加任务。我是这样理解的,比如我现在有一个viewdidload方法,在viewdidload方法里使用dispatch_sync函数给主队列添加一个同步任务,现在将viewdidload看作是任务一,dispatch_sync函数是任务二,任务二只有等任务一执行完毕之后才能执行,这是同步队列的特点,但是任务二是一个同步任务,必须等待任务二执行完毕之后才能执行其他任务,因此造成互相等待的死锁。
死锁的处理:将任务放到异步队列中执行,异步队列也是先进先出的规则,但是异步队列并不会阻塞线程,任务出队列是依据其执行时间来确定的。
36.简述单例
单例对象即如果你不主动销毁这个对象,这个单例对象一直存在在内存中,并且这个单例对象在内存中都是同一个地址。登录模块使用单例。此外,一些其他的设计模式也依托于单例,比如通知NSNotificationCenter NSUserDefaults等等
+ (LXFMDBTest*)manager
{
staticdispatch_once_tonceToken;
staticLXFMDBTest*manager;
dispatch_once(&onceToken, ^{
manager = [[LXFMDBTestalloc]init];
});
returnmanager;
}
37.简述@selector的作用
SEL为方法选择器,@selector是取得一个SEL指针,方法选择器是一个char *指针,标示它所代表的是方法的名字。简单来说,@selector就是用字符串表示某个类的某个方法。
38.多继承的实现(通过消息转发)
消息转发是Runtime的黑魔法,其中一个用处就是可以实现多继承,当发送的消息找不到对应的方法的时候,会经过如下过程:
1.动态方法添加:通过resolveInstanceMethod方法动态添加方法
2.直接消息转发:不修改原本方法签名,直接检查forwardingTargetForSelector是否实现,若返回nil并且非self,则向该返回对象直接转发消息,前提是该类没有与转发对象同名的方法
3.标准消息转发:先处理方法调用再转发消息,重写methodSignatureForSelector:和forwardInvocation:方法,前者为该消息创建一个合适的方法签名,后者则将该消息转发给其他对象。
第二种实现方式如下图
第三种实现方式如下图:
39.列举几种进程同步机制并比较优劣
(1)信号量,用于多线程同步的,根锁不一样的是,信号量不一定是锁定某一资源,而是流程上的概念,比如有A、B两个线程,B线程一定要等到A线程完成某一任务时候才能进行,这个任务并不是锁定某一资源,而对于锁来说,锁住的资源无法被其余的线程访问,从而阻塞线程实现线程同步。
信号量主要有三个函数,分别为dispatch_semaphore_create(M)(创建一个值为M的信号量)、dispatch_semaphore_wait()(如果信号量的值大于0,则使其信号量的值-1,否则阻塞线程到该信号量的值大于0或者超过等待时间)、dispatch_semaphore_signal(使得该信号量+1)。
(2)自旋锁,自旋锁是专为防止多处理器而引入的一种锁,和互斥锁类似,都是为了保证线程安全的锁,但是二者的区别不一样,对于互斥锁来说,当一个线程获得这个锁之后,其他想要获取该锁的线程就会被阻塞,直到该锁被释放,但是自旋锁不一样,当一个线程获得锁之后,其他线程会一直循环在那里查看该锁是否被释放,所以,该锁比较适用于锁的持有者保存时间比较短的情况下。
(3)互斥锁,
40.自动释放池***
通常情况下我们是不需要手动添加autoreleasepool的,使用线程自动维护的autoreleasepool就可以了,但有一些情况下我们需要自动添加,比如循环中创建了大量的临时对象,这些临时对象可能长时间都不会被释放,一直在内存中保留,那么内存的峰值就会一直增加,但是其实这个临时变量我们不再需要了,这个时候可以创建自动释放池来缩短临时变量的生命周期来降低内存的峰值。
底层实现:源码中有一个结构体__AtAutoreleasePool,结构体由objc_autoreleasePoolPush() 和 objc_autoreleasePoolPop() 这两个方法构成,在这两个方法里有一个类叫AutoreleasePoolPage,实质是一个双向链表,page就是链表的一个节点。自动释放池的工作过程就是先调用push函数,当page不为空那么就添加对象到自动释放池中。释放工作交给pop函数。
41.OC优缺点
优点:(1)category,非常实用的扩展机制,可以很方便的为一个已有的类添加属性和方法。
(2)运行时的概念。
42.为什么说OC是一门动态语言
动态语言是指程序在运行时可以改变结构,可以添加新的函数、属性或者删除已有的函数、属性。这是一点,另外一点就是动态语言类型的检查实在运行时做的,OC可以用runtime动态伟类添加方法、属性,比如我有一个Person类,里面只有一个name属性,但是我可以在其他地方为这个类添加age、gender等其他属性。OC的动态性主要体现在三个方面,一是动态类型,比如id类型,到了运行时才决定接受者。二是动态绑定,让代码在运行时判断需要调用什么方法,而不是在编译的时候。三是动态载入,在程序运行时才去决定调用哪些代码和资源,而不是启动的时候就家在所有。
静态类型的语言的类型判断是在编译阶段判断的。
43.通知和协议的不同之处***
44.响应链
响应者对象,iOS中只有继承UIResponder的对象才能处理事件,我们叫他响应者对象。用户触摸屏幕,会产生一个触摸事件,操作系统会将该触摸事件加入到UIApplication管理事件队列,UIApplication会从事件队列中取出最前面的事件,并将事件分发下去处理,会将事件首先交给UIWindow,UIWindow将事件下发,即UIWIndow的UIView,UIView首先看自己能否处理事件以及触摸点是否在自己身上,如果不在自己身上那么继续寻找子视图,遍历子控件,重复进行判断能否处理事件,如果没有找到,那么自己就是事件处理者,如果自己不能处理,那么不做任何处理。这一寻找的过程,叫做事件响应链。
45.frame和bounds的区别
frame的坐标x y是相对于父控件而言的,bounds的x y相对于其自身而言的,自己的bounds的x y都是(0 0)
46.方法和选择器的区别
Selector是一个对象中选择方法来执行的名字,Selector本身并不能做任何事情,它简单的标识了一个方法,通过它可以找到方法。方法是相对于对象来说,包含方法名称和实现。
47.OC的垃圾回收机制
MRC下面是手动引用计数,ARC下面是自动引用计数。当对象的引用计数等于0的时候该对象被销毁。
48.代理为什么要用weak修饰?
比如我现在有两个类ClassA和ClassB,ClassA中定义了一个代理delegate,该delegate使用strong修饰。那么在B中初始化A并且让B遵守A的协议,因为strong修饰符是强引用,会使对象的引用计数加1,这个时候B持有A,A也持有B,会造成循环应用。如果使用weak就不会出现这种情况,因为weak是弱引用,对对象的引用计数不会造成影响。
49.weak实现原理
Runtime维护了一个weak表,key是所指对象的地址,value是weak指针的地址数组。weak的实现原理可以概括为三步:
1.初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址
2.添加引用时:objc_initWeak会调用objc_storeWeak函数,objc_storeWeak的作用是更新指针指向,创建对应的弱引用表
3.释放时:调用clearDeallocation函数,clearDealocation函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录
50.有哪些NSObject的方法
isKindOfClass和isMemberOfClass
51.UIView的动画效果有哪些
位移、大小、透明度
52.深拷贝和浅拷贝的区别
浅拷贝就是对内存地址的拷贝,让目标对象指针和源对象指向同一片内存,使对象的引用计数加1,可以理解为创建了一个指向原对象的新指针而已,并没有创建一个新的对象,也就是说没有开辟新的内存。
深拷贝是拷贝对象的具体内容,内存地址是自主分配的,拷贝后两个对象的值是一样的,但是内存地址不一样,两个对象的互不影响,所以也不涉及到增加引用计数的问题。
深拷贝是内容拷贝,浅拷贝是指针拷贝,本质区别在于是否开辟新的内存,是否影响内存地址的引用计数。
如果一个对象使用浅拷贝,拷贝出来的对象一定是不可变的,比如一个NSString对象使用copy,那么拷贝出来的对象就算定义为NSMutableString对象该对象也是不可变的,如果执行可变操作程序就会崩溃,比如该NSMutableString执行appendString操作就会崩溃。但是如果使用深拷贝,那么拷贝出来的对象就是可变的。
53.字符串为什么要用copy修饰
首先要理解一个概念,copy是浅拷贝,浅拷贝就是指针拷贝,让目标对象指针和源对象的指针指向同一片内存,但是copy也并不是一定不会开辟新的内存,比如说当我copy一个不可变得对象的时候那么一定是指针拷贝,但是当copy的对象为可变对象的时候,copy就会开辟一片新的内存,因为这样做会比较安全,如果源对象的值被改变了,那么这个时候copy的对象就不会受影响,因为开辟了新的内存和原来的内存就没有关系了。我写过一个demo,定义一个由copy修饰的字符串属性比如它叫text,然后在函数中定义一个可变字符串比如叫mutableText,这个时候我执行self.text = mutableText的操作时候,输出self.text和mutableText的对象地址,会发现两个地址不一样,所以这个时候copy应该是内容拷贝。然后我将text的修饰符换为strong我发现输出的两个地址是一样的,这个时候我如果修改mutableText值得时候,输出两个字符串的值发现self.text的值也被修改,因为他们的内存地址是一样的,这样就不具备安全性。其实总结一句话就是copy出来的不管是否开辟了新的内存,他就是不可变的,也不存在值被修改的情况,也不存在安全性的问题。
54.基于Socket聊天室实现流程
a.首先进行服务器请求,获得直播流地址和房间id,请求成功后使用socket与与服务器连接,并且发送一些参数参数比如用户名、用户id等等发送给服务器告诉服务器连接成功,连接地址是写死的,url+端口号。
b.在Socket的delegate方法didReceiveMessage里面去执行与服务器交互的一些操作,比如给服务器发送心跳、用户在聊天室发送弹幕的权限等等。
c.退出房间断开socket连接
55.题库图文混排实现过程
a.比如说一道题里面服务器给我一段html的富文本,html里面会有一些自定义的标签,我通过正则表达式找到标签,比如latex小图、image大图、underline下划线等等,像这些公式都是图片,每一道图的图片都是服务器给我的一个zip包,我将zip包解压后存到本地,然后根据图片名去本地文件夹里面找图片,找到图片以后根据得到的range确定图片的显示位置。
b.使用的是一个叫TYAttributelabel的第三方库实现的,它是基于coretext实现的。
c.题库实现的一个关键点是我们在加载的时候做了优化,比如有的资料会有很多道题,因为zip包是一次性返给我的,如果我将这个资料的所有zip包都解压完存到本地再展示页面那么这个时候用户等待的时间就太长了,我们的加载模式是一次只加载这道题本身以及前后各两道题,这样就不会造成资源浪费。实现的过程就是这是一个collectionview,我回去监听它本身以及前后各两道题是否加载过了,如果加载过了那么久不去加载,如果没有加载就去解压zip包。
56.视频解密过程
将服务器给我的key先进行base64解码得到一个NSData数据,然后用根据这个NSData数据和key使用AES解密的得到解密后的字符串,将这个字符串给播放器
57.Cocoapods工作流程及原理
pod install
a.当我执行命令pod install --verbose的时候可以看到pod install详细的输出信息,它首先有一个操作叫Finding Podfile changes,它去podfile这个文件里面查看有哪些变化,如果有更新信息,接下来会在在CocoaPods/Pods/Release文件夹下面执行git clone操作,将这个第三方库从github上clone下来,然后copy到项目的pods文件夹下面。
b.将文件clone下来后会执行Generating Pods project的操作,它会自动在你项目目录下面创建一个project,然后形成一个workspace。
pod update
a.在cocoapods的本地git仓库的master分支执行fetch+merge操作,master分支下的specs文件夹里存的都是spec配置文件。同时update检查更新cocoapods。
b.fetch+merge后会将本地沙盒里的podfile信息和本地仓库里的spec文件做比较,去判断是否需要更新第三方库。
podfile.lock文件的作用
这个文件最大的用处在于多人开发,cocoapods的podflie文件如果没有指定第三方版本的话那么会获取该第三方最新的版本,当团队中某个人执行完pod install命令以后,生成的podfile.lock就记录下最新第三方库的版本。这时候如果其他成员pull下来包含这份podfile文件的代码,获取的也是最新版本的第三方库代码,但如果其他成员执行pod install的时候给这个第三方指定版本了的话,那么一个团队里就会有不同版本的第三方,但是.lock文件会阻止他做这件事。
58.View和layer的区别
(1)首先,最明显的区别是view可以响应事件,layer不可以,UIKit使用UIResponder作为响应对象,来响应系统传递过来的事件并进行处理。UIApplication、UIViewcontroller、UIView等UIKit类都直接或者间接的继承于UIResponder类,UIResponder定义了各种处理事件和事件传递的接口,而CALayer直接继承于NSObject,并没有响应的事件处理接口。
(2)每个UIView内部都有一个CALayer在背后提供内容的绘制和显示,并且UIView的尺寸样式都由内部的layer提供,两者都有树状层级结构,layer内部都sublayer,view内部有subview,layer比view多了一个AnchorPoint
(3)layer内部维护着三个layer tree,分别是presentlayer tree(动画树)、modeLayer tree(模型树)、Render tree(渲染树),我们修改动画的属性其实是在修改动画树的属性,最终显示在界面上其实依赖的是modelLayer
59.TCP和UDP的区别
UDP在传送数据前不需要先建立连接,远地的主机在收到UDP报文后也不需要给出任何确认,因为这样省去很多开销,使得它的速度比较快,比如一些对实时性要求比较高的服务常常使用UDP。
TCP提供面向连接的服务,在传送数据前必须建立连接,数据传送完成后要释放连接,因为TCP是一种可靠的运输服务,但是正因为这样,不可避免的增加了很多开销,对应的应用层协议有SMTP、HTTP、FTP
60.TCP是如何实现可靠传输的?
(1)TCP的每一端必须设有两个窗口,一个发送窗口和一个接受窗口,TCP的可靠传输机制用字节的序号进行控制,TCP所有的确认都是基于序号而不是基于报文段
(2)发送过的数据未收到确认之前必须保留,以便超时重传时使用。
(3)发送缓存用来暂时存放:发送应用程序传送给发送方TCP准备发送的数据,TCP已发送但尚未收到确认的数据
(4)接收缓存用来暂时存放:按序到达的、但是尚未被接收应用程序读取的数据
61.load方法
load可以说是调用时间最靠前的方法,在主函数运行之前就会调用。调用顺序为父类优先于子类调用,类优先于分类调用。它的调用不是惰性的,在程序调用期间只调用一次,最重要的是,如果在类和分类中都实现了load方法,它们都会调用,不会像其他的在分类中实现的方法被覆盖,这就使load成为方法调剂的绝佳时机。但是由于load方法的运行时间过早,如果要调用其他类的方法这里并不是一个理想的环境,因为在当前类并不能确定其他类已经加载了。这个时间点所有framework都已经加载到了运行时中,所以调用framework中方法是安全的。
62.Runtime替换方法
1.一般会在load方法里替换系统方法,因为load方法在main函数之前执行,这样可以确保类初始化之后执行的是已经被替换过的方法。在load方法里实现替换操作一般只能替换系统方法,不建议替换自定义的方法,因为在load方法执行的时候自定义的方法不一定被添加到runtime中去,我写过一个demo,在一个UIViewController类中自定义一个方法比如叫methodExchange方法,同时我定义一个UIViewController的分类,在分类的load方法里输出这个类的methodList,发现并没有这个自定义方法。
63.静态库和动态库
什么是库:库是程序代码的集合,是共享程序代码的一种方式,根据源代码的公开情况,库可以分为两种类型,开源库,公开源代码,能看到具体实现,比如SDWebImage,闭源库不公开源代码,是经过编译后的二进制文件,看不到具体实现,主要分为静态库和动态库。
(1)存在形式
静态库:以.a和.framework为文件后缀名(.a是一个纯二进制文件,frameword=.a + .h + sourcefile,所以创建静态库最好还是用.framework的形式)
动态库:以.tbd和.framework为文件后缀名,系统直接提供给我们的都是动态库
(2)区别
静态库和动态库是相对于编译期和运行期的,静态库在编译时会被链接到目标代码中,动态库在编译期间不会被链接到目标代码中,只是程序运行时才会被载入,因为在程序运行期间还需要动态库的存在。
64.NSCache理解
iOS中需要频繁读取的数据,都可以用NSCache把数据缓存到内存中提高读取性能,SDWebImage缓存图片就是用的这个类。是系统提供的类似于集合的缓存类,它和集合的不同之处在于NSCache有自动删除的功能,以减少系统占用的内存;NSCache是线程安全的,不需要加线程锁;
属性:totalCostLimit,设置缓存占用的缓存大小,并不是一个严格的限制,当缓存大小超过了设定的值得时候系统会清除一部分内存,直至缓存内存小于所设的值
countLimit,缓存数量,如果超过数量会清除一部分缓存,直至缓存数量小于等于所设数量。
代理方法willEvictObject,缓存被清理的时候调用该方法
65.内存区域分布
代码区、常量区、全局静态区、堆、栈
代码区:用来存放程序执行代码的一块内存区域,这部分区域的大小在程序运行前就已经确定,这块区域通常属于只读。
常量区:存储常量的区域,程序结束后释放
全局静态区:包含全局变量和静态变量,程序结束后系统释放。
堆:就是那些由new alloc创建的对象分配的内存块,ARC下会在当前线程Runloop退出或者休眠的时候销毁这些对象,MRC需要程序员自己释放。
栈:创建临时变量时由编译器自动分配,在不需要的时候自动清除的变量的存储区,函数执行完毕后销毁。
66.加载一个页面需要多长时间,如何优化一个页面
从loadview到viewdidappear输出当前时间,发现从loadview到viewdidappear需要将近0.02毫秒。
优化建议:将关键且耗时比较少的操作放到viewdidload里面,将必要的UI渲染放到viewwillappear,复杂运算放到viewdidappear,一些触发加载的做懒加载或者延时加载
67.MJExtention源码解析
(1)最核心的类是一个NSObject的分类叫做MJKeyvalue,字典转模型的方法都在该类中,其中最核心的方法就是将字典中的值赋值给模型中的属性,这个方法叫做mj_setKeyValues,参数为id对象。
(2)一进来会将这个id对象转换为字典,然后通过一个封装的方法获取Model类及其继承链中的父类的所有属性,取得属性列表的核心方法是runtime提供的函数class_copyPropertyList,然后遍历获取到的属性列表,通过property_getName函数取得每一个属性的名称,通过property_getAttributes获得每一个属性所对应的类,之后会调用自定义的MJFoundation类的方法去判断属性所对应的类是否是Foundation框架下的类,比如NSString、NSArray,如果是Foundation下的类那就可以直接赋值,如果是自定义的类,也就是字典嵌套字典模型嵌套模型的那种情况,那么初始化这个自定义类然后再次调用mj_setKeyValues方法给这个自定义类的属性赋值。循环结束返回这个Model类的实例对象,一次解析结束。
(3)其他核心类,MJProperty(属性对应的类,这个类里面定义了一些属性比如name:属性名称,type:枚举,是foundation框架下的类还是自定义类,srcClass:属性来源于哪个类,有可能是父类),NSObject的分类MJClass,主要用来获得Model类继承链中的所有父类。
68.离屏渲染
为图层设置遮罩、圆角、阴影都会造成离屏渲染,离屏渲染会影响性能。
离屏渲染为什么消耗性能:
(1)GPU屏幕渲染分为On-Screen Rendering (当前屏幕渲染) 和Off-Screen Rendering (离屏渲染),当前屏幕渲染是在当前显示的屏幕缓冲区进行,而离屏渲染需要在当前屏幕缓冲区外开辟一块新的缓冲区进行渲染
(2)需要多次切换上下文,使用离屏渲染要从当前屏幕切换到离屏,离屏渲染结束后再切回到当前屏幕。
69.内存泄漏和野指针
内存泄漏:程序分配的内存未释放或者无法释放,造成系统内存的浪费,导致程序运行缓慢或者崩溃。造成内存泄漏的原因主要是对象没有释放、循环引用。block循环引用、delegate循环引用、大次数循环内存暴涨等。
野指针:如果内存已经被释放,而指针还在引用原始内存,这样的指针就叫做野指针。野指针不是nil指针,是指向垃圾内存的指针。