面试被虐总结一

年后因为各种原因只能重新找工作,期间进攻了UC,CVTE,酷狗还有其他一些比较大一点的公司,一路过来,被虐的不要不要的。

消息机制

关于消息机制简单实现:

isa->class->selector是否忽略(mac垃圾忽略retain,release)->是否nil(nil执行方法忽略,运行时忽略掉:我们给把nil对象设给了一个成员变量,setter就会retain nil对象(当然了这个时候nil对象啥事情也不会做)然后release旧的对象)->查找类的IMP->缓存列表中->方法列表中->父类中(重复上面,直到根类)->任何一处找到加入缓存,方便下次->通过方法中的函数指针跳转到对应的函数执行->都找不到就开始转发

Runtime消息机制完整实现

首先通过obj的isa指针找到obj对应的class。

首先检测这个 selector 是不是要忽略。比如 Mac OS X 开发,有了垃圾回收就不理会 retain,release 这些函数。
检测这个 selector 的 target 是不是 nil,Objc 允许我们对一个 nil 对象执行任何方法不会 Crash,因为运行时会被忽略掉。
如果上面两步都通过了,那么就开始查找这个类的实现 IMP,
在Class中先去cache中 通过SEL查找对应函数method,找到就执行对应的实现。
若cache中未找到,再去methodList中查找,找到就执行对应的实现。
若methodlist中未找到,则取superClass中查找(重复执行以上两个步骤),直到找到最根的类为止。
若任何一部能找到,则将method加 入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。
如果以上都不能找到,则会开始进行消息转发

消息转发:

(动态:resolveInstanceMethod,给机会添加实现)检测是否动态添加方法->(快速:forwardingTargetForSelector,别的对象执行函数)是否实现了forward。方法->(标准:forwardInvocation,目标函数以其他形式执行)Runtime发送消息获取签名->非空就inv转发,否则崩溃退出(doesNotRecognizeSelector)

消息转发完整实现

1.动态方法解析:向当前类发送 resolveInstanceMethod: 信号,检查是否动态向该类添加了方法。(迷茫请搜索:@dynamic)
2.快速消息转发:检查该类是否实现了 forwardingTargetForSelector: 方法,若实现了则调用这个方法。若该方法返回值对象非nil或非self,则向该返回对象重新发送消息。
3.标准消息转发:runtime发送methodSignatureForSelector:消息获取Selector对应的方法签名。返回值非空则通过forwardInvocation:转发消息,返回值为空则向当前对象发送doesNotRecognizeSelector:消息,程序崩溃退出

动态特性:

动态类型:程序直到执行时才能确定所属的类。

动态绑定:程序直到执行时才能确定实际要调用的方法。

动态加载:根据需求加载所需要的资源

总结就是: 在一个函数找不到时,OC提供了三种方式去补救:

1、调用resolveInstanceMethod给个机会让类添加这个实现这个函数
2、调用forwardingTargetForSelector让别的对象去执行这个函数
3、调用forwardInvocation(函数执行器)灵活的将目标函数以其他形式执行。 如果都不中,调用doesNotRecognizeSelector抛出异常。

事件处理

###应用如何找到最合适的控件来处理事件?

1.首先判断主窗口(keyWindow)自己是否能接受触摸事件
2.判断触摸点是否在自己身上
3.子控件数组中从后往前遍历子控件,重复前面的两个步骤(所谓从后往前遍历子控件,就是首先查找子控件数组中最后一个元素,然后执行1、2步骤)
4.view,比如叫做fitView,那么会把这个事件交给这个fitView,再遍历这个fitView的子控件,直至没有更合适的view为止。
5.如果没有符合条件的子控件,那么就认为自己最合适处理这个事件,也就是自己是最合适的view

事件的传递和响应的区别:

+ 事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件。

响应者链的事件传递过程:

1>如果当前view是控制器的view,那么控制器就是上一个响应者,事件就传递给控制器;如果当前view不是控制器的view,那么父视图就是当前view的上一个响应者,事件就传递给它的父视图
2>在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
3>如果window对象也不处理,则其将事件或消息传递给UIApplication对象
4>如果UIApplication也不能处理该事件或消息,则将其丢弃

事件处理的整个流程总结:

  1.触摸屏幕产生触摸事件后,触摸事件会被添加到由UIApplication管理的事件队列中(即,首先接收到事件的是UIApplication)。
  2.UIApplication会从事件队列中取出最前面的事件,把事件传递给应用程序的主窗口(keyWindow)。
  3.主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。(至此,第一步已完成)
  4.最合适的view会调用自己的touches方法处理事件
  5.touches默认做法是把事件顺着响应者链条向上抛。

