iOS技术:https://www.tuicool.com/articles/RVvyuu2
iOS技术:https://www.tuicool.com/articles/mUzYr2z
怎么理解OC是动态语言,Runtime又是什么?
静态语言:如C语言,编译阶段就要决定调用哪个函数,如果函数未实现就会编译报错。
动态语言:如OC语言,编译阶段并不能决定真正调用哪个函数,只要函数声明过即使没有实现也不会报错。
我们常说OC是一门动态语言,就是因为它总是把一些决定性的工作从编译阶段推迟到运行时阶段。OC代码的运行不仅需要编译器,还需要运行时系统(Runtime Sytem)来执行编译后的代码。
Runtime是一套底层纯C语言API,OC代码最终都会被编译器转化为运行时代码,通过消息机制决定函数调用方式,这也是OC作为动态语言使用的基础。
1.动态方法交换示例
现在演示一个代码示例:在视图控制中,定义两个实例方法printA与printB,然后执行交换
- (void)printA{
NSLog(@"打印A......");
}
- (void)printB{
NSLog(@"打印B......");
}
//交换方法的实现,并测试打印
Method methodA = class_getInstanceMethod([self class], @selector(printA));
Method methodB = class_getInstanceMethod([self class], @selector(printB));
method_exchangeImplementations(methodA, methodB);
[self printA]; //打印B......
[self printB]; //打印A......
2.拦截并替换系统方法
Runtime动态方法交换更多的是应用于系统类库和第三方框架的方法替换。在不可见源码的情况下,我们可以借助Rutime交换方法实现,为原有方法添加额外功能,这在实际开发中具有十分重要的意义。
下面将展示一个拦截并替换系统方法的示例:为了实现不同机型上的字体都按照比例适配,我们可以拦截系统UIFont的systemFontOfSize方法,具体操作如下:
步骤1:在当前工程中添加UIFont的分类:UIFont +Adapt,并在其中添用以替换的方法。
+ (UIFont *)zs_systemFontOfSize:(CGFloat)fontSize{
//获取设备屏幕宽度,并计算出比例scale
CGFloat width = [[UIScreen mainScreen] bounds].size.width;
CGFloat scale = width/375.0;
//注意:由于方法交换,系统的方法名已变成了自定义的方法名,所以这里使用了
//自定义的方法名来获取UIFont
return [UIFont zs_systemFontOfSize:fontSize * scale];
}
步骤2:在UIFont的分类中拦截系统方法,将其替换为我们自定义的方法,代码如下:
//load方法不需要手动调用,iOS会在应用程序启动的时候自动调起load方法,而且执行时间较早,所以在此方法中执行交换操作比较合适。
+ (void)load{
//获取系统方法地址
Method sytemMethod = class_getClassMethod([UIFont class], @selector(systemFontOfSize:));
//获取自定义方法地址
Method customMethod = class_getClassMethod([UIFont class], @selector(zs_systemFontOfSize:));
//交换两个方法的实现
method_exchangeImplementations(sytemMethod, customMethod);
}
添加一段测试代码,切换不同的模拟器,观察在不同机型上文字的大小:
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 100, 300, 50)];
label.text = @"测试Runtime拦截方法";
label.font = [UIFont systemFontOfSize:20];
[self.view addSubview:label];
3.实现分类添加新属性
现在演示一个代码示例:为UIImage增加一个分类:UIImage+Tools,并为其设置关联属性urlString(图片网络链接属性),相关代码如下:
//UIImage+Tools.h文件中
UIImage+Tools.m
@interface UIImage (Tools)
//添加一个新属性:图片网络链接
@property(nonatomic,copy)NSString *urlString;
@end
//UIImage+Tools.m文件中
#import "UIImage+Tools.h"
#import <objc/runtime.h>
@implementation UIImage (Tools)
//set方法
- (void)setUrlString:(NSString *)urlString{
objc_setAssociatedObject(self,
@selector(urlString),
urlString,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//get方法
- (NSString *)urlString{
return objc_getAssociatedObject(self,
@selector(urlString));
}
//添加一个自定义方法,用于清除所有关联属性
- (void)clearAssociatedObjcet{
objc_removeAssociatedObjects(self);
}
@end
测试文件中:
UIImage *image = [[UIImage alloc] init];
image.urlString = @"http://www.image.png";
NSLog(@"获取关联属性:%@",image.urlString);
[image clearAssociatedObjcet];
NSLog(@"获取关联属性:%@",image.urlString);
//打印:
//获取关联属性:http://www.image.png
// 获取关联属性:(null)
iOS常用-锁
我们在使用多线程的时候多个线程可能会访问同一块资源,这样就很容易引发数据错乱和数据安全等问题,这时候就需要我们保证每次只有一个线程访问这一块资源,锁 应运而生。
@synchronized关键字加锁 互斥锁,性能较差不推荐使用
@synchronized(这里添加一个OC对象,一般使用self) {
这里写要加锁的代码
}
NSLock 互斥锁 不能多次调用 lock方法,会造成死锁
//创建锁
_mutexLock = [[NSLock alloc] init];
//加锁
[_mutexLock lock];
//这里写要加锁的代码
//解锁
[_mutexLock unlock];
RunLoop
RunLoop的实质是一个死循环,用于保证程序的持续运行,只有当程序退出的时候才会结束
1,保持程序的持续运行
2,处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
3,节省CPU资源,提高程序性能:该做事的时候做事,该休息的时候休息
RunLoop模式常用
NSDefaultRunLoopMode->默认模式,主线程中默认是NSDefaultRunLoopMode
UITrackingRunLoopMode->视图滚动模式,RunLoop会处于该模式下
kCFRunLoopCommonModes->占用,标签
NSRunLoopCommonModes = NSDefaultRunLoopMode && UITrackingRunLoopMode
runloop和线程的关系
主线程Runloop已经创建好了,子线程的runloop需要手动创建 ;一条线程对应一个RunLoop对象,每条线程都有唯一一个与之对应的RunLoop对象。一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出
runloop的实际使用
1。RunLoop运行模式对NSTimer定时器的影响。(解决方案可以设置模式kCFRunLoopCommonModes)
2。ImageView推迟显示-->实现图片加载性能优化
3。后台常驻线程(下载文件、后台播放音乐等)
iOS持久化的方式有什么
plist文件(属性列表)
preference(偏好设置):保存的位置是Library/Preferences 这个目录下的 plist 文件
NSKeyedArchiver(归档)
SQLite 3(FMDB)
CoreData
简述block
block是代码块,其本质和变量类似。不同的是代码块存储的数据是一个函数体。block使用最多的是存储代码块和回调。
1,使用copy关键字,将block从内存栈区移到堆区。是MRC遗留的产物
因为在MRC下Block创建的时候它的内存是分配在栈中的,它本身的作用域就是创建的时候的作用域,如果在此作用域外部调用block就会导致程序崩溃。因为栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续调用这个空对象就会导致程序崩溃,而对block进行copy,block的内存就会分配到堆区。在ARC下写不写都行,编译器会自动对block进行copy操作。
@property (copy , nonatomic) void (^touchXxxx) (void);
if (self.touchXxxx) {
self.touchXxxx();
}
MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);
ARC下block属性的建议写法(使用copy与strong其实都一样)
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
2,block访问外部变量,要用__block关键字https://www.jianshu.com/p/f63cffe2d089
__block int a = 10;
void(^myblock)(void) = ^(void) {
NSLog(@"a=%d",a);
};
a = 20;
myblock();//结果会发现打印的 a = 20
3,循环引用问题:就是两个对象相互持有,在释放时,相互等待释放,造成死循环谁都释放不了,从而内存泄露。block作为self的属性时,又在block内部调用了self的属性和方法,block和self相互持有,那么两者的引用计数都至少是1,都不会被释放。
所以使用weakSelf:这样在 Block 中就不会出现对 self 的强引用。
如果block内部需要访问self的方法、属性、或者实例变量应当使用weakSelf。
__weak typeof(self) weakSelf = self;
self.myblock = ^(int a) {
[weakSelf doSomething];
};
4,避免self在block里提前释放:比如说self在block里用了延迟执行
__strong typeof(self) strongSelf = weakSelf;
这句延长了weakSelf的生命周期,走完dispatch_after的代码才会释放掉,即延迟释放。
__weak typeof(self) weakSelf = self;
self.myblock = ^(int a) {
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongSelf doSomething];
});
};
self.myblock(1);
5,如果在block内需要多次访问self,则需要使用strongSelf
在 doSomething 中,weakSelf 不会变成 nil,不过在 doSomething 执行完成,调用第二个方法 doOtherThing 的时候,weakSelf 有可能被释放,
__weak typeof(self) weakSelf = self;
self.myblock = ^(int a) {
__strong typeof(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doOtherThing];
};
NSTimer或CADisplayLink不小心处理的话,极易造成循环引用
1、CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用
2、CADisplayLink、NSTimer 可能会不准时 (原因是 CADisplayLink、NSTimer 底层都是基于runloop CADisplayLink、NSTimer 在计时的时候是runloop跑一圈记一次 runloop在跑圈的时候 每次处理的事件可能会不同 因此消耗的时间也会不同 所以时间会出现误差 )
- (void)setupTimer {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
}
- (void)dealloc {
[self.timer invalidate];
}
使用带block的API。 不需要使用target,就不需要对控制器进行引用了。
不足:只有NStimer有带block的API,CADisplayLink是没有的。
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
// weakSelf.
}];
项目开发过程中,选择使用定时器时,建议选择GCD定时器,封装好的定时器,可以放到任何项目中使用(解决时间不准和循环引用问题)
SDwebImage加载图片的 原理
1、首先在webimagecache中寻找图片对应的缓存,它是以url为数据索引先在内存中查找是否有缓存;
2、如果没有缓存,就通过md5处理过的key来在磁盘中查找对应的数据,如果找到就会把磁盘中的数据加到内存中,并显示出来;
3、如果内存和磁盘中都没有找到,就会向远程服务器发送请求,开始下载图片;
4、下载完的图片加入缓存中,并写入到磁盘中;
5、整个获取图片的过程是在子线程中进行,在主线程中显示。
iOS图片使用SDWebImage多线程下载和缓存
无缓存情况下:
对同一个URL请求多次,返回的数据可能都是一样的,比如服务器上的某张图片,无论下载多少次,返回的数据都是一样的。
上面的情况会造成以下问题
(1)用户流量的浪费
(2)程序响应速度不够快
解决上面的问题,一般考虑对数据进行缓存。为了提高程序的响应速度,可以考虑使用缓存(内存缓存\硬盘缓存)
第一次请求数据时,内存缓存中没有数据,硬盘缓存中没有数据。缓存数据的过程
//图片缓存的基本代码,就是这么简单(这里把缓存很多的事情做完了)
[scell.imageView sd_setImageWithURL:imagePath1];
//图片缓存的基本代码,就是这么简单,设置占位图片
[cell.imageView sd_setImageWithURL:[NSURL URLWithString: imagePath1] placeholderImage:[UIImage imageNamed:@"placeholder"]];
批量下载1000张图片
SDWebImageDownloader提供了很多下载选项,可以根据情况进行配置。如设置下载优先级、进度、后台下载,图片缩放等,同时支持先进先出,先进后出的下载方式。
1.解压缩shouldDecompressImages:默认情况下,图片下载下来就解压缩,这是拿空间换时间的做法,让图片在使用时能更快的加载。
2.最大下载数maxConcurrentDownloads:此属性实质是设置下载队列(NSOperationQueue)的最大数并发数。
3.当前最大下载数currentDownloadCount:当前下载队列中未执行完毕的操作个数
4.下载超时时间downloadTimeout:默认设置为15秒
5.下载队列executionOrder:默认是先进先出,先到先下载的队列
这里我们主要就是使用了SDWebImagePrefetcher来下载图片,我们只需要将要下载的图片地址传入,SDWebImagePrefetcher就会帮我们将图片逐个下载下来,并且缓存在本地,缓存的方式和我们通常使用的动态加载图片一样以url为key存储
//图片缓存的基本代码,就是这么简单(这里把缓存很多的事情做完了)
[[SDWebImagePrefetcher sharedImagePrefetcher] prefetchURLs:prefetchURLs progress:^(NSUInteger noOfFinishedUrls, NSUInteger noOfTotalUrls) {
} completed:^(NSUInteger noOfFinishedUrls, NSUInteger noOfSkippedUrls) {
}];
当我们需要使用对应地址的图片时,我们只需要以url为key来获取图片即可
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:[NSURL URLWithString:imageURL]];
UIImage *image = [[SDImageCache sharedImageCache] imageFromCacheForKey:key];
return image;
批量上传1000张图片
https://www.jianshu.com/p/9e84fe63d5c0
GCD,NSOperationQueue
事实:GCD是面向底层的C语言的API, NSOperationQueue是基于GCD面向OC的封装
GCD:
一般的需求很简单的多线程操作,用GCD都可以了,简单高效。
1,GCD 栅栏方法:dispatch_barrier_async
在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作。
/**
* 栅栏方法 dispatch_barrier_async
*/
- (void)barrier {
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任务1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_barrier_async(queue, ^{
// 追加任务 barrier
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务4
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
}
});
}
2,GCD 延时执行方法:dispatch_after
/**
* 延时执行方法 dispatch_after
*/
- (void)after {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0秒后异步追加任务代码到主队列,并开始执行
NSLog(@"after---%@",[NSThread currentThread]); // 打印当前线程
});
}
3,一次性代码(只执行一次):dispatch_once
/**
* 一次性代码(只执行一次)dispatch_once 我们在创建单例、或者有整个程序运行过程中只执行一次的代码时
*/
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});
}
4,GCD的队列组 dispatch_group
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的操作
NSLog(@"耗时操作");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的操作
NSLog(@"耗时操作");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程...
});
NSOperationQueue:
各个操作之间有依赖关系、操作需要取消暂停、并发管理、控制操作之间优先级,限制同时能执行的线程数量.让线程在某时刻停止/继续等
1,控制最大并发数
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 设置最大并发操作数
// queue.maxConcurrentOperationCount = 2;
queue.maxConcurrentOperationCount = 1; // 就变成了串行队列
// 添加操作
[queue addOperationWithBlock:^{
NSLog(@"1-----%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.01];
}];
[queue addOperationWithBlock:^{
NSLog(@"2-----%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.01];
}];
2,最吸引人的地方是它能添加操作之间的依赖关系
比如说有A、B两个操作,其中A执行完操作,B才能执行操作,那么就需要让B依赖于A
//操作依赖
- (void)addDependency
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1-----%@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2-----%@", [NSThread currentThread]);
}];
[op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,在执行op2
[queue addOperation:op1];
[queue addOperation:op2];
}
//可以看到,无论运行几次,其结果都是op1先执行,op2后执行。
3,NSOperation、NSOperationQueue 线程间的通信
/**
* 线程间通信
*/
- (void)communication {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// 2.添加操作
[queue addOperationWithBlock:^{
// 异步进行耗时操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
// 回到主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 进行一些 UI 刷新等操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
}];
}
1.串行队列+同步任务:不会开启新的线程,任务逐步完成。
2.串行队列+异步任务:开启新的线程,任务逐步完成。
3.并发队列+同步任务:不会开启新的线程,任务逐步完成。
4.并发队列+异步任务:开启新的线程,任务同步完成。
我们如果要让任务在新的线程中完成,应该使用异步线程。为了提高效率,我们还应该将任务放在并发队列中。因此在开发中使用最多的是并发队列+异步任务
-(void)test7{
//并发同步--可以看到,程序没有并发执行,而且没有开启新线程,是在主线程执行的
NSLog(@"start");
dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1----:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2----:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3----:%@",[NSThread currentThread]);
}
});
NSLog(@"end");
}
2019-03-05 14:46:21.202900+0800 text[1765:21622] start
2019-03-05 14:46:21.203168+0800 text[1765:21622] 1----:<NSThread: 0x6000025baf40>{number = 1, name = main}
2019-03-05 14:46:21.203318+0800 text[1765:21622] 1----:<NSThread: 0x6000025baf40>{number = 1, name = main}
2019-03-05 14:46:21.203428+0800 text[1765:21622] 2----:<NSThread: 0x6000025baf40>{number = 1, name = main}
2019-03-05 14:46:21.203540+0800 text[1765:21622] 2----:<NSThread: 0x6000025baf40>{number = 1, name = main}
2019-03-05 14:46:21.203645+0800 text[1765:21622] 3----:<NSThread: 0x6000025baf40>{number = 1, name = main}
2019-03-05 14:46:21.203744+0800 text[1765:21622] 3----:<NSThread: 0x6000025baf40>{number = 1, name = main}
2019-03-05 14:46:21.203853+0800 text[1765:21622] end
-(void)test8{
//并发异步--可以看到,是并发执行的,而且开启了不止一个新线程。
NSLog(@"start");
dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1----:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2----:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3----:%@",[NSThread currentThread]);
}
});
NSLog(@"end");
}
2019-03-05 14:56:36.803048+0800 text[1989:26884] start
2019-03-05 14:56:36.803277+0800 text[1989:26884] end
2019-03-05 14:56:36.803387+0800 text[1989:26916] 1----:<NSThread: 0x600000074200>{number = 3, name = (null)}
2019-03-05 14:56:36.803543+0800 text[1989:26914] 2----:<NSThread: 0x600000076900>{number = 4, name = (null)}
2019-03-05 14:56:36.803635+0800 text[1989:26978] 3----:<NSThread: 0x600000084b40>{number = 5, name = (null)}
2019-03-05 14:56:36.803738+0800 text[1989:26916] 1----:<NSThread: 0x600000074200>{number = 3, name = (null)}
2019-03-05 14:56:36.803826+0800 text[1989:26914] 2----:<NSThread: 0x600000076900>{number = 4, name = (null)}
2019-03-05 14:56:36.803861+0800 text[1989:26978] 3----:<NSThread: 0x600000084b40>{number = 5, name = (null)}
-(void)test9{
//串行同步--可以看到是按顺输出的,是在同一个线程,但是没有开启新线程,是在主线程执行的
NSLog(@"start");
dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1----:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2----:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3----:%@",[NSThread currentThread]);
}
});
NSLog(@"end");
}
2019-03-05 14:57:38.184850+0800 text[2026:27582] start
2019-03-05 14:57:38.185092+0800 text[2026:27582] 1----:<NSThread: 0x600001069440>{number = 1, name = main}
2019-03-05 14:57:38.185215+0800 text[2026:27582] 1----:<NSThread: 0x600001069440>{number = 1, name = main}
2019-03-05 14:57:38.185318+0800 text[2026:27582] 2----:<NSThread: 0x600001069440>{number = 1, name = main}
2019-03-05 14:57:38.185480+0800 text[2026:27582] 2----:<NSThread: 0x600001069440>{number = 1, name = main}
2019-03-05 14:57:38.185587+0800 text[2026:27582] 3----:<NSThread: 0x600001069440>{number = 1, name = main}
2019-03-05 14:57:38.185686+0800 text[2026:27582] 3----:<NSThread: 0x600001069440>{number = 1, name = main}
2019-03-05 14:57:38.185791+0800 text[2026:27582] end
-(void)test10{
//串行异步--可以看到是按顺输出的,是在同一个线程,而且开启了新线程,
NSLog(@"start");
dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1----:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2----:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3----:%@",[NSThread currentThread]);
}
});
NSLog(@"end");
}
2019-03-05 14:58:50.021576+0800 text[2070:28513] start
2019-03-05 14:58:50.021959+0800 text[2070:28513] end
2019-03-05 14:58:50.022246+0800 text[2070:28543] 1----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
2019-03-05 14:58:50.022493+0800 text[2070:28543] 1----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
2019-03-05 14:58:50.022628+0800 text[2070:28543] 2----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
2019-03-05 14:58:50.022752+0800 text[2070:28543] 2----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
2019-03-05 14:58:50.022943+0800 text[2070:28543] 3----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
2019-03-05 14:58:50.023092+0800 text[2070:28543] 3----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
进程和线程的区别
一个程序至少有一个进程,一个进程至少有一个线程。
多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
KVC,KVO
KVC(Key-value coding)键值编码,我们可以通过以字符串为Key对对象属性进行操作。
Person *p = [[Person alloc] init];
// p.name = @"jack";
// p.money = 22.2;
// 使用setValue: forKey:方法能够给属性赋值,等价于直接给属性赋值
[p setValue:@"rose" forKey:@"name"];
[p setValue:@"22.2" forKey:@"money"];
KVO(Key-Value-Observer)键值观察,就是观察者模式。当一个对象被观察的属性发生变化时,观察者做出的处理
作用:能够监听某个对象属性值的改变
分类(category)
分类只能添加方法,不能添加属性
本类和分类的话,分类优先于本类的方法
可以在不修改原来类的基础上,为一个类扩展方法。
最主要的用法:给系统自带的类扩展方法。
类扩展(extension)
可以用来给当前类添加属性和新方法
代理(delegate)
代理模式是一种消息传递方式,一个完整的代理模式包括:委托对象、代理对象和协议。
深拷贝浅拷贝
浅拷贝:只拷贝内存地址,使原对象的引用计数+1,可以理解为创建了一个指向原对象的新指针而已,并没有创建一个全新的对象。
深拷贝:是真正的复制了一份,复制的对象指向了新的地址,拷贝对象的具体内容,而内存地址是自主分配的,两个对象虽然存的值是相同的,但是内存地址不一样,两个对象也互不影响,互不干涉。(这意味着我修改拷贝对象的值对源对象的值没有任何影响)
深拷贝和浅拷贝的本质是地址是否相同
copy还有它的特点:
修改源对象的属性和行为,不会影响副本对象
修改副本对象的属性和行为,不会影响源对象
NSString *str1 = @"str1";
NSString *str2 = [str1 copy];
str1 = @"asdf";
NSLog(@"\nstr1 = %@ str1P = %p \n str2 = %@ str2P = %p", str1, str1, str2, str2);
/*输出结果,修改str2 同理
str1 = asdf str1P = 0x10776b1a0
str2 = str1 str2P = 0x10776b180
*/
copy: 对于可变对象为深拷贝,对于不可变对象为浅拷贝
mutableCopy:始终是深拷贝
copy方法返回的对象都是不可变对象,所以copy修饰可变对象有时会崩溃
用copy还是strong关键字修饰一个属性时,与深拷贝浅拷贝不要混为一谈了,是两码事
例如dictionary用的最多
mutableCopy:比如后台给你的dic你想去做修改,那么就要把dic进行mutableCopy然后在传给你的下文进行处理。
copy:比如编辑表格的界面,你在做编辑功能的同时也要做取消修改功能。这时候就需要把原来生成表格的数据源dic复制一份以保留其初始数据。
怎么定位突发bug
对于致命的Bug,我们可以通过Crash日志进行分析,常用的有第三方友盟统计
多打印log
多添加关键断点
学会使用异常断点(Exception Breakpoint 当程序抛出异常的时候会触发断点。部分错误会断点在发生错误的代码。数组越界等会崩溃到main.m,不能定位到具体的代码,使用Exception Breakpoint就能定位到具体的代码。)
继承
当A类继承B类,A类就拥有B类中所有成员变量(属性)和方法。这也是继承的主要目的。
继承的好处:代码重用;
继承的缺点:父类的改变影响所有的子类。
苹果推送通知服务APNS
客户端:
客户端发送自身设备的 UDID 和 Bundle Identifier 给 APNs 服务器,经苹果服务器加密后生成 deviceToken,随后只需将用户的 deviceToken 发送服务器保存。
服务器:
服务器在需要给某个用户推送消息时,需要将消息内容和 deviceToken 一起发送给 APNs 服务器,苹果服务器对 deviceToken 解密后可以找到具体的设备,然后将消息推送给该用户。
降低耦合
耦合度的道理其实说起来很简单,就是模块之间相关联程度的度量,指模块与模块之间的关联性,所谓的低耦合就是将两个模块之间的关联性尽可能的降低,一个模块的改动对于其他模块的影响尽量小。
这样的话看起来很明了,平时简单的功能做起来也不难,比如一些简单的低耦合技巧:给tableViewCell赋值的时候,如果有dataSource,那么有些人会在tableView的代理中从dataSource取出需要的数据来赋值给cell,这样就增大了主视图的代码,增大了cell和主视图的联系,这时候就可以改为将dataSource里面的Model赋值给cell并重写setModel方法来实现低耦合。
1、少使用类的继承,多用接口隐藏实现的细节。
2、多用设计模式,比如采用MVC的设计模式就可以降低界面与业务逻辑的耦合度。
3 、iOS路由(MGJRouter) 跳转
4、把TableView从VC里抽离出来。
ARC基本原理
ARC的规则就是只要对象没有强指针引用,就会被释放掉,换而言之 只要还有一个强引用指针变量指向对象,那么这个对象就会存在内存中。弱指针指向的对象,会被自动变成空指针(nil指针),从而不会引发野指针错误。
ARC是Automatic Reference Counting(自动引用计数器)的简称
当一个对象被持有的时候计数加一,不再被持有的时候引用计数减一,当引用计数为零的时候,说明这个对象已经无用了,则将其释放。
内存溢出和内存泄露的区别
内存溢出:out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如说你申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存泄露: memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
memory leak会最终会导致out of memory!
堆和栈的区别
对于栈来讲,是由编译器自动管理,无需我们手工控制;
对于堆来说,释放工作由程序员控制,容易产生内存泄露(memory leak)
Block在没有使用外部变量时,内存存在全局区,然而当Block在使用外部变量的时候,内存是存在于栈区,当Block copy之后,是存在堆区的。存在于栈区的特点是对象随时有可能被销毁,一旦销毁在调用的时候,就会造成系统的崩溃。所以Block要用copy关键字。
循环引用的问题
系统自带的方法中的Block不会造成循环引用,比如UIView动画block、Masonry添加约束block、AFN网络请求回调block等;
但我们在实际开发中,使用自定义Block,在Block { xxx }中使用self,容易导致了循环引用 ,
循环引用导致的原因: 相互强指向;比如block为了保证代码块内部对象不被提前释放,会对block中的对象进行强引用,就相当于持有了其中的对象,而如果此时block中的对象又持有了该block,就会造成循环引用。
- (void)setUpModel {
XYModel *model = [XYModel new];
__weak typeof(self) weakSelf = self;
model.dataChanged = ^(NSString *title) {
__strong typeof(self) strongSelf = weakSelf;
strongSelf.titleLabel.text = title;
};
self.model = model;
}
TCP和UDP概念和区别
TCP是传输控制协议,提供的是面向连接、可靠的字节流服务。当客户的服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
UDP是用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序穿给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用再客户的服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
单例模式
单例模式是一种常用的设计模式,对于一个单例类,必须保证任意时刻只有一个单例对象,并且自行实例化该对象,并向整个系统提供该对象,也就是说无论实例化单例对象多少次,都只能创建出一个对象,该对象是全局的能够整个系统所访问
对于一些不需要频繁创建和销毁的对象,单例模式可以提高系统的性能。
//SingleClass.h文件
#import <Foundation/Foundation.h>
@interface SingleClass : NSObject
@property (copy, nonatomic)NSString *name;
+ (SingleClass *)sharedSingleClass;
@end
#import "SingleClass.h"
//1:创建一个全局静态的单例子对象指针,初始值为nil
static SingleClass *single = nil;
@implementation SingleClass
+ (SingleClass *)sharedSingleClass{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
single = [[SingleClass alloc] init];
});
return single;
}
@end
沙盒
1,Documents/
保存应用程序的重要数据文件和用户数据文件等。用户数据基本上都放在这个位置(例如从网上下载的图片或音乐文件),该文件夹在应用程序更新时会自动备份,在连接iTunes时也可以自动同步备份其中的数据。
2,Library:
这个目录下有两个子目录,可创建子文件夹。可以用来放置您希望被备份但不希望被用户看到的数据。该路径下的文件夹,除Caches以外,都会被iTunes备份.
Library/Caches:
保存应用程序使用时产生的支持文件和缓存文件(保存应用程序再次启动过程中需要的信息),还有日志文件最好也放在这个目录。iTunes 同步时不会备份该目录并且可能被其他工具清理掉其中的数据。
Library/Preferences:
保存应用程序的偏好设置文件。NSUserDefaults类创建的数据和plist文件都放在这里。会被iTunes备份。
3,tmp/:
保存应用运行时所需要的临时数据。不会被iTunes备份。iPhone重启时,会被清空。
UIView和CALayer是啥关系?
UIView 负责响应事件,CALayer 负责绘制 UI
UIView是CALayer的delegate
+load()方法
load方法在main函数前被调用
1.先调用类的 load 方法,先编译哪个类就先调用该类的 load.
2.在调用 load 之前调用父类 load 方法.
3.分类 load 方法不会覆盖本类的 load 方法.
+initialize()方法
初始化时会自动第一个调用的方法
4.initialize 方法先初始化父类,之后再初始化子类.
5.如果子类未实现 initialize 方法,就会调用父类的 initialize 方法.
6.如果分类实现了 initialize 方法,会覆盖本类 initialize 方法.
-(void)layoutSubviews 方法
必要时可以在子类中重写这个方法,以实现更加精细的子视图布局。只要子视图的自动尺寸调整和基于约束系统行为不能提供你想要的效果,你就应该重写这个方法,直接设置子视图的frame(来实现你想要的效果)
官方文档提到了layoutSubviews不是供用户来调用的,而是系统自动调用的,我们能做的就是重写该方法。
1、init初始化不会触发layoutSubviews。
2、addSubview会触发layoutSubviews。
3、当view的frame发生改变的时候触发layoutSubviews。
你不应该直接调用此方法,如果想强制更新布局,要调用 setNeedsLayout 方法,但该方法不会立即 重绘视图(drawing) 。如果想立即更新 视图布局(layout) ,请调用 layoutIfNeeded 方法。
layoutIfNeeded
如果强制刷新布局,请调用 setNeedsLayout
如果想马上刷新界面,请调用layoutIfNeeded
@IBAction func touchXxxBtn(_ sender: UIButton) {
self.view.layoutIfNeeded()
// 修改高度的约束。
if (self.orangeViewHeight.constant == 30) {
self.orangeViewHeight.constant = self.view.bounds.size.height - 100;
} else {
self.orangeViewHeight.constant = 30;
}
UIView.animate(withDuration: 2.0) {
self.view.layoutIfNeeded()
}
}
drawRect
iOS的绘图操作是在UIView类的drawRect方法中完成的,所以如果我们要想在一个UIView中绘图,需要写一个扩展UIView 的类,并重写drawRect方法,在这里进行绘图操作,程序会自动调用此方法进行绘图
ios 在执行main函数之前做了什么?
1.动态库链接库
2.ImageLoader加载可执行文件, 里边是被编译过的符号,代码等
3.runtime与+load
AFNetworking底层原理分析
AFNetworking主要是对NSURLSession的封装
简述Objective-C 消息发送与转发机制原理
消息发送:(Messaging)是 Runtime 通过 selector 快速查找 IMP 的过程,有了函数指针就可以执行对应的方法实现;
消息转发:(Message Forwarding)是在查找 IMP 失败后执行一系列转发流程的慢速通道,如果不作转发处理,则会打日志和抛出异常。
重构一个项目因该从哪些方面着手
1,代码规范,用统一的model层,网络层,
2,功能模块化,耦合性尽量低一些(有必要实现组件化开发)
3,整理资源文件
4,升级各种框架
实现组件化/插件化
组件化的实质:就是对现有项目或新项目进行基础、功能及业务逻辑的拆分,形成一个个的组件库,使宿主工程能在拆分的组件库里面查找需要的功能,组装成一个完整的App。
优点:组件可独立运行,提高的代码的复用性,组件化的颗粒度越细,可复用度就越高。当组件库的数量足够庞大时,项目只需要组合组件即可完成大部分的开发工作。组件化后项目的代码结构更加清晰,追踪问题、修复bug、增加需求更方便不同业务组件相互独立,明确团队开发的业务边界,增加团队协作效率
构建框架或者库该如何做要注意什么
抽象和封装,方便使用。少使用类的继承,多用接口隐藏实现的细节。比如构建一 个文件解压压缩框架,从使用者的角度出发,只需关注发送给框架一个解压请求,框架完成复杂文件的解压操作,如解压完成、解压出错等不需要知道。其次是要有API的说明文档。
UITableView 性能优化
1,最常用的就是cell的重用
注册重新标识符 如果是重用cell时,每当cell显示到屏幕上时,就会重新创建一个新的cell;如果有很多数据的时候,就会堆积很多cell。 如果重用cell,就为cell创建一个ID,每当需要显示cell 的时候,都会先去缓冲池中寻找可循环的cell,如果没有再重新创建cell.
2,加载比较多图片的时候
可以使用sdwebimage异步加载图片并缓存;或者使用runloop 让 ImageView推迟显示-->实现图片加载性能优化
3,提前计算并缓存cell的属性及内容
当我们创建cell的数据源方法时,编译器并不是先创建cell 再定cell的高度而是先根据内容一次确定每一个cell的高度,高度确定后,再创建要显示的cell,滚动时,每当cell进入凭虚都会计算高度,提前估算高度告诉编译器,编译器知道高度后,紧接着就会创建cell,这时再调用高度的具体计算方法,这样可以方式浪费时间去计算显示以外的cell
4,不要使用ClearColor,无背景色,透明度也不要设置为0 渲染耗时比较长
cell的复用
缓存cell的高
下载图片,使用异步加载,并缓存(SDwebImage)
Socket(都能实现双向通信)
Socket翻译过来中文称为套接字
网络上两个程序通过一个双向通信连接实现数据交互,这种双向通信的连接叫做Socket(套接字)。
Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。
websocket(都能实现双向通信)
WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信。
使用WebSocket让我们app端和服务器建立长连接。这样就可以事时接受他发过来的消息
需要跟服务端约定心跳,并且约定发送的数据,即需要每隔几秒告诉服务端,客户端在线,并且有新的数据推给客户端;
(1)WebSocket是一种协议,而Socket是一组接口;
(2)WebSocket是应用层协议,而Socket是位于传输层与应用层之间的抽象层;
(3)WebSocket在传输数据之前需要进行握手操作,而Socket不需要。
MQTT
MQTT,主要提供了订阅/发布两种消息模式,更为简约、轻量,易于使用,特别适合于受限环境(带宽低、网络延迟高、网络通信不稳定)的消息分发,属于物联网(Internet of Thing)的一个标准传输协议。(MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。)
instrument性能优化
其中用到的有:Leaks(内存检测,内存泄漏检测工具。)
由于 Leaks 是动态监测,所以我们需要手动操作 APP,进行测试,一边操作 APP,一边观察 Leaks 的变化,如果内存图中下方显示出红色叉号则代表此处存在内存泄漏。可以通过鼠标在图中圈出此区域,此时下面的CallTree就会打印出对应的函数堆栈调用以及所占内存大小信息,双击便可进入到对应的代码区域来查看。如下图: