echo.hu


  • 首页

  • 关于

  • 归档
echo.hu

AFNetworking流程重新整理

发表于 2018-04-07 | | 阅读次数

AFNetworking生成 NSURLSessionDataTask 对象

生成请求request

1、根据URL生成请求对象。

2、如有设置请求属性则根据self.mutableObservedChangedKeyPaths里面的值会在初始化requestSerializer中相关的属性的时候通过kvo赋值或移除。

3、根据设置的不同的解析管理器调用不同的方法(解析器有json解析器、http解析器等解析器,这里以http解析器为例),在解析器中首先设置请求头参数,然后再如果有定义相关参数定义特殊处理代码块则进行相关的处理,再然后如果为get或者是head请求则拼接相关的字符串并赋值给mutableRequest.URL中,如果是post请求则把参数经过特殊处理变成字符串赋值到请求的头里面。

4、生成失败则直接回调失败代码块。

根据请求和相关参数生成 NSURLSessionDataTask 对象

1、在保证线程安全的前提下调用系统根据请求生成NSURLSession对象的方法。

2、用字典以AFURLSessionManagerTaskDelegate对象的方式保存相关的代码块。字典的key为taskIdentifier,并且添加开始请求和完成请求的相关通知。

AFNetworking数据解析

1、存储解析器的类型(解析器有json,http,xml等类型)。

2、建立局部数据存储,释放全局数据存储data。

3、如返回的网络连接发生错误则直接在主线程回调完成代码块并发送相关通知。

4、反则进行数据解析,先判断数据的有效性(主要判断是否存在可接受的数据类型、MIME类型是否存在、data的长度是否存在可接受的状态码,请求的URL是否存在),然后进行json解码把data变成相关的可解析字符串对象(如设置的是json数据格式)。

5、如是下载文件则把responseObject对象设置成下载文件的URL

6、然后再主线程回调完成代码块和发送完成请求的通知。

echo.hu

生产者消费者模式

发表于 2017-10-12 | | 阅读次数

生产者消费者模式

解决的问题:避免两个线程其中一个线程处理过快等待另外一个线程造成资源的浪费。

举个🌰

比如对图片的处理改变其冷暖色调时,往往用户可能需要进行多次频繁的参数调整,假如设置一次参数渲染图片的时间为1秒(当然肯定不会那么长时间,这是个🌰,这是个🌰),当用户拖动进度条从0拖到10时,处理的次数肯定超过十次,如果此时都在主线程处理,则处理时间肯定会超过十秒,这个很明显是不现实的,所以这个时候就需要生产者消费者模式了。

主线程作为->生产者:主要负责生产任务,即收集任务,比如上例中的渲染一次图片的任务。每次接收到任务时都把上一次的任务清空,直接处理最新数据,抛弃旧数据,让用户可以直接看到最新的处理结果。

异步全局线程作为->消费者:主要负责处理任务。消费者里面会有一个无限循环用于处理生产者生产的任务。

主要过程

主要的过程包括如下

1.初始化时全局异步线程处理代码块进入等待

2.主线程run,push代码块进入数组

3.激活全局异步线程对代码块进行处理

4.执行完pop出代码块,继续进入等待

echo.hu

AFNetworking内存泄露的问题

发表于 2017-03-30 | | 阅读次数

关于使用AFNetworking内存泄露的问题

这几天做完项目于是用instruments里面的Leaks检查了下内存情况,结果发现了一些AF里面的坑,AF Issues上面也有哥们提出问题来,不过也并没有解释清楚,所以特意自己重新看了遍源码整理了下.

下面是我报内存泄露的部分代码

1
2
3
4
5
6
7
8
9
10
11
12
- (instancetype)init
{
self = [super init];
if (self) {
self.manager=[AFHTTPSessionManager manager];
self.manager.requestSerializer=[AFJSONRequestSerializer serializer];
self.manager.requestSerializer.timeoutInterval=30;
self.manager.responseSerializer=[AFJSONResponseSerializer serializer];
self.manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"application/json"];
}
return self;
}