view->控制器/父视图->window->UIApplication->丢弃
时间传递:事件添加到UIApplication队列->取出传递给窗口->窗口中查找最合适的View(主窗是否接受触摸->触摸点是否在自己身上->重复上面步骤在子控件中->没有就自己处理)->调用自己的touches处理事件

NSObiect->UIResponser->UIView(VC)/UIApp->UIControl->UIButton。。

Block:

Block介绍:栈地址和对地址值的拷贝,其实里面存储了指向函数体中包含定义block时的代码块的函数指针,以及block外部上下文变量等信息的结构体

+ Block是“带有自动变量值的匿名函数”
+ 对于一般的block来说,它的数据就是传入的参数和在定义这个block时截获的变量。而它的算法,就是我们往里面写的那些方法、函数调用等。
+ 被编译为C语言里的普通的struct结构体来实现的
+ 一共是四个结构体,显然一个block对象被编译为了一个__main_block_impl_0类型的结构体。这个结构体由两个成员结构体和一个构造函数组成。两个结构体分别是__block_impl和__main_block_desc_0类型的
+ 当block需要截获自动变量的时候,首先会在__main_block_impl_0结构体中增加一个成员变量并且在结构体的构造函数中对变量赋值
+在block被执行的时候,把__main_block_impl_0结构体,也就是block对象作为参数传入__main_block_func_0结构体中,取出其中的val的值,进行接下来的操作。

1. 为什么block中不能修改普通变量的值?

int val = __cself->val;

当然这并没有什么影响,甚至还有好处,因为int val变量定义在栈上,在block调用时其实已经被销毁,但是我们还可以正常访问这个变量。但是试想一下,如果我希望在block中修改变量的值,那么受到影响的是int val而非__cself->val,事实上即使是__cself->val,也只是截获的自动变量的副本,要想修改在block定义之外的自动变量,是不可能的事情

由于无法直接获得原变量,技术上无法实现修改,所以编译器直接禁止了。

2.__block的作用就是让变量的值在block中可以修改么?

只是把val封装在了一个结构体中而已
__Block_byref_val_0 *val;

由于__main_block_impl_0结构体中现在保存了一个指针变量,所以任何对这个指针的操作,是可以影响到原来的变量的。

进一步,我们考虑截获的自动变量是Objective-C的对象的情况。在开启ARC的情况下,将会强引用这个对象一次。这也保证了原对象不被销毁,但与此同时,也会导致循环引用问题。

需要注意的是,在未开启ARC的情况下,如果变量附有__block修饰符,将不会被retain,因此反而可以避免循环引用的问题。

都可以用来让变量在block中可以修改,但是在非ARC模式下,block修饰符会避免循环引用。注意:block的循环引用并非block修饰符引起,而是由其本身的特性引起的。

__block底层实现

__block:传值和传址,Block_byref_a_0对象包装局部变量,block拷贝到堆时__Block_byref_a_0也会被底层拷贝到堆,即使局部变量所在堆被销毁,block依然能对堆中的局部变量进行操作(__forwarding)。

__block:变量变成__block_breaf_val_0结构体,包含实例本身引用__forwarding,通过他访问实例变量val,保证栈复制到堆能正常访问__block,堆block持有堆上__block变量。
当栈block拷贝到堆会将__forwarding的值替换为对上__block变量地址。
当一个__block变量从栈上被复制到堆上时,栈上的那个__Block_byref_i_0结构体中的__forwarding指针也会指向堆上的结构。
__Block_byref_i_0结构体,main函数释放的时候,只是释放了栈上的东西。而所有的对局部变量的修改都已经转移到堆上了。
两个成员:__isa(对象),__forwarding(指向自己活另一个bref结构)

KVO,KVC

KVC:字符串非访问器访问对象实例变量。isa-swizzling就是类型混合指针机制。方法名->环境参数->结合isa找到接口->再找到实现
KCO:对象属性被修改会通知当前对象。运行期动态创建派生类,重写setter方法(isa指向他)

GCD

GCD:基于核心XNU内核实现,放在libdispatch库中,queue:管理block操作。source:处理事件(通讯)

CGD纯C,NSO基于GCD封装。GCD只支持FIFO,NSO可调整顺序,设置并发数。GCD设置依赖代码复杂,NSO简单设置依赖。NSO支持KVO监听状态。GCD速度要快。

JSPatch:

Runtime,使用JS利用OC的动态特性,执行我们想要执行的代码。

Rac:

native-js bridge,以 JavaScript 的形式告诉 Objective-C 该执行什么代码。

Runtime

Runtime内存布局

对象isa指向真实类型,Class的isa指向metaclass,metaclass的isa指向最上层metaclass(NSObject),最上层指向自己。实例方法->对象的methodList,类方法->metaclass的methodList

isa:维护分发表的对象的类
SEL:是方法选择器(selector)的类型,根据方法名字生成唯一ID,字符串。
Method:是方法(method)的类型
IMP:函数指针,指向方法的实现(首地址),可跳过消息机制。

