本篇内容不做为任何题目的解答,仅仅是我的学习记录,若有错误还请指正。算法
atomic
的seter
/getter
内部实现是用了互斥锁来保证seter
/getter
在多线程中的安全,但atomic
修饰的对象是自定义的,可能并无加锁,在多线程中atomic
修饰对象并不能保证线程安全。编程
nonatomic
的setter
/getter
方法的实现并无加互斥锁,因此nonatomic
修饰的对象是非线程安全的,同时nonatomic
的setter
/getter
方法也是非线程安全的,但也正由于没有互斥锁因此性能要比atomic
好。swift
(举例:当多个线程同时调用同一属性的读取方法时,线程1会get
到一个肯定的值,可是get
的值不可控,多是线程2或者线程3...set
以后的值,也多是以前的值)数组
被weak
修饰的对象在被释放时候会被自动置为nil
。缓存
runtime
维护了一个weak
表,用于存储指向某个对象的全部weak
指针。weak
表实际上是一个hash
哈希表,Key
是所指对象的地址,Value
是weak
指针的地址数组(这个地址的值是所指对象指针的地址)。安全
一、初始化时:runtime
会调用objc_initWeak
函数,初始化一个新的weak
指针指向对象的地址。bash
二、添加引用时:objc_initWeak
函数会调用 objc_storeWeak()
函数, objc_storeWeak()
的做用是更新指针指向,建立对应的弱引用表。服务器
三、释放时,调用clearDeallocating
函数。clearDeallocating
函数首先根据对象地址获取全部weak
指针地址的数组,而后遍历这个数组把其中的数据设为nil
,最后把这个entry
从weak
表中删除,最后清理对象的记录。网络
在runtime
内存空间中,SideTables
是一个64个元素长度的hash
数组,里面存储了SideTable
。SideTables
的hash
键值就是一个对象obj
的address
。 所以能够说,一个obj
,对应了一个SideTable
。可是一个SideTable
,会对应多个obj
。由于SideTable
的数量只有64个,因此会有不少obj
共用同一个SideTable
。而在一个SideTable
中,又有两个成员,分别是RefcountMap refcnts
(引用计数的 hash
表,其key
是obj
的地址,而value
,则是obj
对象的引用计数)和weak_table_t weak_table
(weak
引用全局hash
表),weak_table
则存储了弱引用obj
的指针的地址,其本质是一个以obj
地址为key
,弱引用obj
的指针的地址做为value
的hash
表。hash
表的节点类型是weak_entry_t
。 多线程
由于block
变量默认是声明为栈变量的,为了可以在block
的声明域外使用,因此要把block
copy
到堆,因此说为了block
属性声明和实际的操做一致,block
用copy
修饰。
对于strong
修饰符:
首先,在OC
中的三种类型的block
:
_NSConcreateGlobalBlock
全局的静态block
,不会访问任何外部变量。_NSConcreateStackBlock
保存在栈中的block
,当函数返回时会被销毁。_NSConcreateMallocBlock
保存在堆区的block
,当引用计数为0时被销毁。在MRC
环境下,block
存放在全局区、堆区、栈区,ARC
环境下只存放在全局区、堆区(栈区block
,在ARC
状况下会自动拷贝到堆区)。
因此在MRC
环境下,若是block
要访问外部变量,就必须用copy
修饰,而在ARC
环境下,若是block
访问了外部变量,系统会自动将栈区block
拷贝到堆区,因此也可使用strong
。只不过copy
是MRC
遗留下来的,习惯而已
block
中使用到的局部变量,都会在编译时动态建立的block
结构体中建立一个与局部变量名称同样的实例变量,该实例变量存储着外部的局部变量的值,而当执行block
时,再将这里存储下来的值取出来。
__block
所起到的做用就是只要观察到该变量被block
所持有,就将外部变量在栈中的内存地址拷贝堆中。__block
将变量包装成对象,而后在把捕获到的变量封装在block
的结构体里面,block
内部存储的变量为结构体指针,也就能够经过指针找到内存地址进而修改变量的值。
传递链:
(1)发生触摸事件后,系统会利用Runloop
将该事件加入到一个由UIApplication
管理的队列事件中.
(2)UIApplication
会从事件队列中取出最前面的事件,并将事件分发下去处理,发送事件给应用程序的主窗口UIWindow
.
(3)主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件
(4)遍历找到合适的视图控件后,就会调用视图控件的touches
方法来处理事件:touchesBegin
...此子控件就是咱们须要找到的第一响应者
响应链:
(1)判断第一响应者可否响应事件,若是第一响应者能进行响应,则事件在响应链中的传递终止。若是第一响应者不能响应,则将事件传递给 nextResponder
也就是一般的superview
进行事件响应
(2)若是事件继续上传至UIWindow
而且没法响应,它将会把事件继续上报给UIApplication
(3)若是事件继续上报至UIApplication
而且也没法响应,它将会将事件上报给其Delegate
(4)若是最终事件依旧未被响应则会被系统抛弃
注: 穿透事件,扩大响应区域。
KVC
(key-value-coding
)键值编码,是一种间接访问实例变量的方法。提供一种机制来间接访问对象的属性。
一、给私有变量赋值。
二、给控件的内部属性赋值(如自定义UITextFiled
的clearButton
,或placeholder
的颜色,通常可利用runtime
获取控件的内部属性名,Ivar *ivar = class_getInstanceVariable
获取实例成员变量)。
[textField setValue:[UIColor redColor] forKeyPath:@"placeholderLabel.textColor"];
复制代码
三、结合Runtime
,model
和字典的转换(setValuesForKeysWithDictionary
,class_copyIvarList
获取指定类的Ivar
成员列表)
KVO
是一种基于KVC
实现的观察者模式。当指定的被观察的对象的属性更改了,KVO
会以自动或手动方式通知观察者。
监听 ScrollView
的 contentOffSet
属性
[scrollview addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
复制代码
RunLoop
基本做用:
(1).使程序一直运行并接受用户输入:
程序一启动就会开一个主线程,主线程一开起来就会跑一个主线程对应的RunLoop
,RunLoop
保证主线程不会被销毁,也就保证了程序的持续运行。
(2)决定程序在什么时候应该处理哪些Event
好比:touches
事件,timer
事件,selector
事件
(3)节省CPU
时间 程序运行起来,没有任何事件源的时候,RunLoop
会告诉CPU
,要进入休眠状态,这时CPU
就会将其资源释放出来去作其余的事情,当有事件源来的时候RunLoop
就会被唤醒处理事件源。
autoreleasepool
以一个队列数组的形式实现,主要经过下列三个函数完成: (1).objc_autoreleasepoolPush
(2).objc_autoreleasepoolPop
(3).objc_autorelease
经过函数名就能够知道,对autorelease
分别执行push
,和pop
操做。销毁对象时执行release
操做。
iOS -- Autorelease & AutoreleasePool
Instruments
里面工具不少,经常使用的有:
(1).Time Profiler
:性能分析,用来检测应用CPU
的使用状况.能够看到应用程序中各个方法正在消耗CPU
时间。
(2).Zoombies
:检查是否访问了僵尸对象,可是这个工具只能从上往下检查,不智能
(3).Allocations
:用来检查内存,写算法的那批人也用这个来检查
(4).Leaks
:检查内存,看是否有内存泄漏
(5).Core Animation
:评估图形性能,这个选项检查了图片是否被缩放,以及像素是否对齐。被放缩的图片会被标记为黄色,像素不对齐则会标注为紫色。黄色、紫色越多,性能越差。
是一个Class
类型的指针.
每一个实例对象有个isa
的指针,他指向对象的类,而Class
里也有个isa
的指针, 指向meteClass
(元类)。元类保存了类方法的列表。当类方法被调用时,先会从自己查找类方法的实现,若是没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass
)也是类,它也是对象。元类也有isa指针,它的isa
指针最终指向的是一个根元类(root meteClass
).根元类的isa
指针指向自己,这样造成了一个封闭的内循环。
类方法:
(1).类方法是属于类对象的
(2).类方法只能经过类对象调用
(3).类方法中的self
是类对象
(4).类方法能够调用其余的类方法
(5).类方法中不能访问成员变量
(6).类方法中不定直接调用对象方法
实例方法:
(1).实例方法是属于实例对象的
(2).实例方法只能经过实例对象调用
(3).实例方法中的self
是实例对象
(4).实例方法中能够访问成员变量
(5).实例方法中直接调用实例方法
(6).实例方法中也能够调用类方法(经过类名)
共同点:
(1).+load
和+initialize
会被自动调用,不能手动调用它们。
(2).子类实现了+load
和+initialize
的话,会隐式调用父类的+load
和+initialize
方法
(3).+load
和+initialize
方法内部使用了锁,所以它们是线程安全的。
不一样点:
+load
方法是在runtime
加载类、分类的时候调用(根据函数地址直接调用,默认执行),每一个类,分类的+load
方法都只会调用一次。
先执行父类的+load
方法,再执行子类的+load
方法,而分类的+load
方法会在它的主类的+load
方法以后执行。(分类按照编译顺序前后调用)
+initialize
方法在第一次给某个类发送消息时调用(经过objc_msgSend
调用),而且只会调用一次,是懒加载模式,此时全部的类都已经加载到了内存中,若是这个类一直没有使用,就不会调用+initialize
方法。
先执行父类的+initialize
方法,再执行子类的+initialize
方法,若是子类没有实现+initialize
方法时,会调用父类的+initialize
方法,因此父类的+initialize
方法会调用屡次,若是分类实现了+initialize
方法,就会'覆盖掉'类自己的+initialize
方法。
+load
方法通常是用来交换方法Method Swizzle
,+initialize
方法主要用来对一些不方便在编译期初始化的对象进行赋值,或者说对一些静态常量进行初始化操做
_objc_msgForward
是 IMP
类型(函数指针),用于消息转发的:当向一个对象发送一条消息,但它并无实现的时候,_objc_msgForward
会尝试作消息转发。
_objc_msgForward
消息转发作的几件事:
(1).调用resolveInstanceMethod:
方法 (或 resolveClassMethod:
)。容许用户在此时为该 Class
动态添加实现。若是有实现了,则调用并返回YES
,那么从新开始objc_msgSend
流程。这一次对象会响应这个选择器,通常是由于它已经调用过class_addMethod
。若是仍没实现,继续下面的动做。
(2).调用forwardingTargetForSelector:
方法,尝试找到一个能响应该消息的对象。若是获取到,则直接把消息转发给它,返回非 nil
对象。不然返回nil
,继续下面的动做。注意,这里不要返回self
,不然会造成死循环。
(3).调用methodSignatureForSelector:
方法,尝试得到一个方法签名。若是获取不到,则直接调用doesNotRecognizeSelector
抛出异常。若是能获取,则返回非nil
:建立一个NSlnvocation
并传给forwardInvocation:
。
(4).调用forwardInvocation:
方法,将第3步获取到的方法签名包装成 Invocation
传入,如何处理就在这里面了,并返回非nil
。
(5).调用doesNotRecognizeSelector:
,默认的实现是抛出异常。若是第3步没能得到一个方法签名,执行该步骤。
一旦调用_objc_msgForward
,将跳过查找IMP
的过程,直接触发“消息转发”,若是调用了_objc_msgForward
,即便这个对象确实已经实现了这个方法,也会告诉objc_msgSend
没有找到这个方法的实现。最多见的场景是:你想获取某方法所对应的NSInvocation
对象。
先来看一下Method
在头文件中的定义:
typedef struct objc_method *Method;
struct objc_method {
SEL method_name;
char * method_types;
IMP method_imp;
};
复制代码
Method
被定义为一个objc_method
指针,在 objc_method
结构体中,包含一个 SEL 和一个IMP
,其中,SEL
是一个指向objc_selector
的指针,其实就是一个保存方法名的字符串。IMP
是一个“函数指针”,就是用来找到函数地址,而后执行函数。因此,Method
创建了SEL
和IMP
的关联,当对一个对象发送消息时,会经过给出的SEL
去找到IMP
,而后执行。
再加上继承的状况,能够总结出:当向一个对象发送消息时,会去这个类的方法缓存里寻找(cache methodLists
)若是有缓存则跳到方法实现,不然继续在这个类的 methodLists
中查找相应的SEL
,若是查不到,则经过super_class
指针找到父类,再去父类的缓存列表和方法列表里查找,层层递进,直到找到基类为止。最后仍然找不到,才会走消息转发的流程。
不能向编译后获得的类中增长实例变量;能向运行时建立的类中添加实例变量;
由于编译后的类已经注册在runtime
中,类结构体中的objc_ivar_list
实例变量的链表 和instance_size
实例变量的内存大小已经肯定,同时runtime
会调用 class_setIvarLayout
或class_setWeakIvarLayout
来处理strong weak
引用。因此不能向存在的类中添加实例变量;
运行时建立的类是能够添加实例变量,调用 class_addIvar
函数。可是得在调用 objc_allocateClassPair
以后,objc_registerClassPair
以前。
AOP
: Aspect Oriented Programming
面向切面编程,经过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术 AOP
是OOP
的延续,函数式编程的一种衍生范型。(利用 Objective-C Runtime
的黑魔法Method Swizzling
。)
优点:对业务逻辑的各个部分进行隔离,下降业务逻辑各部分之间的耦合度,提升程序的可重用性,提升了开发的效率。
(1).通讯过程当中使用的是未加密的明文,内容会被取抓。
(2).对于服务器或者客户端来讲,不会验证通讯方的身份,所以有可能遭到中间人的假装。
(3).没法验证所发送报文的完整性和安全性,并且报文的内容也有多是中间人篡改以后发送过来的。
HTTP
协议是基于TCP
链接的,是应用层协议,主要解决如何包装数据。HTTP
链接是一种短链接,客户端向服务器发送一次请求,服务器响应后链接断开,节省资源,并且服务器不能主动给客户端响应。
Socket
是对TCP/IP
协议的封装,Socket
自己并非协议,而是一个调用接口(API
),经过Socket
,咱们才能使用TCP/IP
协议。Socket
链接是一种长链接,客户端跟服务器端直接使用Socket
进行链接,没有规定链接后断开,所以客户端和服务器段保持链接通道,双方能够主动发送数据。Socket
默认链接超时时间是30秒,默认大小是8K(理解为一个数据包大小)。
POP
:面向协议编程 面向协议 = 协议 + 扩展 + 继承 经过协议、扩展作功能划分,下降模块间的耦合,加强代码的可扩展性。iOS
中有一个不足之处就是多重继承,而协议正好可以解决多重继承的问题。在Swift中结构体变的更增强大了,不只能定义属性,还能定义方法,还能多重继承协议,这是OC
所不提供的。
若是已经使用Alamofire
进行抽象URLSession
,为何不使用某些方法来抽象URL
,参数等的实质呢,所以,Moya
的基本思想是,咱们须要一些网络抽象层,以充分封装实际直接调用Alamofire
的层。
dispatch_once
能保证任务只会被执行一次,即便同时多线程调用也是线程安全的。经常使用于建立单例、swizzeld method
等功能。
@implementation ZHClass
+ (id)sharedInstance {
static ZHClass *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
复制代码
信号量方式:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.semaphore = dispatch_semaphore_create(1);
for (NSInteger i = 0; i < 10; i ++) {
[[[NSThread alloc]initWithTarget:self selector:@selector(read) object:nil]start];
[[[NSThread alloc]initWithTarget:self selector:@selector(write) object:nil]start];
}
}
- (void)read{
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"%s",__func__);
dispatch_semaphore_signal(self.semaphore);
}
- (void)write{
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"%s",__func__);
dispatch_semaphore_signal(self.semaphore);
}
复制代码
pthread_rwlock_t
方式:
@property (nonatomic,assign) pthread_rwlock_t rwlock;
//初始化读写锁
pthread_rwlock_init(&_rwlock, NULL);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (NSInteger i = 0; i < 3; i ++) {
dispatch_async(queue, ^{
[[[NSThread alloc]initWithTarget:self selector:@selector(readPthreadRWLock) object:nil]start];
[[[NSThread alloc]initWithTarget:self selector:@selector(writePthreadRWLock) object:nil]start];
});
}
//读
- (void)readPthreadRWLock{
pthread_rwlock_rdlock(&_rwlock);
NSLog(@"读文件");
sleep(1);
pthread_rwlock_unlock(&_rwlock);
}
// 写
- (void)writePthreadRWLock{
pthread_rwlock_wrlock(&_rwlock);
NSLog(@" 写入文件");
sleep(1);
pthread_rwlock_unlock(&_rwlock);
}
//销毁锁
- (void)dealloc{
pthread_rwlock_destroy(&_rwlock);
}
复制代码
dispatch_barrier
方式:
//异步队列
self.testqueue = dispatch_queue_create("rw.thread", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (NSInteger i = 0; i < 5; i ++) {
dispatch_async(queue, ^{
[self readBarryier];
[self readBarryier];
[self readBarryier];
[self writeBarrier];
});
}
- (void)readBarryier{
dispatch_async(self.testqueue, ^{
NSLog(@"读文件 %@",[NSThread currentThread]);
sleep(2);
});
}
- (void)writeBarrier{
dispatch_barrier_async(self.testqueue, ^{
NSLog(@"写入文件 %@",[NSThread currentThread]);
sleep(1);
});
}
复制代码
死锁是指两个或两个以上的进程在执行过程当中,因为竞争资源或者因为彼此通讯而形成的一种阻塞的现象,若无外力做用,它们都将没法推动下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。是操做系统层面的一个错误,是进程死锁的简称。
死锁的产生知足一些特定条件:
(1).互斥条件:进程对于所分配到的资源具备排它性,即一个资源只能被一个进程占用,直到被该进程释放。
(2).请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已得到的资源保持不放。
(3).不剥夺条件:任何一个资源在没被该进程释放以前,任何其余进程都没法对他剥夺占用。
(4).循环等待条件:当发生死锁时,所等待的进程一定会造成一个环路(相似于死循环),形成永久阻塞。
(1).NSLock
实现了最基本的互斥锁,遵循了 NSLocking
协议,经过lock
和unlock
来进行锁定和解锁。
(2).NSRecursiveLock
递归锁,能够被一个线程屡次得到,而不会引发死锁。它记录了成功得到锁的次数,每一次成功的得到锁,必须有一个配套的释放锁和其对应,这样才不会引发死锁。只有当全部的锁被释放以后,其余线程才能够得到锁
(3).NSCondition
是一种特殊类型的锁,经过它能够实现不一样线程的调度。一个线程被某一个条件所阻塞,直到另外一个线程知足该条件从而发送信号给该线程使得该线程能够正确的执行。
(4).NSConditionLock
对象所定义的互斥锁能够在使得在某个条件下进行锁定和解锁。它和 NSCondition
很像,但实现方式是不一样的。
(5).pthread_mutex
,互斥锁是一种超级易用的互斥锁,使用的时候,只须要初始化一个pthread_mutex_t
用pthread_mutex_lock
来锁定 pthread_mutex_unlock
来解锁,当使用完成后,记得调用 pthread_mutex_destroy
来销毁锁。
(6).pthread_rwlock
,读写锁,在对文件进行操做的时候,写操做是排他的,一旦有多个线程对同一个文件进行写操做,后果不可估量,但读是能够的,多个线程读取时没有问题的。
当读写锁被一个线程以读模式占用的时候,写操做的其余线程会被阻塞,读操做的其余线程还能够继续进行。 当读写锁被一个线程以写模式占用的时候,写操做的其余线程会被阻塞,读操做的其余线程也被阻塞。
(7).dispatch_semaphore
,信号量机制实现锁,等待信号,和发送信号,当有多个线程进行访问的时候,只要有一个得到了信号,其余线程的就必须等待该信号释放。
(8).@synchronized
,一个便捷的建立互斥锁的方式,它作了其余互斥锁所作的全部的事情。
应当针对不一样的操做使用不一样的锁:
当进行文件读写的时候,使用pthread_rwlock
较好,文件读写一般会消耗大量资源,而使用互斥锁同时读文件的时候会阻塞其余读文件线程,而 pthread_rwlock
不会。 当性能要求较高时候,可使用pthread_mutex
或者 dispath_semaphore
,因为OSSpinLock
不能很好的保证线程安全,而在只有在iOS10
中才有 os_unfair_lock
,因此,前两个是比较好的选择。既能够保证速度,又能够保证线程安全。 对于NSLock
及其子类,速度来讲NSLock
< NSCondition
< NSRecursiveLock
< NSConditionLock
。
持续更新中..........