这里的manager方法并不是单例方法,也就是说一个manager对象会对应一个或多个NSURLSession对象,这样创建会造成内存增长,但是这样应该并不会造成内存泄露才对,况且我在dealloc方法里面已经把manager置空,于是我查找苹果官方文档,找到如下信息

Important

The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you do not invalidate the session, your app leaks memory until it exits.

大意如下:

会话对象保持对代理的强烈引用,直到您的应用程序退出或显式使会话无效。 如果您不使会话无效,您的应用程序会在内存溢出之前泄漏内存。

然后我在AF的源码里面的initWithSessionConfiguration方法找到如下代码

1
2
3
4
@property (readwrite, nonatomic, strong) NSURLSession *session;
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

那么答案就出来了session被AFNetworkReachabilityManager对象强引用了,session的委托又强持有了AFNetworkReachabilityManager对象,所以导致AFNetworkReachabilityManager对象置空后session依然存在,从而导致内存泄漏了

所以,正确的用法应该在AF上面再封装多一层把manager写成单例,降低内存峰值,代码如下:

1
2
3
4
5
6
7
8
9
10
11
+ (AFHTTPSessionManager *)sharedManager {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager=[AFHTTPSessionManager manager];
manager.requestSerializer=[AFJSONRequestSerializer serializer];
manager.requestSerializer.timeoutInterval=30;
manager.responseSerializer=[AFJSONResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"application/json"];
});
return manager;
}

使用则直接[封装类名 sharedManager] POST如此使用即可.

echo.hu

AFNetworking图片加载理解

发表于 2017-02-19 | | 阅读次数

先是UIImageView+AFNetworking分类

主要方法setImageWithURLRequest

1
2
3
4
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure

该方法主要做了如下几件事:

  • 如果请求的url为空则直接设置占位符图片为最终图片。

  • 当前的回调的request和需要请求的request是不是为同一个,是的话则判断为重复调用,直接返回。

  • 开始请求前,先取消之前的task,即解绑回调。

  • 从缓存中根据这个请求获取缓存,如果有缓存则直接回调成功方法块。(这里和后面设置从网络请求数据中设置的缓存策略相对应)

  • 无缓存有占位符图片则设置占位符图片。然后去下载图片,并得到一个receipt(AFImageDownloadReceipt对象),可以用来取消回调,分别传入成功回调方法块 (判断receiptID和downloadID是否相同 成功回调,设置图片,清空receipt) 和失败回调方法块 (清空receipt,回调上层失败方法块)

然后是AFImageDownloader类

这个类里面的方法主要负责从网络加载图片,当然如果本地有缓存也会优先获取缓存

主要方法downloadImageForURLRequest:

1
2
3
4
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
withReceiptID:(nonnull NSUUID *)receiptID
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure

该方法主要做了如下几件事(方法里面皆为同步串行处理):

  • 判断请求的url是否为空,如果为空则直接返回错误信息。

  • 判断这个任务是否已经存在,存在则添加成功失败Block,然后直接返回,即一个url用一个request,可以响应好几个block(防止重复请求)

  • 根据request的缓存策略,加载缓存,如果有缓存则直接加载成功方法块(缓存类AFAutoPurgingImageCache下面总结)

  • 用sessionManager(AFHTTPSessionManager)去请求,注意,只是创建task,还是挂起状态。回调处理方法块如下(方法里面皆为异步并行处理):
    • 请求完毕,安全移除AF task,并返回当前被移除的AF task
    • 如果请求失败则遍历AF task里面的失败方法,并在主线程里面回调
    • 如果请求成功则遍历AF task里面的成功方法,并在主线程里面回调
    • 回调完成,减少活跃的任务数,如果可以,则开启下一个任务
  • 根据createdTask任务创建mergedTask并往当前任务字典里添加任务

  • 判断当前并行限制数,如果小于,则开始任务下载resume,否则等待

  • 然后则回到AF里面的各种委托回调了