Method

Method=SEL+IMP+method_type。SEL与IMP直线的映射(Dispatch Table:将方法的名字(SEL)跟方法的实现(IMP,指向 C 函数的指针)一一对应。Swizzle 一个方法其实就是在程序运行时在 Dispatch Table 里做点改动,让这个方法的名字(SEL)对应到另个 IMP)。

成员变量与属性的区别

成员变量地址可根据实例的内存地址偏移寻址。而属性的读写都需要函数调用,相对更慢。
对于复杂的C++类型,往往设为成员变量更合适,也许这种类型不支持copy,或者完全复制很麻烦。
多线程环境下,为保证数据一致性,在需要同步执行的代码段更应该使用成员变量。如果对需要同步更新的数据用getter/setter 方法,数据更新效率低,会带来更多的获取锁请求失败。
成员变量可以做直观的内存管理。属性可以一层层继承,还可以复写。容易出错。
默认用属性,会生成不必要的getter/setter 方法,程序体积会变大。

权限

1.如果只是单纯的private变量,最好声明在implementation里.
2.如果是类的public属性,就用property写在.h文件里
3.如果自己内部需要setter和getter来实现一些东西,就在.m文件的类目里用property来声明

使用

关联对象
方法混淆
NSCoding(归档和解档, 利用runtime遍历模型对象的所有属性)
字典 –> 模型 (利用runtime遍历模型对象的所有属性, 根据属性名从字典中取出对应的值, 设置到模型的属性上)
KVO(利用runtime动态产生一个类)
用于封装框架(想怎么改就怎么改) 这就是我们runtime机制的只要运用方向

RunLoop(至少一个事件源)

简介

事件接收和分发机制的一个实现,一种异步执行代码的机制,不能并行执行任务。作用是: 保证程序执行的线程不会被系统终止。

两种源事件:input sources和timer sources。

input sources 传递异步事件,通常是来自其他线程和不同的程序中的消息;
timer sources(定时器) 传递同步事件(重复执行或者在特定时间上触发)。

原理

1>当有时间发生时,Runloop会根据具体的事件类型通知应用程序作出相应;
2>当没有事件发生时,Runloop会进入休眠状态,从而达到省电的目的;
3>当事件再次发生时,Runloop会被重新唤醒,处理事件.

提示:一般在开发中很少会主动创建Runloop,而通常会把事件添加到Runloop中.

iOS 开发中能遇到两个线程对象: pthread_t 和 NSThread。过去苹果有份文档标明了 NSThread 只是 pthread_t 的封装,但那份文档已经失效了,现在它们也有可能都是直接包装自最底层的 mach thread

RunLoop 的销毁是发生在线程结束时

一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

实现

unLoop 的核心是基于 mach port 的,其进入休眠时调用的函数是 mach_msg(),RunLoop 调用这个函数去接收消息,如果没有别人发送 port 消息过来,内核会将线程置于等待状态。例如你在模拟器里跑起一个 iOS 的 App,然后在 App 静止时点击暂停,你会看到主线程调用栈是停留在 mach_msg_trap() 这个地方。

应用:

AutoreleasePool,事件响应,手势识别,界面更新,定时器,PerformSelecter,关于GCD,关于网络请求,AFNetworking,AsyncDisplayKit

多线程

多线程介绍与底层实现

线程:1个进程要想执行任务,必须得有线程.线程是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行
底层:Mach是第一个以多线程方式处理任务的系统,因此多线程的底层实现机制是基于Mach的线程。

synchronized:互斥锁,@synchronized(obj)指令使用的obj为该锁的唯一标识,只有当标识相同时,才为满足互斥
dispatch_semaphore是GCD用来同步的一种方式
NSLock:Cocoa提供最基本的锁对象
NSRecursiveLock:递归锁,这个锁可以被同一线程多次请求,而不会引起死锁
NSCondition:条件锁(NSConditionLock)。手动控制线程wait和signal
pthread_mutex/pthread_mutex(recursive)
OSSpinLock:自旋锁,性能最高(do while 忙等)

OSSpinLock和dispatch_semaphore的效率远远高于其他。
@synchronized和NSConditionLock效率较差。
鉴于OSSpinLock的不安全,所以我们在开发中如果考虑性能的话,建议使用dispatch_semaphore。
如果不考虑性能,只是图个方便的话,那就使用@synchronized。

测试:

黑盒:在软件的接口处进行,不考虑程序内部的逻辑结构和内部特性,只依据程序的需求规格
测试:对软件的过程性细节做细致的检查利用程序内部的逻辑结构及有关信息,对程序所有逻辑路径进行测试
测试:介于白黑盒之间,灰盒测试关注输出对于输入的正确性,同时也关注内部表现。

内存相关字段

