objective-c代码总是先预编译成C代码,runtime机制也是基于C的实现。
消息机制是runtime的基础。研究runtime机制基本上就是阅读和理解对应的C代码。
objc_msgSend(C代码)
void objc_msgSend(id self,SEL op, ...)
objc_msgSend(self,@selector(doSomethingWithVar:),var1);
消息转发机制
clang -rewrite-objc xxx.mm
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
– (id)forwardingTargetForSelector:(SEL)aSelector
– (void)forwardInvocation:(NSInvocation *)anInvocation
doesNotRecognizeSelector
方法对换
void method_exchangeImplementations(Method m1, Method m2)
如果不小心将一个可变数组赋值给它,将导致修改可变数组的元素的时候导致该“不可变数组”的元素发生变化。实际上指向了可变数组的内存。
初始化的时候会导致得到一个不可变数组,增加修改的时候会崩溃。doesnotreconginizeselector
利用runtime动态修改 methodList 的值来添加成员方法,这也是 Category 实现的原理,因此它不能增加成员变量。添加数据可以通过关联对象来绑定一个对象。
使用场景
extension和category的区别?
extension
extension在编译期决议,它就是类的一部分,在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension。
extension可以添加实例变量.
category是在运行期决议的。
category是无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。但是通过关联对象可以绑定数据成员。
typedef struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
} category_t;
上面结构表面,category可以增加实例方法,类方法,实现协议,添加属性(关联对象)。不能增加实例变量。
通过编译成C代码可以看的更清晰
clang -rewrite-objc MyClass.m
+load
代码形式 | 编译器/运行期 | 实例变量 | 和类的关系 | 类的源代码 |
---|---|---|---|---|
@interface MainViewController ()编译器决定 |
可以增加实例变量 |
是类的一部分,跟类的产生而产生,随类的消亡一起消亡.用来隐藏类的私有信息。 |
需要类的源代码 |
|
@interface MainViewController (Gesture) | 运行期决定 | 不可以增加实例变量,但是可以绑定一个数据对象 | 不是类的组成部分,由运行期修改类的方法数组增加的方法 | 不需要类的源代码 |
运行时通过AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的,这是一个全局的map,key是关联对象的指针地址,value是另外一个AssociationsHashMap,里面保存了关联对象的kv对。
KVC运用了一个isa-swizzling技术。isa-swizzling就是类型混合指针机制。KVC主要通过isa-swizzling,来实现其内部查找定位的。isa指针,如其名称所指,(就是is a kind of的意思),指向维护分发表的对象的类。该分发表实际上包含了指向实现类中的方法的指针,和其它数据。
一个对象在调用setValue的时候,
-(1)首先根据方法名找到运行方法的时候所需要的环境参数。
-(2)他会从自己isa指针结合环境参数,找到具体的方法实现的接口。
-(3)再直接查找得来的具体的方法实现。
当观察者为一个对象的属性进行了注册,被观察对象的isa指针被修改的时候,isa指针就会指向一个中间类,而不是真实的类。所以isa指针其实不需要指向实例对象真实的类。所以我们的程序最好不要依赖于isa指针。在调用类的方法的时候,最好要明确对象实例的类名。
只有当我们调用KVC去访问key值的时候KVO才会起作用,KVO是基于KVC实现的。
因为KVC的实现机制,可以很容易看到某个KVC操作的Key,也很容易的跟观察者注册表中的Key进行匹对。假如访问的Key是被观察的Key,那么我们在内部就可以很容易的到观察者注册表中去找到观察者对象,而后给他发送消息。
总结一下,想使用KVO有三种方法:
1)使用了KVC
使用了KVC,如果有访问器方法,则运行时会在访问器方法中调用will/didChangeValueForKey:方法;
没用访问器方法,运行时会在setValue:forKey方法中调用will/didChangeValueForKey:方法。
2)有访问器方法
运行时会重写访问器方法调用will/didChangeValueForKey:方法。
因此,直接调用访问器方法改变属性值时,KVO也能监听到。
3)显示调用will/didChangeValueForKey:方法。
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。
tasklet运行在伪并发中,使用channel机制进行同步数据交换。python中的greenlet提供了微线程的操作。不同于多线程,它给我们提供了一种更加轻量的异步编程模式。
协程(Coroutine)提供了不同于线程的另一种方式,它首先是串行化的。其次,在串行化的过程中,协程允许用户显式释放控制权,将控制权转移另一个过程。释放控制权之后,原过程的状态得以保留,直到控制权恢复的时候,可以继续执行下去。所以协程的控制权转移也称为“挂起”和“唤醒”。
同步和异步决定了要不要开启新的线程
在当前线程中执行任务,不具备开启新线程的能力
dispatch_sync
将同步任务加入串行队列,会顺序执行,一般不这样做并且在一个任务未结束时调起其它同步任务会死锁。将同步任务加入并行队列,会顺序执行,但是也没什么意义。
在新的线程中执行任务,具备开启新线程的能力
dispatch_async
将异步任务加入串行队列,会顺序执行,并且不会出现死锁问题。将异步任务加入并行队列,会并行执行多个任务,这也是我们最常用的一种方式。
并发和串行决定了任务的执行方式
一个任务执行完毕后,再执行下一个任务
同步线程的实现,在同一个线程内依次执行任务。串行队列
dispatch_queue_create
dispatch_get_main_queue
主线程相关联的串行队列
并行 concurrency
其实是真正的异步,多核CPU可以同时开启多条线程供多个任务同时执行,互不干扰
只有多核CPU才存在并行,多个CPU独立开启线程调度
CPU时间片调度,同时只有一个线程在执行。
但是可以多个线程异步执行,从外部看起来像是同时在执行,实际上是根据优先级不断抢占CPU的时间片执行代码。高优先级的抢占的时间多并且优先执行,所以上层看起来是同时执行但有优先级的概念。叠加并行即多处理器后更复杂,即并行同时并发,并发未必是并行的,因为可能在单CPU上并发
并发队列
dispatch_get_global_queue
全局的并发队列同步函数不具备开启线程的能力,无论是什么队列都不会开启线程;
异步函数具备开启线程的能力,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)。
同步函数
(1)并发队列:不会开线程
(2)串行队列:不会开线程
异步函数
(1)并发队列:能开启N条线程
(2)串行队列:开启1条线程
把一组任务提交到队列中,这些队列可以不相关,然后监听这组任务完成的事件。
dispatch_group_create
创建一个调度任务组dispatch_group_async
把一个任务异步提交到任务组里dispatch_group_enter/dispatch_group_leave
这种方式用在不使用dispatch_group_async
来提交任务,且必须配合使用dispatch_group_notify
用来监听任务组事件的执行完毕dispatch_group_wait
设置等待时间,在等待时间结束后,如果还没有执行完任务组,则返回。返回0代表执行成功,非0则执行失败使用
dispatch_group_wait
,可以阻塞线程,等待group的任务执行完毕,才能继续执行后续任务使用
dispatch_group_notify
,不会阻塞线程(group外的线程执行顺序不受影响),而且可以在执行完成group的任务后进行操作
现在有4个任务,任务1、任务2、任务3、任务4. 任务3必须在任务2之后,任务4必须在前3个任务都执行完成后,才能执行,并且需要在主线程更新UI。
思路分析:
任务3必须在任务2之后,所以这两个必须串行执行,同时,任务2和3整体可以和任务1并行执行,最后,任务4只能等待前3个任务全部执行完成,才能执行。这里就可以用group快速实现场景需求。
-(void)dispatchGroup
{
dispatch_queue_t globalQuene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t selfQuene = dispatch_queue_create("myQuene", 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, globalQuene, ^{
NSLog(@"run task 1");
});
dispatch_group_async(group, selfQuene, ^{
NSLog(@"run task 2");
});
dispatch_group_async(group, selfQuene, ^{
NSLog(@"run task 3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"run task 4");
});
}
A)
dispatch_group_async(group, queue, ^{
// 。。。
});
<==>
B)
dispatch_group_enter(group);
dispatch_async(queue, ^{
//。。。
dispatch_group_leave(group);
});
-(void)disGroupEnterAndLeave{
dispatch_queue_t globalQuene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
//任务1
dispatch_group_enter(group);
dispatch_async(globalQuene, ^{
NSLog(@"run task 1");
sleep(1);
dispatch_group_leave(group);
});
//任务2
dispatch_group_enter(group);
dispatch_async(globalQuene, ^{
NSLog(@"run task 2");
sleep(2);
dispatch_group_leave(group);
});
//任务3
dispatch_group_enter(group);
dispatch_async(globalQuene, ^{
NSLog(@"run task 3");
sleep(3);
dispatch_group_leave(group);
});
//一直等待完成
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//任务4
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"run task 4");
});
}
imageNamed
的优点是当加载时会缓存图片。同时会占用内存。imageWithContentsOfFile
仅加载图片。适合加载一个大图片而且是一次性使用的情况,不缓存,节省内存。NSDateFormatters
都是一个好的实践。applicationDidReceiveMemoryWarning:
的方法didReceiveMemoryWarning
启用Enable Zombie Objects 僵尸对象 进行悬挂指针的检测
上线打包的时候要确保关掉,否则会影响上线
应用Product -> Analysis进行内存泄露的初步检测
可以在xcode的build setting中打开implicit retain of ‘self’ within blocks,xcode编译器会给出警告,逐个排查警告。检查block的循环引用
在Build Settings启用Analyze During ‘Build’
应用Leak Instrument进行内存泄露查找。
通过查看dealloc
是否调用查看某个class是否泄露的问题
利用ARC中weak指针指向的对象在对象释放时会自动置为nil的特性来检测VC是否在内存驻留。
通过监控UINavigationController的navigation stack,可以判断一个VC的生命周期的开始和结束。就是当VC从navigation stack移除且VC的viewDidDisappear方法执行时,可以认为一个VC的生命周期即将结束。这时候就可以创建一个指向该VC的weak指针,并初始化一个定时器对VC进行延时扫描,最后通过1中的方法判断VC是否还驻留在内存从而得出VC是否发生内存泄露的结论。
在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop
#常量
1.NSString * const MY_CONSTANT;
2.const NSString * myVariable;
技巧:按*分割两部分,左边是数据的类型,右边是变量或常量。
1中,左边是数据类型为NSString,右边是constant,即常量,完整的说法是指向NSString(不可变类型)数据类型的常量MY_CONSTANT。
2中,左边是数据类型为const NSString,即数据类型为常量字符串,右边是变量,完整的说法是指向常量字符串的变量myVariable.
该变量可以再次指向其它任意的常量字符串。
1.百度推送证书更新问题
Mac OS 刚好升级到最新版本,按官方教程制作上传的证书无法验证通过。
最后发现是Open SSL版本兼容性问题,需要下载之前的版本重新安装。百度兼容性问题。
2.ViewController无法释放问题
知道是循环引用导致无法释放,但是因为涉及业务代码非常多,相关Block代码都需要逐一检查。
NSTimer、通知、Block。
在每个ViewController的dealloc做跟踪。
原理
利用JavaScriptCore.framework将JS代码利用Objective-C runtime动态解释成OC代码,封装成NSInvocation传递。
优点
缺点
1.安全风险 具备Native的能力,加密下发的JS代码或https
提高域名解析成功率
请求指定的页面信息,并返回实体主体。
GET请求请提交的数据放置在HTTP请求协议头中,GET方法通过URL请求来传递用户的输入,GET方式的提交你需要用Request.QueryString来取得变量的值。
GET方法提交数据,可能会带来安全性的问题,数据被浏览器缓存。
GET请求有长度限制。
向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。
POST请求可能会导致新的资源的建立和/或已有资源的修改。
POST方式提交时,你必须通过Request.Form来访问提交的内容
从客户端向服务器传送的数据取代指定的文档的内容。
请求服务器删除指定的页面。
DELETE请求一般返回3种码
类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头。
HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
允许客户端查看服务器的性能。
回显服务器收到的请求,主要用于测试或诊断。
https://www.jianshu.com/p/e9d989c12ff8
在Allocation中我们主要关注的是Persistent和Persistent Bytes,分别表示当前时间段,申请了但是还没释放的内存数量和大小。
记住当前这两个值,然后进入某个新页面,退出该页面,观察这两个值是否增加。需要注意的是,由于有些图片调用本身是有缓存的,如果是用SDWebImage管理,则网络图片都会缓存在内存中。因此退出页面后内存有增加是正常的,而且还有些单例的内存也是不会释放的,我们可以再次进入同一个页面,在图片都加载过的情况下,反复进入退出查看内存状况,如果持续增加,则说明有泄漏。