接下来是AFAutoPurgingImageCache类

主要方法是addImage,作用是用来添加image到cache里里面,同时这个方法会判断当前的缓存是否有溢出,如果有溢出则会根据时间的排序取出最早时候的缓存不断清除,知道到达没有溢出最大设置缓存为止。方法比较简单,直接点链接看吧!

echo.hu

runtime笔记

发表于 2017-02-11 | | 阅读次数

获取列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
-(void)runtimeLearn{
unsigned int count;
//获取属性列表
objc_property_t *propertyList = class_copyPropertyList([clsDogName class], &count);
for (unsigned int i=0; i<count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
}
//获取方法列表
Method *methodList = class_copyMethodList([clsDogName class], &count);
for (unsigned int i; i<count; i++) {
Method method = methodList[i];
NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
}
//获取成员变量列表
Ivar *ivarList = class_copyIvarList([clsDogName class], &count);
for (unsigned int i; i<count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
}
//获取协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([clsDogName class], &count);
for (unsigned int i; i<count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
}
}

解析各种奇怪数据的时候可以使用这些方法获取特定的方法名字或者属性名字来完成各种需求,包括MJExtension解析的主要实现方法都有使用到。

方法交换

1
2
3
Method originalMethod=class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod=class_getInstanceMethod([NSString class], @selector(eoc_myLowercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);

替换两个方法的实现达到输出测试结果的目的(作用范围,整个运行期),或者如AFNetworking中的替换掉NSUrlSession中的resume和suspend方法为自身逻辑服务.

关联对象方法的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-(void)loadAlertView{
UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"Question" message:@"2333" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Submit", nil];
void (^block)(NSInteger) = ^(NSInteger btnIndex){
if (btnIndex==0) {
NSLog(@"0000");
}else{
NSLog(@"other");
}
};
objc_setAssociatedObject(alert, EOCMYAlertViewKey, block, OBJC_ASSOCIATION_COPY);
[alert show];
}
#pragma mark - UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex NS_DEPRECATED_IOS(2_0, 9_0){
void (^block)(NSInteger)=objc_getAssociatedObject(alertView, EOCMYAlertViewKey);
block(buttonIndex);
}

使用关联对象方法可以把逻辑集中写在定义控件的地方使得逻辑不会过于分散.

拦截调用

程序执行中,如果没有找到方法就会转向拦截调用。拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject的四个方法来处理。

1
2
3
4
5
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//后两个方法需要转发到其他的类处理
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
  • 第一个方法是当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理然后返回YES。
  • 第二个方法和第一个方法相似,只不过处理的是实例方法。
  • 第三个方法是将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target。
  • 第四个方法是将你调用的不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。
echo.hu

多线程笔记

发表于 2017-02-09 | | 阅读次数

每次看完就忘,还是总结下.

线程的创建

两种线程的创建方法

1
2
3
4
5
//获取全局并行线程,第一个参数为优先级
self.syscQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//串行队列创建,当第二个参数为DISPATCH_QUEUE_CONCURRENT时创建的是并行队列,多个串行队列可以并列执行.
dispatch_queue_t serialQueue=dispatch_queue_create("com.zxd.hwd", DISPATCH_QUEUE_SERIAL);

异步dispatch_async

把两个任务添加到全局队列里面让他们并行运行,因为最后一句输出语句是在主线程运行的,所以会与两个任务的线程并行运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//并行队列,队列里面的东西运行时串行的,队列之间是并行运行的
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i=0; i<5; i++) {
NSLog(@"并行First task %d",i);
sleep(1);
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int j=0; j<5; j++) {
NSLog(@"并行Second task %d",j);
sleep(1);
}
});
NSLog(@"并行dispatch is over");
//串行队列
dispatch_queue_t serialQueue=dispatch_queue_create("com.zxd.hwd", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
for (int i=0; i<5; i++) {
NSLog(@"串行First task %d",i);
sleep(1);
}
});
dispatch_async(serialQueue, ^{
for (int j=0; j<5; j++) {
NSLog(@"串行Second task %d",j);
sleep(1);
}
});
NSLog(@"串行dispatch is over");