weak与strong:强&弱引用,是否负责销毁(循环引用问题),strong让编译器帮我插入retain,weak相当于assign,对象不再有strong指向会释放,weak就会清除环迅,置nil(weak)。分别对应MRC的retain,assign,避免循环引用使用weak

assign与weak:assign指针赋值,不操作引用计数,没有置nil,有野指针。delloc操作使用assign,weak已经找不到了(多了一步nil)。

strong与retain:属性时一样,block时strong=copy,retain==assign。保持与强的区别。不可变对象copy=retain

copy与retain:copy(深:内容拷贝),建立相同对象(拷贝),retain(浅:指针拷贝)

__weak,__strong:前者打破环(打破环),后者局部变量,栈中(block结束后回收)===不会循环引用
weakify(),strongify():局部strong引用计数+1,pop时不会dealloc,strong持有对象,block结束。局部回收。

autorrelease

autorrelease:建立pool,生成对象,调用autorrelease(标记,提醒后面release),结束pool需要release,每个对象release一次(不能大于1)
autorrelease:延迟调用,对象放到当前autorrelease pool中,pool释放时,所持有所有对象release
autorrelease:避免频繁申请/释放内存,每个函数自己负责(谁拥有谁释放),不需关系内部管理,使用线程自动维护,主循环结束前释放。
autorrelease:栈中(进出),对象释放,从池中删除自己,池中CCMutableArray(存储结构)。不是根据作用域来决定释放时机是依据runloop,本次runloop迭代结束时清理掉被本次迭代期间被放到autorelease pool中的对象的,发送- autorelease消息,就是将这个对象加入到当前AutoreleasePoolPage的栈顶next指针指向的位置

nonatomic

nonatomic:非原子性访问,不加同步,多线程并发访问会提高性能.

网络请求

状态码:三个十进制,第一个定义类型,后面才是详细(100信息,200成功,300重定向,400客户端错误如找不到,500服务器错误)

三次握手:客户端发送信号包到服务器,进入send状态,等待确认。服务器收到信号包,确认并发送信号(+确认信号)包给客户端,进入recv状态。客户端收到服务器信号包,发送信号(确认包)给服务器。完成,开始传输数据。

冲突:避免同一文件修改(使用桥接),选择性提交(实时),避免查看Xib,记住备份。project.文件,Nib删除多余,或者重新拉取。

协议相关

IP:网络层
TCP(传输控制):传输层,链接,可靠安全,慢,数据大。传输的是字节流
UDP(用户数据包,广播式):传输层,不连接(单向),不可靠,快,数据小
HTTP(基于TCP,超文本传输):应用层,数据如何包装。http协议和基于http协议的Soap协议。传输的是数据包
TCP/IP(传输控制协议/网际协议,一系列):传输层,数据如何传输
Socket(套接字,网络协议的传输层):TCP/IP网络的API(包装),不是协议,是接口,通过他才能使用TCP/IP

Http&Socket

Http():短连接,相应后断开。应用级接口方便,要求不高。但是传输慢,数据大,实时服务器压力大,安全差。

Socket(快,安全,实时):长连接,不会主动断开(心跳包)。传输字节,可定义,量小,时间段,性能高,适合实时交互,可加密,安全强。但是需要解析传输数据,开发要求高,增加开发量。

XMPP(即时通讯的传输协议):

基于XML的协议,使用TCP传输XML,使用Socket开发,基于TCP/IP协议,核心类XMPPStream,对象GCDAsynSocket封装C中输入流,输出流,基于模块开发。有网关,服务器,客户端。

ReactiveCocoa:

函数响应式编程(FRP)框架。以后使用RAC解决问题,就不需要考虑调用顺序,直接考虑结果,把每一次操作都写成一系列嵌套的方法中,使代码高聚合,方便管理。

JSON&XML

JSON(SAX,时间模型:触摸和回调):轻量级交换,逐行解析(遍历字符,根据规则{}[];)->编,解,体,交,传。
XML(DOM,树型结构):可拓展标记,整个文档(父子遍历)

加密:

网络(url编码,加密/加盐,post/网络传输,二次验证),本地(H5,js,plist/sqlite/defaults数据,keychain),代码(方法体/结构/逻辑混淆)
可逆=对称(加密和解密使用同一个算法):DES,AES。非对称:RSA,DSA。不可逆(消息摘要):MD5,SHA。电子签名:确认消费发送方身份。

程序启动:

main->UIApplication->代理->主循环,监听事件->调用didFinishLaunching创建Window->设置根控制->info.plist中storyboard有就设置根控制器->初始化自对应的子View显示->plist文件storyBoard没有就didFinishLaunching设置根控制器didFinishLaunching->初始化自对应的子View显示

页面执行顺序

