赞
踩
如有错误 请及时在评论中指出 文章将不定期更新
作用:这个函数是IMP类型(方法实现的内存地址也就是函数指针),用于消息转发,当向一个对象发送一条消息,但它并没有实现时,这个函数会尝试进行消息转发。
直接调用现象:可能导致程序crash,因为很可能查不到函数的实现(jspatch热更新就是使用的这个来调用或者替换oc的方法,让app具备热更新的功能)
runtime对注册的类会进行布局,对于weak对象会放到一个hash表(字典)中,用weak指向的内存地址作为key(weak对象的内存地址),用指向weak对象的所有weak指针地址组成的数组作为value,当此对象的引用计数为0时dealloc,假如weak指向的对象内存地址为a(即key),然后就会以a为键在这个weak表中进行搜索,找到所有以a为key的weak对象(即对象的那块内存),从而置为nil。
不能向编译后的类中增加实例变量,因为编译后的类已经注册在runtime中,类结构体中的objc_ivar_list实例变量的链表和instance_size实例变量的内存大小已经确定,同时runtime会调用class_setIvarLayout或class_setWeakvarLayout 来处理strong weak 引用。所以不能向编译后的类中添加实例变量。
可以向运行时创建的类中添加实例变量,需要调用class_addIvar来添加,并且需要在objc_allocateClassPair之后和objc_registreClassPair之前调用class_addIvar进行实例变量的添加,原因此时实例变量的链表和所占内存都未确定。
runloop是为了线程而生,是线程的基础架构部分,每个线程都有与之对应的runloop对象。
其他知识:
(1)主线程中的runloop是默认启动的,其他线程默认不启动。
(2)获取runloop的代码:NSRunLoop *runloop = [NSRunLoop currentRunLoop];
(3)runloop的作用:让线程在没有执行任务时休息,有任务时执行任务。
(4)让线程一直存在着
制定事件在运行循环中的优先级。
该优先级分为:
空闲状态(NSDefaultRunLoopMode(kCFRunLoopDefaultMode)(系统默认提供这个状态))
UITrackingRunLoopMode(scrollview滑动时的状态)
UIInitializationRunLoopMode;(启东时的状态)
NSRunLoopCommonModes;(model集合状态(kCFRunLoopCommonModes))
原因:当scrollview滑动时,runloop的mode会切换到UITrackingRunLoopMode模式来保证scrollview的滑动流畅度,而此时以NSDefaultRunLoopMode模式加入runloop的timer就会不在被调度。
解决方案:通过将timer添加到NSRunLoopCommonModes模式中可以解决这个问题。
代码演示:
//tempTimer你的timer的名字
[[NSRunLoop currentRunLoop] addTimer:tempTimer forMode:NSRunLoopCommonModes];
内部应该是一个do while 当while收到退出通知时,就结束这个循环,do里面应该是接收消息->等待->处理的逻辑
在编译和运行时通过分析代码上下文帮助开发者添加retain和release的代码来控制对象的retainCount来是实现的。
使用retainCount的机制来管理对象内存。
每次runloop时,都会检查对象的retainCount,如果为0,就认为该对象已经不会再被使用了,就把该对象释放掉。
分两种情况进行释放:
1、手动干预释放:指定autoreleasepool,这样在大括号结束时就会被释放。
2.系统自动去释放:(不手动指定autoreleasepool)autorelease对象出了作用域之后,会被添加到最近一次创建的自动释放池中,并在当前的runloop迭代结束时释放。如果一个autorelease对象在vc的viewdidload中被创建,那么该对象在viewdidappear执行前就会被销毁。
1.访问了野指针
2.死循环
3.向一个已经释放的对象发送消息或者访问其成员变量
autoreleasepool以一个双向链表的形式实现,主要通过objc_autoreleasepoolPush来实现push操作,通过objc_autoreleasePop来实现pop操作,通过objc_autorelease来实现销毁操作。
发生原因:一个对象中强引用了block,在block中又强引用了这个对象,此时就会造成循环引用(即形成强引用环就会发生循环引用)
解决方案:思路:避免强引用环的形成
使用弱引用(代码演示:__weak typeof(self) weakSelf = self)
默认情况下,block访问外部变量是复制(深拷贝)过去的,即写操作对原变量不生效。想要修改可以通过__block来修饰变量实现修改外部变量的目的。
uiview的是不需要的,但是GCD和NSNotificationCenter这些含有ivar的系统api,需要注意循环引用,尽量采用弱引用来避免。
GCD的队列分两种类型,并行队列(concurrent dispatch queue)和串行队列(serial dispatch queue)
使用dispatch group将多个block追加到global group queue中,等待这些block执行完毕,就会到制定的线程中执行接下来的操作。
代码演示:- (void)groupQueueDemo{
//模拟有两个任务完成后再执行下一个任务的过程
//第一个任务
dispatch_group_async(Group, GlobalQueue, ^{
});
//第二个任务
dispatch_group_async(Group, GlobalQueue, ^{
});
//执行完以上两个任务后在执行的任务,这里选择的主线程来执行接下来的任务
dispatch_group_notify(Group, MainQueue, ^{
});
}
等待并行队列任务执行完成时再执行追加dispatch_barrier_async的任务,避免数据竞争等问题
注意该函数必须放到自定义并行队列中才能正确执行,不能放到globalQueue中,如果放到globalQueue中,其效果跟dispatch_async相同。
因为该函数容易造成死锁
打印1,然后主线程发生死锁(使用的是同步线程,打印2的任务会等待打印3的任务完成时才会执行,而打印3的任务也要等待打印2的任务完成时才会执行打印3的任务,两者进入到了互相等待的情况,就造成了死锁)。
observer设置观察者
keypath:被观察的对象的属性
options:需要记录的属性的哪些情况的值,即观察的选项
context:上下文
// 添加键值观察
/*
1 观察者,负责处理监听事件的对象
2 观察的属性
3 观察的选项
4 上下文
*/
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person Name"];
// 所有的 kvo 监听到事件,都会调用此方法
/*
1. 观察的属性
2. 观察的对象
3. change 属性变化字典(新/旧)
4. 上下文,与监听的时候传递的一致
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
- (void)useKVO{
//手动调用kvo,调用观察者的响应方法,即observerforkey的方法,调用这两方法就会调用kvo的回调方法
[self willChangeValueForKey:@"需要观察的对象的那个属性"];
[self didChangeValueForKey:@"需要观察的对象的那个属性"];
}
系统kvo的调用过程是,改变被观察对象那个被观察的属性时,kvo就会被调用。
kvo被调用的原理是调用willChangeValueForKey(记录旧值)这个函数,然后在调用didChangeValueForKey(记录新值)这个函数之后就会触发kvo
系统是通过isa混写(isa-swizzing)来实现的
这两个key都可以使用
(1)必须用在集合对象上或普通对象的集合属性上
(2)简单几何运算符有:@avg @count @max @min @sum
(3)格式@"@sum.age" 或 @"集合属性.@max.age"
kvo支持成员变量
首先objc是拓展了c语言,加入了面向对象特性和smalltalk的消息传递机制,这个拓展的核心是使用c和汇编编写runtime,它是oc面向对象和动态性的基石。runtime的核心——消息传递。
// [array insertObject:foo atIndex:5];
// objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);
第一行代码在编译时会被替换成第二段代码,这里就体现了oc的消息传递。C语言在调用方法时是直接跳到方法的内存去执行那段代码,不具备动态性,而oc是向对应的对象发出一条消息,由运行时来决定是接受还是转发还是不理睬。
在objc中,类、对象、方法都是c的结构体。
解释一下objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);的执行过程
(1)首先根据array的isa找到它的class
(2)然后在class的objc_method_list查找selector
(3)如果没找到,就到superclass中去查找
(4)一旦找到selector,就会执行IMP(函数的实现代码)
注意:这种方式会导致每次都要进行遍历,从而造成效率低下,苹果采用了objc_cache以selector的method_name作为key,method_imp作为value存储起来,下次调用这个selector时,直接从objc_cache中获取,防止每次都进行遍历导致的效率低下
如果selector找不到,通常程序会崩溃并抛出unrecognized selector sent to …的异常,在抛出这个异常之前有三种方式来尝试解决这个问题:
(1)method resolation:系统会调用+resolveInstanceMethod: 或者 +resolveClassMethod:来查找你提供的另一个函数实现:
代码演示:
//替换的方法,此时并没有是实现foo:的方法,当调用foo:这个方法时,由于找不到foo:,系统会去调用resolveInstanceMethod,此时添加完新方法并返回yes,系统会重新开始一次消息发送到过程,去调用fooMethod这个方法
void fooMethod(id obj, SEL _cmd)
{
NSLog(@"Doing foo");
}
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if(aSEL == @selector(foo:)){
class_addMethod([self class], aSEL, (IMP)fooMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod];
}
(2)fast forwarding
如果目标对象实现了 -forwardingTargetForSelector: ,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(foo:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
只要这个方法返回的不是 nil 和 self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续 Normal Fowarding 。
如果对象实现了forwardingTargetForSelector,runtime会调用这个方法,给你转发消息的机会
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(foo:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
只要该函数返回值不是nil或者self,消息发送就会重启,将消息发送给你指定的对象。否则,就继续normal fowarding。
(3)normal forwarding:
这个runtime挽救程序的最后一次机会,程序会调用methodSignatureForSelector获得函数的参数和返回值类型,如果返回nil,程序会发出doesNotRecognizeSelector消息,然后崩溃。如果返回一个函数签名,程序会创建一个NSInvocation对象并发送forwardInvocation消息个目标对象。NSInvocation 实际上就是对一个消息的描述,包括selector 以及参数等信息。所以你可以在 -forwardInvocation: 里修改传进来的 NSInvocation 对象,然后发送 -invokeWithTarget: 消息给它,传进去一个新的目标:
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL sel = invocation.selector;
if([alternateObject respondsToSelector:sel]) {
[invocation invokeWithTarget:alternateObject];
}
else {
[self doesNotRecognizeSelector:sel];
}
}
不设置监听,就可以关闭了。
实现思想:当设置监听一个对象时,动态创建一个新的继承被监听对象原本类的类,并重写setter方法,最后把这个对象的isa指针(即这个对象的类的内存地址)更改为这个新创建的类,这时,这个对象就变成了这个新类的实例
重写addobserver方法:
(1)检查该对象的类有没有对应的setter方法,如果没有就抛出异常
(2)检查对象的类是不是一个kvo类,如果不是,就新建一个继承这个类的新类,并把对象的isa指向这个新类
(3)检查这个新的kvo类有没有重写setter方法,如果没有,就重写setter方法
(4)添加这个观察者
苹果使用了isa-swizzing的方式实现了kvo。
首先当设置监听的时候,运行时会动态创建一个集成被观察对象的类的新类,并重写setter方法,然后让这个对象的isa指针指向这个新类,此时这个对象就变成了新类的实例,当被观察的对象的被观察属性变化时,就会调用被重写的setter方法,而重写方法中调用了willchangevalueforkey和didchangevalueforkey,此时kvo就会被调用。
storyboard和xib创建的vc,会有一个私有数组强引用所有top level对象,而所有top level对象会强引用所有子对象,所以没必要再强引用这些子对象。
通过kvc的方式来设置这些属性就可以使用了,key-path的形式,比如说,设置view的圆角就可以使用这种方式
产生这个错误的原因:
访问了野指针;
向已经释放的对象发送消息,或者调用这个对象的属性;
死循环;
解决方案:
(1)重写object的responseToselector方法:
1. #ifdef _FOR_DEBUG_
2. -(BOOL) respondsToSelector:(SEL)aSelector {
3. printf("SELECTOR: %s\n", [NSStringFromSelector(aSelector) UTF8String]);
4. return [super respondsToSelector:aSelector];
5. }
6. #endif
(2)通过instrument里面的zombie来定位问题
(3)设置全局断点来定位
(4)打开系统提供的Address Sanitizer功能,在edit scheme中
bt:最后一次的调用堆栈,显示函数的调用顺序
break NUM :在指定的行上设置断点
clear FILENAME:NUM :删除某个文件中某行的断点
continue:继续执行断点调试中的程序
display EXPR :每次程序停止后显示表达式的值,表达式由程序定义的变量组成
file FILE:装在可执行的文件进行调试
help NAME:显示执行命令的帮助信息
info break:显示当前断点信息
info files:显示被调试文件的详细信息
info func:显示所有函数的名称
info local:显示当前函数中的局部变量信息
info prog:显示被调试程序的执行状态
info var:显示所有的全局和静态变量的名称
kill:终止正在被调试的程序
list:显示源代码段
make:在不退出gdb的情况下运行make
next:在不单步执行进入下一个函数之前,向前执行一段源代码
print EXPR:显示表达式EXPR的值
print-object:打印一个对象
print (int)name:打印一个类型
print-object 【art description】:调用一个函数
set art = @"test":设置变量值
whatis:查看变量的类型
控件不能接受事件的原因:
(1)交互属性未打开
(2)控件隐藏时
(3)控件透明时
(4)未设置交互属性时,uiimageView不能接受事件
响应者链:由响应者组成的链条
响应者对象:继承uiresponder类创建的对象就是响应者对象
如何寻找上一个响应者:
(1)如果当前view是控制器的view,那么控制器就是上一个响应者
(2)如果当前view不是控制器的view,那么父控件就是上一个响应者
(3)在视图层次结构的最顶级对象也不能处理消息,就将消息传递给window
(4)如果window对象也不处理,就传给uiapplication对象
(5)如果uiapplication也不处理,就丢弃
系统如何查找最合适的view来接受事件:
(1)先判断自己是否能接受触摸事件
(2)在判断触摸的当前点在不在自己身上
(3)如果在自己身上,它会从后往前遍历,遍历出每一个控件,重启前两部
(4)如果没有合适的子控件,那么自身就是最合适的view
(5)在这个过程中,系统会调用hitTest和pointInside
如果当前点不在view中,还要调用就重写hitTest就可以
(1)使用kvc的方式访问
- (void)readPrivatePropertyWithKVC{
//使用kvc的方式读写私有属性
NSString *dogName = [self.dog valueForKey:@"name"];
NSLog(@"%@", dogName);
[self.dog setValue:@"我是一个迷你小狗" forKey:@"name"];
NSString *dogName1 = [self.dog valueForKey:@"name"];
NSLog(@"%@", dogName1);
}
(2)使用runtime的方式访问
- (void)readPrivatePropertyWithRuntime{
//使用runtime读写对象的私有属性
Ivar dogName = class_getInstanceVariable([Dog class], "_name");
NSString *dogName_name = (NSString *)object_getIvar(self.dog, dogName);
NSLog(@"%@", dogName_name);
object_setIvar(self.dog, dogName, @"我是一只小鸭纸");
dogName_name = (NSString *)object_getIvar(self.dog, dogName);
NSLog(@"%@", dogName_name);
}
拓展
。m中设置私有变量就是用的这种方式
遵守nscoding协议即可
apple pay是一种移动支付技术,可以跟支付宝微信什么的买东西
流程:
(1)配置apple pay
(2)用户授权支付请求
(3)服务器处理支付请求
(1)沙盒目录:
1)Application:存放程序源文件,上架前经过数字签名,上架后不可修改
2)Documents:常用目录,iCloud备份目录,存放数据,这里不能放缓存,否则上架不能通过
3)Library:cache:存放体积大又不需要备份的数据(sdwebimage用的就是这个)
Preference:存放设置信息(iCloud设置信息就存放在这)
4)tmp:存放临时文件,不会被备份,而且这个文件下的数据有随时被清除的可能
(2)APP Bundle:
1)info.plist:存放项目的配置信息,系统依赖此文件获得应用程序的相关信息
2)可执行文件:此文件包括应用程序的入口和通过静态链接到target的代码
3)资源文件:图片、声音、视频等文件
4)其他:可以嵌入定制的资源
(1)点击xcode中的window
(2)选择需要查看日志的设备
(3)选择view of device log
目的:防止中途篡改APP,保证APP的完整性,保证APP是由指定私钥发布的
流程:首先利用摘要算法获得摘要,然后利用私钥对摘要进行加密获得密文,最后将源文本、密文和私钥对应的公钥一起发布。
然后验证方首先验证公钥是否是私钥发布的,然后利用公钥对密文进行解密获得摘要,然后根据摘要算法从APP获得摘要,两个摘要进行对比,如果相等就一切正常,如果有一步未通过,就是验证失败。
(1)后台获取API(background Fetch):使用场景:当用户还未打开APP时,APP就有机会运行代码来加载数据,刷新UI。这样当用户打开时,就可以直接查看数据,不需要加载刷新的过程。
(2)推送唤醒(remote notifications):使用场景:当收到远程推送时,系统会唤醒设备和后台应用,用来加载数据,刷新UI,然后显示通知在设备上,告诉用户。这样,当用户查看推送时,就省去了加载刷新的过程,体验更加快速
实现原理:应该是一个uiview,然后frame是contentsize,bounds是它的活动范围
捕捉和响应手势:是通过在手势的代理方法中进行判断来捕捉和响应手势的
(1)首先建一个分类
(2)然后写一个新的方法来添加自己的代码
(3)然后在运行这个方法之前告诉系统自己要在某个方法中添加代码
代码演示:
#import "NSObject+logging.h"
@implementation NSObject (logging)
+ (void)myDescription{
NSLog(@"日志记录功能展示");
}
+ (void)load{
Method myMethod = class_getClassMethod(self, @selector(myDescription));
Method osMethod = class_getClassMethod(self, @selector(description));
method_exchangeImplementations(myMethod, osMethod);
}
@end
(1)分类的initialize会覆盖原类的该方法,load不会覆盖
(2)两者都不需要显性声明继承
(3)load在程序执行时就会执行,而initialize会在使用某个类时才会调用,是惰性调用,都只调用一次
(4)load方法都会执行,先父类,再子类,在分类,initialize是先父类,再子类,如果有分类,只执行分类,不执行子类
拓展:
程序启动时需要启动一些模块,这时一般是添加在appdelegate中的didfinish那个方法中,这事为了appdelegate的简洁,可以使用load方法和notification来实现同样的功能。
代码演示:
+ (void)load
{
__block id observer =
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
WEAKSELF2
[weakSelf setup]; // Do whatever you want
[[NSNotificationCenter defaultCenter] removeObserver:observer];
}];
}
说明:
(1)+load可以在足够早的时间被调用
(2)block版本的通知会生成一个__nsobserver *给外部remove观察者
(3)block对observer的捕捉早于函数的返回,所以若不加__block,会返回nil
(4)在block结束时,清除observer,无需其他清理工作
(5)这样,程序启动的代码就可以在其他位置进行处理
同时注意:appdelegate文件中尽量只处理app的delegate,减少其他代码
(1)使用runtime来实现
(2)代码演示:
首先新建一个类别(category)
#import <Foundation/Foundation.h>
@interface NSObject (AddPorpertyCategory)
@property (nonatomic, copy) NSString *testName;
- (void)test;
@end
#import "NSObject+AddPorpertyCategory.h"
static const NSString *testNameKey = @"testName";
@implementation NSObject (AddPorpertyCategory)
- (void)test{
self.testName = @"lalalallaal";
NSLog(@"%@", self.testName);
}
#pragma mark - getter setter
- (void)setTestName:(NSString *)testName{
objc_setAssociatedObject(self, (__bridge const void *)(testNameKey), testName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)testName{
return objc_getAssociatedObject(self, (__bridge const void *)(testNameKey));
}
@end
(1)nsoperation可取消任务执行
(2)提供了更多的功能,更容易封装
(3)提供了任务的状态
(4)可以更容易的添加任务间的依赖关系
(5)提供了GCD中不那么容易复制的特性
(1)strong用户arc情况下,类似retain,使对象计数器加1,且不能用于修饰基本数据类型
(2)weak用于修饰oc对象,不能是计数器加1,且不能用于修饰基本数据类型,对象销毁时使对象指向nil
(3)unsafe_unretained类似assign,可 修饰所有属性,不能使计数器加1,也不能使对象销毁时对象指向nil,容易造成野指针错误
在.h文件中定义为readonly,在。m文件中加一个拓展,然后声明readwrite
是每个类对象所对应的类,当使用类名发送消息时,就会去meta-class中查找该消息的实现
(1)uiview可以理解为calayer的代理
(2)两者都有类似的树壮结构
(3)uiview继承与UIresponder,可以相应事件,而calayer主要用于图层内容管理,uiview的图层绘制就由calayer来完成
(4)uiview的layer属性可以返回uiview所使用的layer对象,layerClass方法可以返回主layer所使用的类,uiview通过重载layerClass这个方法可以自己指定layer
(5)uiview的layer树有三层copy,逻辑树:该部分代码可控制,更改layer的属性就是这部分;动画树:这是中间层,在这一层上执行渲染操作;显示树:这棵树正是被显示在屏幕上的内容。这三者的树型结构是相同的,只是属性不同而已
(6)uiview也可以理解为是对calayer的一次深度封装
个人理解:应该是首先建立一个定时器,执行时刻根据Duration的参数确定,当定时器到了执行事件的时刻就去执行animations的方法,动画执行完成时去执行completion的内容
当改变calayer的某个属性时,界面发生平滑转换时,就是发生了隐式动画。因为这一切都是默认行为,所以称之为隐式动画。
单独开一个线程去处理网络请求,防止主线程卡顿,然后实现代理就好
frame是相对父视图的位置,bounds是相对于自身的位置
UIImageJPEGRepresentation
推送分为两种,本地推送和远程推送
本地推送:由开发人员自己定义各种属性,开发人员来控制,不需要联网
远程推送:首先设备会与apns服务器之间形成一个长链接,然后设备会发送自己的uuid和boundle id给apns服务器,然后服务器处理后返回给设备一个deviceToken,然后设备将这个deviceToken告诉APP服务器,然后当需要给设备远程推送消息时,APP服务器会将deviceToken和推送消息发送给apns服务器,然后apns服务器根据deviceToken发送给确定的某一台设备。
runloop是跟线程的一部分,每个线程都会有对应的runloop,用来管理线程资源任务等的一种机制,当线程需要资源时就调用cpu资源,当不需要时,就让cpu休息来节省cpu资源
(1)定义,用于Foundation对象和core Foundation对象之间交换数据,俗称桥接
(2)使用情况:
1)Foundation对象转core Foundation对象
使用_bridge桥接后,ARC会接管两个对象
使用_bridge_retained桥接后,需要手动管理core Foundation对象
2)core Foundation对象转Foundation对象
使用_bridge桥接,需要手动管理core Foundation对象
使用_bridge_transfer桥接,系统会自动管理两个对象
使用过nsmutableAttributeString来实现富文本,根据需求对参数进行设置即可
(1)使用nsthread时:performSelectorInBackground (在后台开启新线程执行代码)
performSelectorOnMainThread(在主线程中执行代码)
performSelector: onThread: withObject: waitUntilDone: modes:
在制定线程中执行代码
(2)GCD:使用dispatch切换到需要执行代码的线程去执行即可
(1)首先注意要及时释放vc对象
(2)使用每次从文件读取的方式来展示图片,不使用imageWithNamed的方式
(3)根据高清大图获得屏幕分辨率前提下的大图,然后展示这个低分辨率的图片
(4)每次只加载一个,然后后台开一个线程去加载左右邻近的大图
(1)交换方法,用于在一些API中添加自己的代码来打印日志,比如dealloc、viewdidappear等方法中打印日志
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 需求:给imageNamed方法提供功能,每次加载图片就判断下图片是否加载成功。
// 步骤一:先搞个分类,定义一个能加载图片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
// 步骤二:交换imageNamed和imageWithName的实现,就能调用imageWithName,间接调用imageWithName的实现。
UIImage *image = [UIImage imageNamed:@"123"];
}
@end
@implementation UIImage (Image)
// 加载分类到内存的时候调用
+ (void)load
{
// 交换方法
// 获取imageWithName方法地址
Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
// 获取imageWithName方法地址
Method imageName = class_getClassMethod(self, @selector(imageNamed:));
// 交换方法地址,相当于交换实现方式
method_exchangeImplementations(imageWithName, imageName);
}
// 不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉,而且分类中不能调用super.
// 既能加载图片又能打印
+ (instancetype)imageWithName:(NSString *)name
{
// 这里调用imageWithName,相当于调用imageName
UIImage *image = [self imageWithName:name];
if (image == nil) {
NSLog(@"加载空的图片");
}
return image;
}
(2)使用runtime来给对象发送消息
// 创建person对象
Person *p = [[Person alloc] init];
// 调用对象方法
[p eat];
// 本质:让对象发送消息
objc_msgSend(p, @selector(eat));
// 调用类方法的方式:两种
// 第一种通过类名调用
[Person eat];
// 第二种通过类对象调用
[[Person class] eat];
// 用类名调用类方法,底层会自动把类名转换成类对象调用
// 本质:让类对象发送消息
objc_msgSend([Person class], @selector(eat));
(3)类\对象的关联对象,使用这个特性来给分类添加属性,利用runtime可以给对象关联对象的特性,可以给一些对象添加更多的属性,例如给alertview的delegate方法传递更多的参数
//给分类添加属性
// 定义关联的key
static const char *key = "name";
@implementation NSObject (Property)
- (NSString *)name
{
// 根据关联的key,获取关联的值。
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name
{
// 第一个参数:给哪个对象添加关联
// 第二个参数:关联的key,通过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
//传递更多参数
/**
* 删除点击
* @param recId 购物车ID
*/
- (void)shopCartCell:(BSShopCartCell *)shopCartCell didDeleteClickedAtRecId:(NSString *)recId
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:@"确认要删除这个宝贝" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
// 传递多参数
objc_setAssociatedObject(alert, "suppliers_id", @"1", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(alert, "warehouse_id", @"2", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
alert.tag = [recId intValue];
[alert show];
}
/**
* 确定删除操作
*/
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) {
NSString *warehouse_id = objc_getAssociatedObject(alertView, "warehouse_id");
NSString *suppliers_id = objc_getAssociatedObject(alertView, "suppliers_id");
NSString *recId = [NSString stringWithFormat:@"%ld",(long)alertView.tag];
}
}
(4)动态添加方法
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person *p = [[Person alloc] init];
// 默认person,没有实现eat方法,可以通过performSelector调用,但是会报错。
// 动态添加方法就不会报错
[p performSelector:@selector(eat)];
}
@end
@implementation Person
// void(*)()
// 默认方法都有两个隐式参数,
void eat(id self,SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
// 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(eat)) {
// 动态添加eat方法
// 第一个参数:给哪个类添加方法
// 第二个参数:添加方法的方法编号
// 第三个参数:添加方法的函数实现(函数地址)
// 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
class_addMethod(self, @selector(eat), eat, "v@:");
}
return [super resolveInstanceMethod:sel];
}
@end
(5)字典转模型
// Ivar:成员变量 以下划线开头
// Property:属性
+ (instancetype)modelWithDict:(NSDictionary *)dict
{
id objc = [[self alloc] init];
// runtime:根据模型中属性,去字典中取出对应的value给模型属性赋值
// 1.获取模型中所有成员变量 key
// 获取哪个类的成员变量
// count:成员变量个数
unsigned int count = 0;
// 获取成员变量数组
Ivar *ivarList = class_copyIvarList(self, &count);
// 遍历所有成员变量
for (int i = 0; i < count; i++) {
// 获取成员变量
Ivar ivar = ivarList[i];
// 获取成员变量名字
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 获取成员变量类型
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// @\"User\" -> User
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
// 获取key
NSString *key = [ivarName substringFromIndex:1];
// 去字典中查找对应value
// key:user value:NSDictionary
id value = dict[key];
// 二级转换:判断下value是否是字典,如果是,字典转换层对应的模型
// 并且是自定义对象才需要转换
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
// 字典转换成模型 userDict => User模型
// 转换成哪个模型
// 获取类
Class modelClass = NSClassFromString(ivarType);
value = [modelClass modelWithDict:value];
}
// 给模型中属性赋值
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
(1)系统默认的切圆角是利用layer对象的cornerredius和maskToBounds两个属性来实现的,但是设置maskToBounds会导致离屏渲染,从而引起性能下降,当需要切的圆角数量特别多时,就会产生掉帧、卡顿的情况,当然如果不需要设置maskToBounds就可以实现,就不需要优化,如果切的数量比较少,也不需要优化。
(2)如何解决:用core g'raphics画一个圆角矩形的图片,然后放在uiimageView上,如果需要展示的不是这个uiimageView,那就把它放在需要展示的那个视图的最底层
(1)首先有一个确定的URL
(2)然后根据URL去内存缓存中查找有没有该image的缓存,有直接用,没有去沙盒中查看有没有image缓存
(3)如果沙盒中有缓存,就把image放到内存缓存中,然后从内存缓存中读取该图片
(4)如果沙盒缓存中没有该图片,那就去查看下载队列中有没有该图片的下载,如果有就等待,然后按照上面的顺序读取图片,没有就开始下载,然后放到内存缓存中,然后刷新ui
使用FMDB结合FMDBMigrationManager的方式
首先自定义一个sql语句的类
#import <Foundation/Foundation.h>
#import "FMDBMigrationManager.h"
@interface Migration : NSObject<FMDBMigrating>
- (instancetype)initWithName:(NSString *)name andVersion:(uint64_t)version andExecuteUpdateArray:(NSArray *)updateArray;//自定义方法
@property (nonatomic, readonly) NSString *name;
@property (nonatomic, readonly) uint64_t version;
- (BOOL)migrateDatabase:(FMDatabase *)database error:(out NSError *__autoreleasing *)error;
@end
#import "Migration.h"
@interface Migration()
@property(nonatomic,copy)NSString * myName;
@property(nonatomic,assign)uint64_t myVersion;
@property(nonatomic,strong)NSArray * updateArray;
@end
@implementation Migration
- (instancetype)initWithName:(NSString *)name andVersion:(uint64_t)version andExecuteUpdateArray:(NSArray *)updateArray
{
if (self=[super init]) {
_myName=name;
_myVersion=version;
_updateArray=updateArray;
}
return self;
}
- (NSString *)name
{
return _myName;
}
- (uint64_t)version
{
return _myVersion;
}
- (BOOL)migrateDatabase:(FMDatabase *)database error:(out NSError *__autoreleasing *)error
{
for(NSString * updateStr in _updateArray)
{
[database executeUpdate:updateStr];
}
return YES;
}
@end
///每次在这里添加
FMDBMigrationManager * manager=[FMDBMigrationManager managerWithDatabaseAtPath:DBPath migrationsBundle:[NSBundle mainBundle]];
Migration * migration_1=[[Migration alloc]initWithName:@"新增USer表" andVersion:1 andExecuteUpdateArray:@[@"create table User(name text,age integer)"]];
Migration * migration_2=[[Migration alloc]initWithName:@"USer表新增字段email" andVersion:2 andExecuteUpdateArray:@[@"alter table User add email text"]];
Migration * migration_3=[[Migration alloc]initWithName:@"USer表新增字段address" andVersion:3 andExecuteUpdateArray:@[@"alter table User add address text"]];
[manager addMigration:migration_1];
[manager addMigration:migration_2];
[manager addMigration:migration_3];
BOOL resultState=NO;
NSError * error=nil;
if (!manager.hasMigrationsTable) {
resultState=[manager createMigrationsTable:&error];
}
resultState=[manager migrateDatabaseToVersion:UINT64_MAX progress:nil error:&error];
(1)会清除没有显示的view
(2)如果一直不处理内存警告,应用会被强制杀死
解决:
(1)sdwebimage清空缓存
(2)将图片改成缩略图显示
(3)避免使用imageNamed
(4)清除掉内存中不必要的东西,比如没用了的view之类
自我理解:非强联通图有向图的极大强连通子图,称为强连通分量
概念:
有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly
connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly
connected components)。
定义:
有向图强连通分量:
在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。
如果有向图G的每两个顶点都强连通,则称G是一个强连通图。
非强连通图有向图的极大强连通子图,成为强连通分量(strongly connected components)。
下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达,{5},{6}也分别是两个强连通分量。
(1)注意cell要进行重用,注册cell重用标识符
(2)避免cell的重新布局
(3)提前计算并缓存cell的属性及内容
(4)减少cell中控件的数量
(5)不要使用clearColor,也不要设置透明度为0
(6)刷新时尽量使用局部刷新
(7)加载网络数据时,下载图片时,使用异步加载,同时进行缓存
(8)尽量避免给cell动态添加view
(9)不要实现没用的代理方法
(10)行高问题,如果是固定的就使用预估行高写死,如果不是固定,就使用一个字典缓存一下
(11)不要做多余的绘制工作
(12)预渲染图片
(13)使用正确的数据结构来保存数据
(1)c可以直接跟v通讯
(2)c可以直接跟m进行通讯
(3)M与V避免直接通讯
(4)v跟c要通过间接方式通讯
(5)M跟c要通过间接方式通讯
不准
不准的原因:
(1)默认创建的nstimer会添加到主线程的defaultModel下的runloop里,当有scrollview进行滑动时,runloop模式就会切换到UITrackingRunLoopMode,这是nstimer就无法得到回调,就不准了
精确的方法:
(1)默认在主线程创建时[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
(2)在子线程中进行NSTimer的操作,再在主线程中修改UI界面显示操作结果
(3)使用GCD
将编写的代码转化成机器码
使用了clang和LLVM的编译器
底层是一个哈希表
使用哈希表来实现key和value的映射和存储的
哈希原理:
散列表又称哈希表,是通过关键码值直接进行访问的数据结构,它通过把关键码值映射到一个表中的位置来访问记录,从而加快访问速度,这个函数称之为散列函数,存放记录的数组称之为散列表
哈希表本质是一个数组,数组中存放的是一个个的元素,每个元素是一个键值对
(1)函数指针在编译器就确定了 block在运行时才会确定
(2)block是一个对象 继承NSObject 函数指针只是一个指针 block可以接受消息
(3)block可以访问局部变量
mvc 模块化 以网路层举例 自己总结一下
//快速排序
void quickSort(int arr[], int left, int right){
if (left >= right) return;
int key = arr[left], begin = left, end = right;
while (begin < end) {
//找到比key小的数并且放到靠前的位置
while (begin<end && arr[end]>=key) end--;
if (begin < end) arr[begin] = arr[end];
//找到比key大的数并且放到靠后的位置
while (begin<end && arr[begin]<=key) begin++;
if (begin < end) arr[end] = arr[begin];
}
arr[begin] = key; //将key放到比key小的数后面 比key大的数前面
quickSort(arr, left, begin-1); //递归更小的范围 执行相同操作
quickSort(arr, begin+1, right);
}
#pragma mark - 选择排序
void select_sort(int arr[],int size)
{
int i=0,j=0;
int k=0;
for(i=0;i<size;i++){
k=i;
for(j=i+1;j<size;j++){
if(arr[k]>arr[j]){
k=j;
}
}
if(k!=i){
int tmp=arr[k];
arr[k]=arr[i];
arr[i]=tmp;
}
}
}
#pragma mark - 冒泡排序
void bubbleSort(int arr[],int size)
{
for(int i=0;i<size;i++)
{
// 第二层循环,随着外层循环次数的递增是递减的,因为排序一次,就已经把大的数放到后面了,就不需要再次排它了
for(int j=0;j<size-i-1;j++)
{
if(arr[j]>arr[j+1])
{
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
//调用方式
- (void)quickSortTest{
void(^arrPrintfBlock)(int a[]) = ^(int a[]){
for (int i = 0; i < 6; i++) {
printf("%d", a[i]);
}
};
int a[6] = {4, 1, 2, 3, 5, 6};
arrPrintfBlock(a);
quickSort(a, 0, 5);
arrPrintfBlock(a);
}
int binary_search(int arrays[],int result,int length){
int begin=0,end=length-1;
int mid=0;
while(begin<=end){
mid=(begin+end)/2;
if(arrays[mid]==result)break;
else if(arrays[mid]<result)begin=mid+1;
else if(arrays[mid]>result)end=mid-1;
}
return mid;
}
copy会在内存中从新开辟一个空间去存储针织指向部分的内容,是深拷贝。
strong不会在内存中开辟新空间存储指针指向的部分的内容,是浅拷贝
- (void)testCopy{
self.muStr = [NSMutableString stringWithFormat:@"4321"];
self.cStr = self.muStr;
self.sStr = self.muStr;
NSLog(@"muStr:%@, copyStr:%@, strongStr:%@", self.muStr, self.cStr, self.sStr); //打印结果 muStr:4321, copyStr:4321, strongStr:4321
[self.muStr replaceCharactersInRange:NSMakeRange(0, 1) withString:@"5"];
NSLog(@"muStr:%@, copyStr:%@, strongStr:%@", self.muStr, self.cStr, self.sStr); //打印结果 muStr:5321, copyStr:4321, strongStr:5321
}
(1)@synchronized 加锁 互斥锁,性能较差不推荐使用
1).加锁的代码尽量少
2).添加的OC对象必须在多个线程中都是同一对象
3).优点是不需要显式的创建锁对象,便可以实现锁的机制。
4). @synchronized块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。
//@synchronized
- (void)synchronizedLockTest{
__block int _tickets = 5;
void(^testBlock)() = ^(){
while (1) {
@synchronized(self) {
if (_tickets > 0) {
_tickets--;
NSLog(@"剩余票数= %d, Thread:%@",_tickets,[NSThread currentThread]);
} else {
NSLog(@"票卖完了 Thread:%@",[NSThread currentThread]);
break;
}
}
}
};
//线程1
dispatch_async(dispatch_get_main_queue(), ^{
testBlock();
});
//线程2
dispatch_async(dispatch_get_main_queue(), ^{
testBlock();
});
}
(2)NSLock
互斥锁 不能多次调用 lock方法,会造成死锁
//NSLock
- (void)NSLockTest{
NSLock *lock = [NSLock new];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程1 尝试加速ing...");
[lock lock];
sleep(3);//睡眠5秒
NSLog(@"线程1");
[lock unlock];
NSLog(@"线程1解锁成功");
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程2 尝试加速ing...");
BOOL x = [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];
if (x) {
NSLog(@"线程2");
[lock unlock];
}else{
NSLog(@"失败");
}
});
}
(3) NSRecursiveLock 递归锁
//NSRecursiveLock 递归锁 适合循环使用
- (void)NSRecursiveLockTest{
NSRecursiveLock *rLock = [NSRecursiveLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveBlock)(int);
RecursiveBlock = ^(int value) {
[rLock lock];
if (value > 0) {
NSLog(@"线程%d", value);
RecursiveBlock(value - 1);
}
[rLock unlock];
};
RecursiveBlock(4);
});
}
(4)NSCondition 条件锁
// NSCondition
- (void)NSConditionTest{
{
//线程等待2秒
NSCondition *cLock = [NSCondition new];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"start");
[cLock lock];
[cLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
NSLog(@"线程1");
[cLock unlock];
});
}
{
//唤醒一个线程
NSCondition *cLock = [NSCondition new];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"线程1加锁成功");
[cLock wait];
NSLog(@"线程1");
[cLock unlock];
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"线程2加锁成功");
[cLock wait];
NSLog(@"线程2");
[cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"唤醒一个等待的线程");
[cLock signal];
});
}
{
//唤醒所有线程
NSCondition *cLock = [NSCondition new];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"线程1加锁成功");
[cLock wait];
NSLog(@"线程1");
[cLock unlock];
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"线程2加锁成功");
[cLock wait];
NSLog(@"线程2");
[cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"唤醒所有等待的线程");
[cLock broadcast];
});
}
}
(5)dispatch_semaphore 信号量实现加锁(GCD)推荐使用
//dispatch_semaphore 信号量
- (void)semTest{
//dispatch_semaphore_create(1): 传入值必须 >=0, 若传入为 0 则阻塞线程并等待timeout,时间到后会执行其后的语句
//dispatch_semaphore_wait(signal, overTime):可以理解为 lock,会使得 signal 值 -1
//dispatch_semaphore_signal(signal):可以理解为 unlock,会使得 signal 值 +1
dispatch_semaphore_t signal = dispatch_semaphore_create(1); //传入值必须 >=0, 若传入为0则阻塞线程并等待timeout,时间到后会执行其后的语句
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程1 等待ing");
dispatch_semaphore_wait(signal, overTime); //signal 值 -1
NSLog(@"线程1");
dispatch_semaphore_signal(signal); //signal 值 +1
NSLog(@"线程1 发送信号");
NSLog(@"--------------------------------------------------------");
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程2 等待ing");
dispatch_semaphore_wait(signal, overTime);
NSLog(@"线程2");
dispatch_semaphore_signal(signal);
NSLog(@"线程2 发送信号");
});
}
(6)OSSpinLock (暂不建议使用,原因参见[这里](https://link.jianshu.com/?t=http://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/))性能最好 安全性不够高 不推荐使用
//OSSpinLock 自旋锁
- (void)OSSpinLockTest{
__block OSSpinLock oslock = OS_SPINLOCK_INIT;
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程1 准备上锁");
OSSpinLockLock(&oslock);
sleep(1);
NSLog(@"线程1");
OSSpinLockUnlock(&oslock);
NSLog(@"线程1 解锁成功");
NSLog(@"--------------------------------------------------------");
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程2 准备上锁");
OSSpinLockLock(&oslock);
NSLog(@"线程2");
OSSpinLockUnlock(&oslock);
NSLog(@"线程2 解锁成功");
});
}
(7)pthread_mutex 互斥锁 苹果在新系统中大量使用了该方式实现线程锁
互斥锁: 加锁后只能有一个线程访问该对象,后面的线程需要排队,并且 lock 和 unlock 是对应出现的,同一线程多次 lock 是不允许的
//pthread_mutex 互斥锁
- (void)pthreadMutexTest{
static pthread_mutex_t pLock;
pthread_mutex_init(&pLock, NULL);
//1.线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程1 准备上锁");
pthread_mutex_lock(&pLock);
sleep(1);
NSLog(@"线程1");
pthread_mutex_unlock(&pLock);
});
//1.线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程2 准备上锁");
pthread_mutex_lock(&pLock);
NSLog(@"线程2");
pthread_mutex_unlock(&pLock);
});
}
(8)pthread_mutex(recursive) 递归锁
递归锁允许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操作。
//pthread_mutex(recursive) 递归锁
- (void)pthreadRecursiveTest{
static pthread_mutex_t pLock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); //初始化attr并且给它赋予默认
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //设置锁类型,这边是设置为递归锁
pthread_mutex_init(&pLock, &attr);
pthread_mutexattr_destroy(&attr); //销毁一个属性对象,在重新进行初始化之前该结构不能重新使用
//1.线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveBlock)(int);
RecursiveBlock = ^(int value) {
pthread_mutex_lock(&pLock);
if (value > 0) {
NSLog(@"value: %d", value);
RecursiveBlock(value - 1);
}
pthread_mutex_unlock(&pLock);
};
RecursiveBlock(5);
});
}
(9)NSConditionLock 条件锁
//NSConditionLock 相比于 NSLock 多了个 condition 参数,我们可以理解为一个条件标示
- (void)ConditionLockTest{
//NSConditionLock 还可以实现任务之间的依赖 根据后面的Condition参数值
NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if([cLock tryLockWhenCondition:0]){
NSLog(@"线程1");
[cLock unlockWithCondition:1];
}else{
NSLog(@"失败");
}
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lockWhenCondition:3];
NSLog(@"线程2");
[cLock unlockWithCondition:2];
});
//线程3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lockWhenCondition:1];
NSLog(@"线程3");
[cLock unlockWithCondition:3];
});
}
自旋和互斥对比?
自旋锁适合任务量小的操作,类似do while 所有线程会等待上锁的线程执行任务 之后再执行任务 适合cpu较强的机器
互斥锁社会较大任务量的操作,所有线程会被休眠,等执行完之后在唤醒其他线程 适合任务量大的操作
使用以上锁需要注意哪些
互斥锁lock与unlock要成对出现 不能重复加锁 递归循环避免使用互斥锁
用C/OC/C++,任选其一,实现自旋或互斥?口述即可!
自旋锁:do while
互斥锁:获取当前所有线程 休眠除上锁线程以外的所有线程 等上锁线程任务执行完成后再唤醒之前休眠的线程
性能对比:
同步:多个任务情况下,一个任务A执行结束,才可以执行另一个任务B。只存在一个线程。
异步:多个任务情况下,一个任务A正在执行,同时可以执行另一个任务B。任务B不用等待任务A结束才执行。存在多条线程。
并行:多个线程同时执行多个任务
并发:是在同一个cpu上执行多个任务,只能在一条线程上不停的切换任务,比如任务A执行了20%,任务A停下里,线程让给任务B,任务执行了30%停下,再让任务A执行。这样我们用的时候,由于cpu处理速度快,你看起来好像是同时执行,其实不是的,同一时间只会执行单个任务。
串行:多个任务按先后顺序执行(可以是多个线程)
(1)单例生成了多个
(2)block循环引用
(3)delegate循环引用 使用weak即可
(4)NSTimer循环引用
(5)非OC对象内存处理 使用GPUImage 通讯录等部分需要注意内存处理要手动添加
(6)地图类处理 需要注意内存的正确释放,大体需要注意的有需在使用完毕时将地图、代理等滞空为nil,注意地图中标注(大头针)的复用,并且在使用完毕时清空标注数组等。
(7)循环次数非常多时 及时释放占用内存大的临时变量,减少内存占用峰值
若常用框架出现内存泄漏如何处理
使用instrument找出问题 通知相关人员及时处理 组内讨论顶一个临时解决方案
使用runtime替换方法用category替换方法 try catch NSException
(1)AOP 拦截系统交互事件
(2)Event Collector 埋点信息收集
(3)Event Cache 埋点缓存 (内存 硬盘)
(4)Event Upload 埋点信息上传 (内存中使用并发队列 硬盘中使用串行队列)
(1)查找当前类中有没有可以替代的方法 有就执行 没有继续执行下一步
(2)如果有可以相应对应消息的对象 就返回一个新的对象 没有就执行下一步
(3)把消息组装成一个nsinvocation 然后转发给一个对象 对象响应了就结束了 (如果没有生成nsinvocation对象 就会崩溃 错误信息unrecognized selector sent to instance)
apns推送过程: 设备获取deviceToken -> 发送给app服务器 -> app服务器打包消息信息发给apns服务器 -> apns服务器根据deviceToken发送给对应的设备
apns原理:系统级别的长链接
(1) 时间复杂度:
* 时间复杂度表达形式:T(n) = O(f(n))
* 作用:度量算法好坏 占用资源时间
* 定义:假设输入一个数n,根据n看整个函数的每一行会执行多少次运算 然后加起来 就是时间复杂度
* 类型(大O阶类型):
* 常数阶:O(1)
* 线性阶:(随着问题n的扩大,对应计算次数呈直线增长)for循环(一层)O(n)
* 平方阶:O(n^2)
* 立方阶:O(n^3)
* 指数阶:O(2^n)
* 对数阶:(幂)O(logn)
* nlogn阶:O(nlogn)
* 常用时间复杂度大小:O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
(2)空间复杂度:
* 表达形式:S(n) = O(f(n))
* 时间复杂度和空间复杂度可以相互影响 举例闰年的例子 可以直接按算法计算 可以存到一个数组里面直接读取
* 特定算法的空间复杂度:
* 递归算法:O(n)
* 栈(类似饭店点菜):
* 编译器管理(系统自己管理)
* 类似数据结构中的栈(先进后出)
* 存储局部变量、函数参数等
* 申请过程:
* 申请方式:系统自动分配
* 申请后响应:只要剩余栈区够大,就通过,不够就报栈溢出
* 申请大小限制:栈是向低地址拓展的连续内存,空间是确定的,并且较小,所以能申请的空间也较小
* 申请效率:栈区由系统控制,速度快
* 存储过程:
* 第一个进栈的是主函数的下一条指令,然后是参数,然后是局部变量,由右向左入栈,调用结束后,局部变量先出,然后是参数(先进后出),然后栈顶指针指向最开始的内存地址
* 运行时刻赋值,栈区读写速度快
* 堆(类似自助烤肉):
* 程序员管理,程序结束时程序也会释放那些忘记释放的部分
* 数据结构类似链表
* 存放程序员自己管理的对象
* 申请过程:
* 申请方式:程序员自己申请,需指明大小
* 申请后响应:遍历记录空闲地址的链表,查找出一个比所需控件大的堆结点,然后删除空闲地址链表中对应的记录,并将对应空间分配给程序,系统自动将刚才找到的空间中剩余的部分重新放入空闲地址链表中
* 申请大小限制:堆是向高地址拓展的不连续内存,堆区的大小受限于计算机虚拟内存的大小,所以可申请的空间较大
* 申请效率:堆区是程序员自己创建对象,速度较慢,且容易产生内存碎片
* 存储过程:
* 堆头部用一个字节存放堆大小,其余空间由程序员自己安排
* 运行时刻赋值,读写速度较栈区满
* 全局区:
* 编译器管理
* 存放全局变量和静态变量 有两块区域组成全局区(静态区),一块是存放未初始化的全局变量和静态变量,另一块是初始化完成的全局变量和静态变量,这两块区域是相邻的
* 文字常量区:
* 编译器管理
* 存放常量字符串
* 程序代码区:
* 编译器管理
* 存放函数的二进制代码
(1)路由
(2)做成pod私有库形式
(3)git子模块
(4)framework的方式
(5)MVVM相对于MVC也是解耦
* 命名的区别:
* 静态库:以.a和.framework为文件后缀名
* 动态库:以.tbd(之前叫.dylib)和.framework为文件后缀名
* 链接时区别:
* 静态库:链接时会被完整的复制到可执行文件中,被多次使用就有多个拷贝
* 动态库:链接时不复制,程序运行时由系统动态加载到内存,系统只加载一次,多个系统共用(如系统的UIKit.framework等),节省内存。
* 苹果不允许使用自己的动态库,否则有可能审核不过
* 静态库制作:分.a和.framework两种类型 两种的制作方式是类似的
* 创建cocoa touch static library项目
* 添加响应代码
* 选择公开的头文件 build phases -> copy files
* build settings -> Build Active Architecture Only修改为NO,使生成的静态库就支持所有设备的架构
* 编译 两次(generic ios device一次 然后选择一个模拟器)后生成两个.a文件
* 查看静态库支持的框架:lipo -info 静态库名字
* 合并两个版本的.a文件为最终支持模拟器和真机的.a文件:lipo -create 第一个.a文件的绝对路径 第二个.a文件的绝对路径 -output 最终的.a文件路径
* 将最终的.a文件和头文件放到一个文件夹里面放到使用静态库的工程里面,就可以使用了
* 动态库制作:使用cocoa touch framework可以制作动态库 过程类似静态库
* 使用cocoa touch framework制作动态库时 如果Mach-O Type选择static Library的话 最终制作出来的是静态库
* 实例对象:new出来的 实现了 接口和数据的分离 实例对象是数据保存者 类对象是接口持有者 实例对象的isa指针指向类对象
* 类对象:类对象是一个单例 类对象的isa指针指向元类 存储对象方法 对象方法在类对象中查找 实例对象对应的类就是类对象
* 元类:类对象isa指向元类 存储类方法 类对象对应的类就是元类 元类的父类跟类对象中的父类是相同的 元类isa指向根元类
* nsobject:NSObject的元类是它自己 它的父类是nil
* 成员变量:没有生成setter getter 在.m默认是私有的 子类不可访问 使用{}括起来的 成员变量包含属性 (runtime的函数可以打印所有的大括号内的和所有property)
* 属性:自动生成setter getter 可以在其他类中调用 可以使用点语法访问
* oc没有真正意义上的私有变量和方法 只要知道其名称就可通过runtime访问
* 网络七层(OSI七层模型):
* 物理层:位于 OSI 参考模型的最低层,它直接面向原始比特流的传输
* 数据链路层:数据链路层涉及相邻节点之间的可靠数据传输,数据链路层通过加强物理层传输原始比特的功能,使之对网络层表现为一条无错线路。为了能够实现相邻节点之间无差错的数据传送,数据链路层在数据传输过程中提供了确认、差错控制和流量控制等机制
* 网络层:网络中的两台计算机进行通信时,中间可能要经过许多中间结点甚至不同的通信子网。 网络层的任务就是在通信子网中选择一条合适的路径,使发送端传输层所传下来的数据能 够通过所选择的路径到达目的端
* 传输层:传输层是 OSI 七层模型中唯一负责端到端节点间数据传输和控制功能的层
* 会话层:会话层的功能是在两个节点间建立、维护和释放面向用户的连接
* 表示层:表示层以下的各层只关心可靠的数据传输,而表示层关心的是所传输数据的语法和语义
* 应用层:应用层是 OSI 参考模型的最高层,负责为用户的应用程序提供网络服务
* http是应用层 tcp是传输层 ip是网络层 http协议基于tcp链接 我们在传输数据时,可以只使用传输层(TCP/IP),但是那样的话,由于没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用应用层协议,应用层协议很多,有HTTP、FTP、TELNET等等,也可以自己定义应用层协议。WEB使用HTTP作传输层协议,以封装HTTP文本信息,然后使用 TCP/IP 做传输层协议将它发送到网络上。Socket是对 TCP/IP 协议的封装,Socket 本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP 协议
* TCP/IP模型
* 应用层
* 传输层
* 网络层
* 网络接口
* http与socket的区别
* HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。
* Socket 是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口
* socket服务端可以主动发送数据给客户端 http必须是请求才能收到数据
* socket
* 建立socket链接:
* 服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
* 客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
* 连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
* socket与tcp
* socket有可能是基于tcp也有可能是基于udp 看具体场景要求 根据tcp和udp的特点去区分
* tcp udp区别
* tcp面向连接、传输可靠、适合大数据流、速度慢、消耗资源多 (面向连接:tcp中数据必须由同一台主机发出)
* udp面向非连接、传输不可靠、适合小数据流、速度快 (面向非连接:只要知道接收端的ip和端口,且网络可达 就可以向接收端发送数据,但是如果一次有多个报文可读 就会乱套 )
* tcp三次握手 (防止客户端重复发送连接请求 实现连接和同步两种需求 我要跟你连接 咱们两个就需要同步)(我要跟你打电话->我现在可以跟你打电话 但是你现在能马上跟我打电话吗 -> 我现在可以马上跟你打电话 -> 电话通了 开始吧 )
* 客户端发送syn包给服务端 客户端进入syn_send状态
* 服务端收到syn包 然后服务端发送syn_ask包给客户端 服务端进入syn_recv状态
* 客服端收到syn_ask包 然后向服务端发送ask包 客户端和服务端都进入established状态
* 完成三次握手 tcp连接简历完成 开始传输数据
* tcp的四次挥手(服务端也可以先发)(我要挂电话了 -> 我知道 你先等会 我还没说完 -> 我说完了 你现在可以挂了 -> 行 那我挂了)
* 客户端发给服务端fin+ack
* 服务端发给客户端ack
* 服务端发送客户端fin+ack
* 客户端发给服务端ack
* ip mac:ip地址是用来在整个网络环境中找到那个子网使用的 mac是用来在子网中找到对应设备使用的
* dns服务器是用来将域名解析成ip地址的
* 子网掩码: 子网掩码又叫网络掩码、地址掩码、子网络遮罩,是一个 4 * 8bit(1字节)由 0/1 组成的数字串。
它的作用是屏蔽(遮住)IP地址的一部分以划分成网络地址和主机地址两部分,并说明该IP地址是在局域网上,还是在远程网上。
通过子网掩码,可以把网络划分成子网,即VLSM(可变长子网掩码),也可以把小的网络归并成大的网络即超网。
子网掩码不能单独存在,它必须结合IP地址一起使用。
* 网关:路由器的ip地址
* main之前:
* 主要工作:加载dylib和可执行文件
* 优化方法:
* 减少不必要的framework,因为动态链接比较耗时
* 移除不需要的类
* 合并类似的类和拓展
* 压缩资源图片
* 将不必须在+load方法中做的事情延迟到+initialize中
* 删减没有被调用到或者已经废弃的方法
* main之后:
* 主要工作:从main到applicationWillFinishLaunching执行完成的时间 显示首页
* 优化方法:
* 尽量使用纯代码实现UI
* 对某些可以延迟加载的业务进行延迟加载
* 减少启动时的网络请求
* cell重用:重用机制实现了数据和显示的分离,并不会为每个要显示的数据都创建一个Cell,一般情况下只创建屏幕可显示的最大的cell个数+1,每当有一个cell从屏幕消失,就将其放到缓存池中,如果有新的cell出现,就去缓存池中取,如果缓存池中没有,再创建。
* 列表优化
* 数据源避免使用时计算 提前在内存中缓存好
* cell重用
* 背景尽量不透明 复杂图层合成导致渲染成本高
* 控件要少
* 异步处理一些任务
* 按需加载cell
* 离屏渲染问题
* 不要动态的add 或者 remove 子控件
* CoreGraphic:c框架 偏向于使用cpu
* CoreAnimation:oc框架 cpu gpu智能选择 uiview基于它
使用父类的不同初始化方法生产出不同子类
原子性就是在读写属性时 通过加同步锁保证安全
* 进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元
* 同一个进程中可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文),一个进程至少包括一个线程
1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
2) 线程的划分尺度小于进程,使得多线程程序的并发性高。
3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
* 帮助理解编程思想
* 帮助理解方法内部的实现原理 能更高效率的使用
* 实现更高的效率
* iOS的基石
* ls -l :显示详细列表
* cd:目录转换
* pwd:示当前路径
* cat:显示文件内容
* rm:删除文件
* mkdir: 创建目录
* rmdir:删除目录
* cp:文档复制
* vi:编辑器
* shutdown:关闭系统
* date:用于显示或改变时间
* clear:消除终端屏幕
* 对于容器内包含容器的情况:使用归解档解决
* copy与mutableCopy实现是依据NSCopying和NSMutableCopying协议中的copyWithZone:和mutableCopyWithZone:方法。
* 首先,MRc时代用retain修饰block会产生崩溃,因为作为属性的block在初始化时是被存放在静态区的,如果block内调用外部变量,那么block无法保留其内存,在初始化的作用域内使用并不会有什么影响,但一旦出了block的初始化作用域,就会引起崩溃。所有MRC中使用copy修饰,将block拷贝到堆上。
其次,在ARC时代,因为ARC自动完成了对block的copy,所以修饰block用copy和strong都无所谓。
* instance只能作为返回值,不可以作为函数参数,id可以
* instance与当前类有链接 可以主动判断类,id不可以 不主动告诉程序类是什么 instanceType可以知道 就是使那些非关联返回类型的方法返回所在类的类型 能知道对象是哪个类的实例
* 单例模式:一个应用程序中,某个类的实例对象只有一个
* 观察者模式:对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
* 装饰者模式:对已有的业务逻辑进一步的封装,使其增加额外的功能
* 适配器模式:将两种完全不同的事物联系到一起,就像现实生活中的变压器。假设一个手机充电器需要的电压是20V,但是正常的电压是220V,这时候就需要一个变压器,将220V的电压转换成20V的电压,这样,变压器就将20V的电压和手机联系起来了。
* 工厂模式:简单工厂模式:一个抽象的接口,多个抽象接口的实现类,一个工厂类,用来实例化抽象的接口 (父类写需要实现的接口 子类去实现 实现不同类型的子类)
* 代理模式:找一个合适的对象解决问题
链表的创建、增、删、改、查
* 左移 << : 二进制向左移一位
* 右移 >>: 二进制向右移一位
* 按位或 |: 有1则1
* 按位与 & :有0则0 都1则1
* 按位取反 ~:0转1 1转0
* 按位异或 ^ : 不用为1 相同为0
* pthread
* 特点:
* 通用
* 使用难度大
* nsthread
* 特点:
* 面向对象
* 简单
* 直接操作线程对象
* 使用:
* 跟button添加点击事件类似 然后start
* 有直接创建完selector之后直接启动的方法
* 注意cancle和exit的区别,cancle是标记线程进入一个等待释放的状态,exit是释放除主线程之外的所有线程
* gcd
* 特点:
* 简单
* 充分利用多个核心性能
* nsoperation
* 特点:
* 面向对象
* 基于GCD
* 功能更多相比GCD
* 使用:
* 必须将任务封装到nsoperation的子类中
* 子类类型
* NSInvocationOperation
* 默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作 只有将NSOperation操作任务放到一个NSOperationQueue中,才会异步执行操作
* NSBlockOperation
* 只要NSBlockOperation封装的操作数 >1,就会异步执行操作
* 自定义子类继承NSOperation,实现内部相应main的方法封装操作
* 如果是自定义类继承于NSOperation, 那么需要将操作写到自定义类的main方法中,重写main方法
* 重写-(void)main方法的注意点
● 自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
● 经常通过-(BOOL)isCancelled方法检测操作是否被取消,对取消做出响应
* 首先错误信息里面会有布局重复的view,可以依据此把重复去除
* 可以设置期中一个view的布局优先级比其他的高 也可以解决布局重复的问题
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。