同步dispatch_sync

在全局线程上面同步运行,第一个任务执行完毕才开始执行第二个任务.

1
2
3
4
5
6
7
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"3333");
sleep(1);
});
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"2333");
});

dispatch_after

延迟一段时间把一项任务提交到队列中执行

1
2
3
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"延迟");
});

dispatch_apply

把一项任务提交到队列中多次执行,具体是并行执行还是串行执行由队列本身决定.注意,dispatch_apply不会立刻返回,在执行完毕后才会返回,是同步的调用.

1
2
3
4
5
6
7
8
9
10
NSArray *list=@[@"hello",@"hwd",@"hello world"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_apply(3, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
//相对主队列(主线程)是异步的,在global队列中是并行执行的
NSString *str=list[index];
NSLog(@"%lu",(unsigned long)str.length);
});
NSLog(@"Dispatch_after in global queue is over");
});
NSLog(@"Dispatch_after in main queue is over");

dispatch_once

1
2
3
4
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//保证在APP运行期间,block中的代码只执行一次的代碼
});

dispatch_group_t

想要在所以并行线程执行完毕后执行某个任务通常用到dispatch_group_t

1
2
3
4
5
6
7
8
9
10
11
12
13
dispatch_group_t hwdGroup=dispatch_group_create();
//全局队列,这个队列为并行队列
dispatch_queue_t globalQueueDefault=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//创建一个用户队列,这个队列为串行队列
dispatch_queue_t userCreateQueue=dispatch_queue_create("com.test.helloHwc",DISPATCH_QUEUE_SERIAL);
[self downLoadTask1:hwdGroup :globalQueueDefault];
[self downLoadTask2:hwdGroup :userCreateQueue];
[self downLoadTask3:hwdGroup :userCreateQueue];
dispatch_group_notify(hwdGroup, dispatch_get_main_queue(), ^{
NSLog(@"Group tasks are done");
});
NSLog(@"Now viewDidLoad is done");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-(void)downLoadTask1 :(dispatch_group_t)group :(dispatch_queue_t)queue{
dispatch_group_async(group, queue, ^{
sleep(3);
NSLog(@"Task1 is done");
});
}
-(void)downLoadTask2 :(dispatch_group_t)group :(dispatch_queue_t)queue{
dispatch_group_async(group, queue, ^{
sleep(3);
NSLog(@"Task2 is done");
});
}
-(void)downLoadTask3 :(dispatch_group_t)group :(dispatch_queue_t)queue{
dispatch_group_async(group, queue, ^{
sleep(3);
NSLog(@"Task3 is done");
});
}

dispatch_semaphore_create

通常用来控制并发线程的数量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//创建一个信号量dispatch_semaphore_create
//提高信号量dispatch_semaphore_signal
//等待降低信号量dispatch_semaphore_wait
self.semaphore=dispatch_semaphore_create(1);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self task_first];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self task_second];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self task_third];
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-(void)task_first{
//当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"First task starting");
sleep(1);
NSLog(@"First task is done");
//是发送一个信号,自然会让信号总量加1
dispatch_semaphore_signal(self.semaphore);
}
-(void)task_second{
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"Second task starting");
sleep(1);
NSLog(@"Second task is done");
dispatch_semaphore_signal(self.semaphore);
}
-(void)task_third{
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"Thrid task starting");
sleep(1);
NSLog(@"Thrid task is done");
dispatch_semaphore_signal(self.semaphore);
}

死锁

  • 死锁案例一
1
2
3
4
5
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3