(View)==加载:alloc->initXX->loadView->viewDidLoad->viewWillApp->viewDidiApp->viewWillLayoutSubviews->viewDidLayoutSubviews==卸载:viewWillDis->viewDidDis->viewDidUnload->didReceiveMemoryWarning->dealloc

TableView执行顺序:

numOfSec->heightForHeader->heaghtForFooter->numOfRow->heightForHeader->heaghtForFooter->numOfRow->heightForRow->cellForRow->WillDisPlayCell

tagged Pointer

64(13年9-5s-A7)位tagged Pointer(标识+数据):对象中内存对齐,地址是指针的整数倍(16的倍数),64位的整数,为了对齐,一些为永远为0,tagged Pointer利用这个特性,使得非零位有了特殊含义,64位中,如果对象指针最低有效位为1(奇数),则为tagged Pointer:不是通过isa找类,通过接下来三位的类表索引找对应的类。剩下60位供使用(NSNumber,NSString)

load&initilize

load:首次加载(一次),main之前
initilize:首次初始化(一次),init之前,更适合写代码(懒加载)

分类:

将category和主(元)类注册到哈希表,如果主(元)类已经实现,重建方法列表。实例方法和属性->主类,类方法->元类。分类中协议->主类/元类

求交集:

排序、索引(空间换时间),压缩(减小范围)

coreData与多线程(每个线程一个context):

manager context私有,persistent store coordinator私有或者共享。

UIView&CaLayer

UIView:UIKit(iOS),集成UIReaponder,多了一个事件处理功能,对CALayer高层封装,需要CALayer支持,
CALayer:QuartzCore(OS+iOS),集成NSObject,动画需要加到这里。UIView依靠他才能显示

音视频编解码

H264(mov):编码层视频压缩格式(协议层rtmp与http),视频编码层(VCL)与网络提取层(NAL)VideoToolbox(AVKit和AVFoudation,Core Me/Vi)ios8-C
ACC(wav):基于MPEG-2的音频编码技术(取代mp3),ADIF(音频数据交换格式)和ADTS(音频数据传输流)
M3U8:描述多媒体文件地址的纯文本文件
合并H264+ACC=MP4:FFmpeg推流(LFLiveKit),使用拉流ijkPlayer,视频和音频数据使用FFmpeg封装为MPEG-TS包和MP4文件。
FLV封装格式是由一个FLV Header文件头和一个一个的Tag组成的。Tag中包含了音频数据以及视频数据

ios10+xcode8:

打印,证书管理,各种权限,字体变大,Nib警告,openUrl废弃,插件,导航栏适配,Nib兼容,推送。

图片缓存

图片:下载->处理->写磁盘->读到缓冲区->赋值到用户空间->解压为位图->字节对齐->渲染解压
异步下载,子线程解压,使用缓存(内存/磁盘),存储,减少内存级拷贝与字节对齐,预下载

NSCache:

自动删除,减少内存,线程安全,不会被赋值(NSM)

数字签名:

指定信息使用哈希算法,得到固定长度的信息摘要,使用私钥对该摘要加密,就得到了数字签名(代码签名)

优化:

cell的重用,减少cell中控件的数量,少使用addView ,按需加载cell,缓存行高(属性及内容),使用局部更新(避免cell的重新布局),不要使用ClearColor,加载网络数据,下载图片,使用异步加载,并缓存,不要实现无用的代理方法,避免渐变,图像缩放以及离屏绘制,使用shadowPath来设置阴影,适当的数据结构来保存,恒定高度非delegate,不要使用富文本(缓存),,,,

刷新

  1. layoutSubviews
  2. layoutIfNeeded
  3. setNeedsLayout
  4. setNeedsDisplay
  5. drawRect
  6. sizeThatFits
  7. sizeToFit

大概常用的上面几个 , 具体的应该还有别的。

layoutSubviews

这个方法,默认没有做任何事情,需要子类进行重写 。 系统在很多时候会去调用这个方法:

1.初始化不会触发layoutSubviews,但是如果设置了不为CGRectZero的frame的时候就会触发。
2.addSubview会触发layoutSubviews
3.设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化
4.滚动一个UIScrollView会触发layoutSubviews
5.旋转Screen会触发父UIView上的layoutSubviews事件
6.改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件

在苹果的官方文档中强调: You should override this method only if the autoresizing behaviors of the subviews do not offer the behavior you want.layoutSubviews, 当我们在某个类的内部调整子视图位置时,需要调用。反过来的意思就是说:如果你想要在外部设置subviews的位置,就不要重写。

setNeedsLayout

标记为需要重新布局,不立即刷新,但layoutSubviews一定会被调用
配合layoutIfNeeded立即更新

layoutIfNeeded

如果,有需要刷新的标记,立即调用layoutSubviews进行布局

所以上面不管写多少约束的改变,只需要在动画里动用 一次self.view.layoutIfNeeded() ,所有的都会已动画的方式 。如果一些变化不想动画 。在动画前执行self.view.layoutIfNeeded()

drawRect

这个方法是用来重绘的。

drawRect在以下情况下会被调用:

1、如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。drawRect调用是在Controller->loadView, Controller->viewDidLoad 两方法之后掉用的.所以不用担心在控制器中,这些View的drawRect就开始画了.这样可以在控制器中设置一些值给View(如果这些View draw的时候需要用到某些变量值).
2、该方法在调用sizeToFit后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。3、通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。4、直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0。以上1,2推荐;而3,4不提倡

drawRect方法使用注意点:

1、若使用UIView绘图,只能在drawRect:方法中获取相应的contextRef并绘图。如果在其他方法中获取将获取到一个invalidate的ref并且不能用于画图。drawRect:方法不能手动显示调用,必须通过调用setNeedsDisplay 或者 setNeedsDisplayInRect,让系统自动调该方法。
2、若使用calayer绘图,只能在drawInContext: 中(类似于drawRect)绘制,或者在delegate中的相应方法绘制。同样也是调用setNeedDisplay等间接调用以上方法3、若要实时画图,不能使用gestureRecognizer,只能使用touchbegan等方法来掉用setNeedsDisplay实时刷新屏幕

sizeToFit

sizeToFit会自动调用sizeThatFits方法;
sizeToFit不应该在子类中被重写,应该重写sizeThatFits
sizeThatFits传入的参数是receiver当前的size,返回一个适合的size
sizeToFit可以被手动直接调用sizeToFit和sizeThatFits方法都没有递归,对subviews也不负责,只负责自己

内存

  1. Leaked memory:
  2. Abandoned memory:
  3. Cached memory:

Leaked memory 和 Abandoned memory 都属于应该释放而没释放的内存,都是内存泄露,而 Leaks 工具只负责检测 Leaked memory,而不管 Abandoned memory。在 MRC 时代 Leaked memory 很常见,因为很容易忘了调用 release,但在 ARC 时代更常见的内存泄露是循环引用导致的 Abandoned memory,Leaks 工具查不出这类内存泄露,应用有限。

对于 Abandoned memory,可以用 Instrument 的 Allocations 检测出来。检测方法是用 Mark Generation 的方式,当你每次点击 Mark Generation 时,Allocations 会生成当前 App 的内存快照,而且 Allocations 会记录从上回内存快照到这次内存快照这个时间段内,新分配的内存信息

在 GitHub 上有一些内存泄露检测相关的项目,例如 HeapInspector-for-iOS 和 MSLeakHunter。

HeapInspector-for-iOS 可以说是 Allocations 的改进。它通过 hook 掉 alloc,dealloc,retain,release 等方法,来记录对象的生命周期。具体的检测内存泄露的方法和原理,与 Instrument 的 Allocations 一致。然而它跟 Allocations 一样,存在的问题是,你需要一个个场景去重复的操作,还有检测不及时。

MSLeakHunter 就简单得多,它只检测 UIViewController 和 UIView,通过 hook 掉 UIViewController 的 -viewDidDisappear: 方法,并认为 -viewDidDisappear: 后,UIViewController 将很快被释放,如果 UIViewController 没有被释放,则打个建议日志。这种做法其实不是很好,-viewDidDisappear: 被调用可能是因为又 push 进来一个新的 ViewController,把当前的 ViewController 挡住了,所以可能有很多错误的建议,需要结合你实际的操作去具体地分析日志。

MLeaksFinder 提供了内存泄露检测更好的解决方案。只需要引入 MLeaksFinder,就可以自动在 App 运行过程检测到内存泄露的对象并立即提醒,无需打开额外的工具,也无需为了检测内存泄露而一个个场景去重复地操作。MLeaksFinder 目前能自动检测 UIViewController 和 UIView 对象的内存泄露,而且也可以扩展以检测其它类型的对象

原理

MLeaksFinder 一开始从 UIViewController 入手。我们知道,当一个 UIViewController 被 pop 或 dismiss 后,该 UIViewController 包括它的 view,view 的 subviews 等等将很快被释放(除非你把它设计成单例,或者持有它的强引用,但一般很少这样做)。于是,我们只需在一个 ViewController 被 pop 或 dismiss 一小段时间后,看看该 UIViewController,它的 view,view 的 subviews 等等是否还存在。

具体的方法是,为基类 NSObject 添加一个方法 -willDealloc 方法,该方法的作用是,先用一个弱指针指向 self,并在一小段时间(3秒)后,通过这个弱指针调用 -assertNotDealloc,而 -assertNotDealloc 主要作用是直接中断言。