因为任务2被同步添加进主线程,所以任务2需要等待任务3执行完毕后才能执行,而因为是同步任务3需要等待任务2执行完毕才能执行,所以造成主线程阻塞(死锁)。

  • 死锁案例二
1
2
3
4
5
6
7
8
9
10
11
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
while (1) {
}
NSLog(@"5"); // 任务5

控制台输出:1 4 ,1和4的顺序不一定

线程案例

  • 线程案例一
1
2
3
4
5
6
7
8
9
10
11
dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); // 任务1
//把任务加入到队列queue最后面
dispatch_async(queue, ^{
NSLog(@"2"); // 任务2
dispatch_sync(queue, ^{
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5

控制台输出:1 5 2 ,5和2的顺序不一定

  • 线程案例二
1
2
3
4
5
6
7
8
9
NSLog(@"1"); // 任务1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2"); // 任务2
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5

1最先执行;2和5顺序不一定;4一定在3后面

  • 不要使用dispatch_get_current_queue()获取当前线程
echo.hu

tips笔记总结

发表于 2017-01-10 | | 阅读次数

tableViewCell里面图片圆角离屏渲染解决方案

撰写一个UIImageView的分类,分类仿写SDWebImage里面加载图片的方法把图片加载下来后,通过另外一个UIImage分类用贝塞尔曲线切圆角处理,然后根据和SDWebImage的逻辑一样保留圆角图片缓存,清除原有非圆角图片缓存

字典深浅拷贝

1
2
3
4
NSMutableDictionary *myDic=[@{@"22":@"33"} mutableCopy];
NSMutableDictionary *otherDic=myDic;
//NSMutableDictionary *otherDic=[myDic mutableCopy];
[otherDic setValue:@"33" forKey:@"44"];

改变可变字典otherDic时myDic也会同时改变,所以使用可变字典时要让两个字典独立需要注意使用mutableCopy.

NSMutableSet里面尽量少用不可变对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
NSMutableSet *set=[NSMutableSet new];
NSMutableArray *arrayA=[@[@1,@2] mutableCopy];
[set addObject:arrayA];
NSLog(@"set=%@",set);
NSMutableArray *arrayB=[@[@1,@2] mutableCopy];
[set addObject:arrayB];
NSLog(@"set=%@",set);
NSMutableArray *arrayC=[@[@1] mutableCopy];
//[set addObject:[arrayC copy]];
[set addObject:arrayC];
NSLog(@"set=%@",set);
[arrayC addObject:@2];
NSLog(@"set=%@",set);
NSSet *setB=[set copy];
NSLog(@"setB=%@",setB);

如上面代码所示使用可变对象时会出现里面有相同元素的情况,而当重新将其浅拷贝的时候则里面内容会转回不可变对象,所以才会恢复正常.

打印输出黑科技

重写实体类如下方法然后就可以直接通过NSLog(@”cls=%@”,cls)输出定制的内容

1
2
3
4
5
6
7
8
9
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p, %@", [self class],self,@{@"youName":_youName, @"myName":_myName}];
}
//debug环境下
- (NSString *)debugDescription {
// return [NSString stringWithFormat:@"<%@: %p, %@", [self class],self,@{@"youName":_youName,
// @"myName":_myName}];
return [self description];
}

基于运行期OC的Get方法和Set方法的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//动态方法解析函数
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSString *selectorString=NSStringFromSelector(sel);
if ([selectorString hasPrefix:@"set"]) {
class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
}else{
class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
}
return YES;
}
id autoDictionaryGetter(id self,SEL _cmd){
EOCAutoDictionary *typeSelf=(EOCAutoDictionary *)self;
NSMutableDictionary *backingStore=typeSelf.backingStore;
NSString *key=NSStringFromSelector(_cmd);
return [backingStore objectForKey:key];
}
void autoDictionarySetter(id self,SEL _cmd,id value){
EOCAutoDictionary *typeSelf=(EOCAutoDictionary *)self;
NSMutableDictionary *backingStore=typeSelf.backingStore;
NSString *selectorString=NSStringFromSelector(_cmd);
NSMutableString *key=[selectorString mutableCopy];
[key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
[key deleteCharactersInRange:NSMakeRange(0, 3)];
NSString *lowercaseFirstChar=[[key substringToIndex:1] lowercaseString];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
if (value) {
[backingStore setObject:value forKey:key];
}else{
[backingStore removeObjectForKey:key];
}
}

当对象在收到无法解读的消息后,首先调用其所属类的下列类方法

+(BOOL)resolveInstanceMethod:(SEL)sel

所以可以利用其消息转发机制的这个函数来重新get,set方法,完整代码链接.

valueForKeyPath使用总结

取数组里面数字的最大值

1
2
3
NSArray * array = [NSArray arrayWithObjects:@0,@50,@9, nil];
NSInteger max = [[array valueForKeyPath:@"@max.floatValue"] integerValue];
NSLog(@"%ld",(long)max);

去数组里面数字的平均值

1
2
3
NSArray *arr = @[@0,@10,@40];
CGFloat avg = [[arr valueForKeyPath:@"@avg.floatValue"] floatValue];
NSLog(@"---%f",avg);

剔除重复数据

1
2
NSArray *array1 = @[@"name", @"w", @"aa", @"jimsa", @"aa"];
NSLog(@"%@", [array1 valueForKeyPath:@"@distinctUnionOfObjects.self"]);

同样可以嵌套使用,先剔除name对应值的重复数据再取值

1
2
3
4
5
6
NSArray *array2 = @[@{@"name" : @"cookeee",@"code" : @1},
@{@"name": @"jim",@"code" : @2},
@{@"name": @"jim",@"code" : @1},
@{@"name": @"jbos",@"code" : @1}];
NSLog(@"%@", [array2 valueForKeyPath:@"@distinctUnionOfObjects.name"]);

执行数组里面的特定属性处理完成后返回一个数组

1
2
NSArray *arrayList = @[@"name", @"w", @"aa", @"jimsa"];
NSLog(@"%@", [arrayList valueForKeyPath:@"uppercaseString"]);

求和、求平均值、最大值、最小值

1
2
3
4
NSNumber *sum = [array valueForKeyPath:@"@sum.floatValue"];
NSNumber *avg = [array valueForKeyPath:@"@avg.floatValue"];
NSNumber *max = [array valueForKeyPath:@"@max.floatValue"];
NSNumber *min = [array valueForKeyPath:@"@min.floatValue"];

KVO

添加kvo通知

1
[self.dogName addObserver:self forKeyPath:@"masterName" options:NSKeyValueObservingOptionNew context:nil];

在实体类中设置通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
//自动通知
if ([key isEqualToString:@"dogNameStr"]) {
return YES;
}
//手动通知
if ([key isEqualToString:@"masterName"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
-(void)setMasterName:(NSString *)pmasterName{
//设置手动通知
[self willChangeValueForKey:@"masterName"];
@synchronized(self) {
_masterName=pmasterName;
}
[self didChangeValueForKey:@"masterName"];
}

然后在需要通知的界面接收通知

1
2
3
4
5
6
7
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([object isKindOfClass:[clsDogName class]]) {
if ([keyPath isEqualToString:@"dogNameStr"]) {
self.lbDogName.text=[change objectForKey:NSKeyValueChangeNewKey];
}
}
}

主要用于比如在一个界面修改了用户的资料,按保存同时能实时的同步修改用户的资料这样类似的场景.

NSPredicate谓词筛选器

把时间小于2016-10-04的时候剔除,重复时间也剔除,然后排序,其他用法在这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-(void)loadPredicateTest{
//把DATETIME相同时间合并排序,然后再过滤掉2016-10-04号前的日期
NSArray *list=@[@{@"DATETIME":@"2016-10-01"},
@{@"DATETIME":@"2016-10-06"},
@{@"DATETIME":@"2016-10-05"},
@{@"DATETIME":@"2016-10-04"},
@{@"DATETIME":@"2016-10-05"},
@{@"DATETIME":@"2016-10-03"},
@{@"DATETIME":@"2016-10-02"},
@{@"DATETIME":@"2016-10-02"}];
//把日期过滤
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"DATETIME >= '2016-10-04'"];//创建谓词筛选器
NSArray *filtrationList = [list filteredArrayUsingPredicate:predicate];
//剔除重复数据
filtrationList=[filtrationList valueForKeyPath:@"@distinctUnionOfObjects.DATETIME"];
//排序
NSArray *sortSetArray = [filtrationList sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:nil ascending:NO]]];
NSLog(@"%@",sortSetArray);
}
echo.hu

AFNetworking流程总结

发表于 2016-11-08 | | 阅读次数

AFNetworking流程总结

一.调用POST或GET方法,并把成功、失败方法块传入.

二.调用dataTaskWithHTTPMethod方法返回NSURLSessionDataTask对象dataTask(下面是生成NSURLSessionDataTask对象的时候里面做的事情)

(1).根据POST或GET方法、URL、参数生成NSMutableURLRequest对象request

  • 判断参数有效与否

  • 根据URL和HTTPMethod方法类型初始化NSMutableURLRequest对象

  • self.mutableObservedChangedKeyPaths里面的值会在初始化requestSerializer中相关的属性的时候通过kvo赋值或移除

  • 根据初始化选择的可序列化的类型(json、html、xml)调用不同类的方法赋值给mutableRequest

(2).根据(1)中生成的request对象并传入上传进度、下载进度方法块生成NSURLSessionDataTask对象并传入回调方法块(回调方法块里面根据返回的参数选择调用成功或失败方法块)

  • 判断线程安全

  • 根据NSURLSessionDataTask对象dataTask绑定方法块(成功回调方法块、下载上传进度方法块)和委托,并添加相关通知如暂停任务和继续任务通知、添加相关任务属性kov的检测如countOfBytesReceived,countOfBytesExpectedToReceive属性等

三.调用dataTask对象的方法resume发送网络请求

四.然后从网络请求委托回调返回数据转发给AFURLSessionManagerTaskDelegate委托中的对应的方法

五.解析数据,然后从AFURLSessionManagerTaskDelegate中取出completionHandler方法块回调(解析数据是在另外开一个进程里面解析的)

echo.hu

查阅笔记

发表于 2016-10-19 | | 阅读次数

主要为了方便查阅容易忘掉的知识点

NSURLRequestCachePolicy 缓存策略

  • NSURLRequestUseProtocolCachePolicy = 0, 默认的缓存策略,如果缓存不存在,直接从服务端获取。如果缓存存在,会根据response中的Cache-Control字段判断下一步操作,如: Cache-Control字段为must-revalidata, 则询问服务端该数据是否有更新,无更新的话直接返回给用户缓存数据,若已更新,则请求服务端.
  • NSURLRequestReloadIgnoringLocalCacheData = 1, 忽略本地缓存数据,直接请求服务端.

  • NSURLRequestIgnoringLocalAndRemoteCacheData = 4, 忽略本地缓存,并且移除本地缓存,代理服务器以及其他中介,直接请求源服务端.

  • NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData

  • NSURLRequestReturnCacheDataElseLoad = 2, 有缓存就使用,不管其有效性(即忽略Cache-Control字段), 无则请求服务端.

  • NSURLRequestReturnCacheDataDontLoad = 3, 死活加载本地缓存. 没有就失败. (确定当前无网络时使用)

  • NSURLRequestReloadRevalidatingCacheData = 5, 缓存数据必须得得到服务端确认有效才使用(貌似是NSURLRequestUseProtocolCachePolicy中的一种情况)

echo.hu

echo.hu

9 日志
© 2021 echo.hu
由 Hexo 强力驱动
主题 - NexT.Pisces