这样,当我们认为某个对象应该要被释放了,在释放前调用这个方法,如果3秒后它被释放成功,weakSelf 就指向 nil,不会调用到 -assertNotDealloc 方法,也就不会中断言,如果它没被释放(泄露了),-assertNotDealloc 就会被调用中断言。这样,当一个 UIViewController 被 pop 或 dismiss 时(我们认为它应该要被释放了),我们遍历该 UIViewController 上的所有 view,依次调 -willDealloc,若3秒后没被释放,就会中断言。

设计模式

简单工厂,工厂方法,抽象方法

都不需要知道具体类,把容易发生改变的地方封装起来,控制变化(哪里变化封装哪里),以适应客户端变动,项目拓展。

简单工厂:一个工厂的各种产品,创建一个类中,客户端无需知道具体产品名称,只要知道对应的参数,但是工厂的责任太重,且类过多时不利于维护与拓展。
一般对各种不同产品的构造函数的封装,需要一个标志产品参数以生产不同的产品。

工厂方法:在简单工厂下增加一个产品就要修改工厂类,不符合开闭,工厂方法下只需要增加具体工厂和产品就可以。
有一个工厂的抽象类,实现不同的具体工厂,每个子类工厂生产不同的产品

抽象工厂:类似一个产品族,一个产品的界面可以通过直接改变具体的工厂实例来改变风格。
类似工厂方法,但是每个子类工厂生产一个产品族,而不是一个产品。

特点

简单工厂模式:专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。它又称为静态工厂方法模式。它的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。在这个模式中,工厂类是整个模式的关键所在。它包含必要的判断逻辑,能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。用户在使用时可以直接根据工厂类去创建所需的实例,而无需了解这些对象是如何创建以及如何组织的。有利于整个软件体系结构的优化。

工厂方法模式:工厂方法是粒度很小的设计模式,因为模式的表现只是一个抽象的方法。提前定义用于创建对象的接口,让子类决定实例化具体的某一个类,即在工厂和产品中间增加接口,工厂不再负责产品的创建,由接口针对不同条件返回具体的类实例,由具体类实例去实现。工厂方法模式是简单工厂模式的衍生,解决了许多简单工厂模式的问题。首先完全实现‘开-闭 原则’,实现了可扩展。其次实现更复杂的层次结构,可以应用于产品结果复杂的场合。工厂方法模式是对简单工厂模式进行了抽象。有一个抽象的Factory类(可以是抽象类和接口),这个类将不在负责具体的产品生产,而是只制定一些规范,具体的生产工作由其子类去完成。在这个模式中,工厂类和产品类往往可以依次对应。即一个抽象工厂对应一个抽象产品,一个具体工厂对应一个具体产品,这个具体的工厂就负责生产对应的产品。

抽象工厂模式:抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。它有多个抽象产品类,每个抽象产品类可以派生出多个具体产品类,一个抽象工厂类,可以派生出多个具体工厂类,每个具体工厂类可以创建多个具体产品类的实例。每一个模式都是针对一定问题的解决方案,工厂方法模式针对的是一个产品等级结构;而抽象工厂模式针对的是多个产品等级结果。

优点

简单工厂模式:工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅"消费"产品。简单工厂模式通过这种做法实现了对责任的分割。简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。通过它,外界可以从直接创建具体产品对象的尴尬局面中摆脱出来。外界与具体类隔离开来,偶合性低。明确区分了各自的职责和权力,有利于整个软件体系结构的优化。

工厂方法模式:工厂方法模式是为了克服简单工厂模式的缺点(主要是为了满足OCP)而设计出来的。简单工厂模式的工厂类随着产品类的增加需要增加很多方法(或代码),而工厂方法模式每个具体工厂类只完成单一任务,代码简洁。工厂方法模式完全满足OCP,即它有非常良好的扩展性。

抽象工厂模式:抽象工厂模式主要在于应对“新系列”的需求变化。分离了具体的类,抽象工厂模式帮助你控制一个应用创建的对象的类,因为一个工厂封装创建产品对象的责任和过程。它将客户和类的实现分离,客户通过他们的抽象接口操纵实例,产品的类名也在具体工厂的实现中被分离,它们不出现在客户代码中。它使得易于交换产品系列。一个具体工厂类在一个应用中仅出现一次——即在它初始化的时候。这使得改变一个应用的具体工厂变得很容易。它只需改变具体的工厂即可使用不同的产品配置,这是因为一个抽象工厂创建了一个完整的产品系列,所以整个产品系列会立刻改变。它有利于产品的一致性。当一个系列的产品对象被设计成一起工作时,一个应用一次只能使用同一个系列中的对象,这一点很重要,而抽象工厂很容易实现这一点。抽象工厂模式有助于这样的团队的分工,降低了模块间的耦合性,提高了团队开发效率。

缺点

简单工厂模式:当产品有复杂的多层等级结构时,工厂类只有自己,以不变应万变,就是模式的缺点。因为工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,有可能造成工厂逻辑过于复杂,违背了"开放--封闭"原则(OCP).另外,简单工厂模式通常使用静态工厂方法,这使得无法由子类继承,造成工厂角色无法形成基于继承的等级结构。                  

工厂方法模式:不易于维护,假如某个具体产品类需要进行一定的修改,很可能需要修改对应的工厂类。当同时需要修改多个产品类的时候,对工厂类的修改会变得相当麻烦(对号入座已经是个问题了)。

抽象工厂模式:抽象工厂模式在于难于应付“新对象”的需求变动。难以支持新种类的产品。难以扩展抽象工厂以生产新种类的产品。这是因为抽象工厂几乎确定了可以被创建的产品集合,支持新种类的产品就需要扩展该工厂接口,这将涉及抽象工厂类及其所有子类的改变。          

适用范围

简单工厂模式:工厂类负责创建的对象比较少,客户只知道传入了工厂类的参数,对于始何创建对象(逻辑)不关心。

工厂方法模式:当一个类不知道它所必须创建对象的类或一个类希望由子类来指定它所创建的对象时,当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候,可以使用工厂方法。

抽象工厂模式:一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。这个系统有多于一个的产品族,而系统只消费其中某一产品族。同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。

测试

白盒测试和黑盒测试是软件测试的两种不同方法,任何工程产品(注意是任何工程产品)都可以使用二者之一进行测试:

白盒测试:是通过程序的源代码进行测试而不使用用户界面。这种类型的测试需要从代码句法发现内部代码在算法,溢出,路径,条件等等中的缺点或者错误,进而加以修正。
黑盒测试:是通过使用整个软件或某种软件功能来严格地测试, 而并没有通过检查程序的源代码或者很清楚地了解该软件的源代码程序具体是怎样设计的。测试人员通过输入他们的数据然后看输出的结果从而了解软件怎样工作。在测试时,把程序看作一个不能打开的黑盆子,在完全不考虑程序内部结构和内部特性的情况下,测试者在程序接口进行测试,它只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收和正确的输出。

二者最大的区别应该就是测试对象不一样,白盒测试主要针对的是程序代码逻辑,黑盒测试主要针对的是程序所展现给用户的功能,简单的说就是前者测试后台程序后者测试前台展示功能。

什么是黑盒测试和白盒测试?

任何工程产品(注意是任何工程产品)都可以使用以下两种方法之一进行测试。

黑盒测试:已知产品的功能设计规格,可以进行测试证明每个实现了的功能是否符合要求。
白盒测试:已知产品的内部工作过程,可以通过测试证明每种内部操作是否符合设计规格要求,所有内部成分是否以经过检查。

黑盒测试

软件的黑盒测试意味着测试要在软件的接口处进行。这种方法是把测试对象看做一个黑盒子,测试人员完全不考虑程序内部的逻辑结构和内部特性,只依据程序的需求规格说明书,检查程序的功能是否符合它的功能说明。因此黑盒测试又叫功能测试或数据驱动测试。

黑盒测试主要是为了发现以下几类错误:

1、是否有不正确或遗漏的功能?
2、在接口上,输入是否能正确的接受?能否输出正确的结果?
3、是否有数据结构错误或外部信息(例如数据文件)访问错误?
4、性能上是否能够满足要求?
5、是否有初始化或终止性错误?

白盒测试

软件的白盒测试是对软件的过程性细节做细致的检查。这种方法是把测试对象看做一个打开的盒子,它允许测试人员利用程序内部的逻辑结构及有关信息,设计或选择测试用例,对程序所有逻辑路径进行测试。通过在不同点检查程序状态,确定实际状态是否与预期的状态一致。因此白盒测试又称为结构测试或逻辑驱动测试。

白盒测试主要是想对程序模块进行如下检查:

1、对程序模块的所有独立的执行路径至少测试一遍。
2、对所有的逻辑判定,取“真”与取“假”的两种情况都能至少测一遍。
3、在循环的边界和运行的界限内执行循环体。
4、测试内部数据结构的有效性,等等。

以上事实说明,软件测试有一个致命的缺陷,即测试的不完全、不彻底性。由于任何程序只能进行少量(相对于穷举的巨大数量而言)的有限的测试,在未发现错误时,不能说明程序中没有错误。

灰盒测试

灰盒测试,是介于白盒测试与黑盒测试之间的,可以这样理解,灰盒测试关注输出对于输入的正确性,同时也关注内部表现,但这种关注不象白盒那样详细、完整,只是通过一些表征性的现象、事件、标志来判断内部的运行状态,有时候输出是正确的,但内部其实已经错误了,这种情况非常多,如果每次都通过白盒测试来操作,效率会很低,因此需要采取这样的一种灰盒的方法。 
坚持原创技术分享,您的支持将鼓励我继续创作!