OC:分类(好处,和延展的区别)
分类:
在不修改原有的类的基础上增加新的方法
一个庞大的类可以分模块开发
一个庞大的类可以由多个人来编写,更有利于团队合作
分类是对原有类的一种扩展,在分类里可以给原类添加方法,但是不能添加属性
延展(扩展)
是一种匿名的分类,即:分类名为空,在延展中可以给类扩展方法和属性,
这些方法和属性是相对私有的,不能被继承。相对私有:调用头文件即可使用。
数据解析
网络上传输数据通用的有XML,JSON等,iOS中也可以用Plist。
要进行数据传输,就要首先进行序列化:
1.序列化.
对象转换成二进制流.(这个一句话就行)
2.反序列化.
二进制流转换为对象等. (关键要弄清楚这个)
JSON:(和XML一样都是用来传数据的)
轻量级的数据交换格式,正在逐步取代XML.
XML:
结构性的标记语言,易读.但数据量大.
Plist偶尔用着玩玩:
Mac、iOS中用的多一种格式。
一、应用场景
1、XML的应用场景:
XMPP——即时通讯,KissXML
RSS目前还有少量的企业在使用
开源的WebServices,例如天气预报等
如果设计好XML的接口,XML的解析并不会太复杂
2、JSON的应用场景:(数据量小,轻量级)
移动开发中绝大多数还是使用JSON
如果自己开发,或者公司后台接口,最好使用JSON.
加载本地数据
NSData *da = [NSData dataWithContentsOfFile:[[NSBundle mainBundle]pathForResource:@"product.json" ofType:nil]];
Plist解析数据
定义一个Plist的格式如下:
• - (void)loadData
• {
• // 1. url
• NSURL *url = [NSURL URLWithString:@"http://localhost/videos.plist"];
5
1. // 2. request
2. // timeoutInterval 如果5.0之内没有从服务器返回结果,就认为超时了
3. /**
4. NSURLRequestUseProtocolCachePolicy = 0, // 使用协议缓存策略(默认)
5. NSURLRequestReloadIgnoringLocalCacheData = 1, // 忽略本地缓存数据(断点续传时使用)
6. NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData, == 1
12
1. // 以下少用
2. NSURLRequestReturnCacheDataElseLoad = 2, // 如果有缓存,就返回缓存数据,否则加载
3. NSURLRequestReturnCacheDataDontLoad = 3, // 死活不加载远程服务器数据,如果用户没有网络连接时可以使用
16
1. // 以下没有实现
2. NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // 没有实现
3. NSURLRequestReloadRevalidatingCacheData = 5, // 没有实现
4. */
5. NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:5.0];
22
1. // 3. 网络异步请求
2. [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
25
1. if (connectionError) {
2. NSLog(@"错误 %@", connectionError);
3. return;
4. }
30
1. // data是一个plist数据, 对data进行反序列化,解析
2. NSArray *array = [NSPropertyListSerialization propertyListWithData:data options:0 format:NULL error:NULL];
33
1. // 刷新数据,在UI线程中更新UI
2. dispatch_async(dispatch_get_main_queue(), ^{
3. //.....
4. });
5. }];
iOS中XML解析方式的两种方式(Android中好友pull等) :
1、SAX(SimpleAPI for XML)
只能读,不能修改,只能顺序访问,适合解析大型XML,解析速度快
常应用于处理大量数据的XML,实现异构系统的数据访问,实现跨平台
从文档的开始通过每一节点移动,定位一个特定的节点
2、DOM(DocumentObject Model)
不仅能读,还能修改,而且能够实现随机访问,缺点是解析速度慢,适合解析小型文档.方便操作.
在内存中生成节点树操作代价昂贵
XML解析步骤:
1、实例化NSXMLParser,传入从服务器接收的XML数据
2、定义解析器代理
3、解析器解析,通过解析代理方法完成XML数据的解析。
解析XML用到的的代理方法:
1. 开始解析XML文档
2. (void)parserDidStartDocument:
3. 开始解析某个元素,会遍历整个XML,识别元素节点名称,如<video>开头
-(void)parser:didStartElement:namespaceURI:qualifiedName:attributes:
1. 文本节点,得到文本节点里存储的信息数据。 节点中的数据<video>XXXX</video>
2. (void)parser:foundCharacters:
3. 结束某个节点 如</vgyyyideo>开头
4. (void)parser:didEndElement:namespaceURI:qualifiedName:
注意:在解析过程中,2、3、4三个方法会不停的重复执行,直到遍历完成为止
5.解析XML文档结束
• (void)parserDidEndDocument:
6.解析出错
-(void)parser:parseErrorOccurred:
注意: 从网络上加装数据是不能用懒加载的.
XML文件格式如下:
[html] view plaincopy
1. <?xml version="1.0" encoding="utf-8"?>
2. <videos>
3. <video videoId="1">
4. <name>苍老师1</name>
5. <length>320</length>
6. <videoURL>/苍老师1.mp4</videoURL>
7. <imageURL>/苍老师1.png</imageURL>
8. <desc>学iOS找苍老师1</desc>
9. <teacher>苍老师111</teacher>
10. </video>
11
1. <video videoId="2">
2. <name>苍老师2</name>
3. <length>2708</length>
4. <videoURL>/苍老师2.mp4</videoURL>
5. <imageURL>/苍老师2.png</imageURL>
6. <desc>学iOS找苍老师2</desc>
7. <teacher>苍老师222</teacher>
8. </video>
9. </videos>
解析代码如下:
[objc] view plaincopy
• /** 加载数据 */
• - (void)loadData
• {
• // 1. url确定资源
• NSURL *url = [NSURL URLWithString:@"http://localhost/videos.xml"];
6
1. // 2. request建立请求
2. NSURLRequest *request = [NSURLRequest requestWithURL:url];
9
1. // 3. 发送异步请求,新建数据处理队列,待数据处理完成后,再更新UI
2. [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
12
1. // 1> 实例化XML解析器
2. NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
15
1. // 2> 设置解析器的代理
2. parser.delegate = self;
18
1. // 3> 开始解析
2. [parser parse];
3. }];
4. }
23
• #pragma mark - XML解析代理方法
• // 1. 开始解析文档
• - (void)parserDidStartDocument:(NSXMLParser *)parser
• {
• // 为了避免重复刷新数据,可以清空数组
29
1. }
31
1. // 2,3,4三个方法会循环被调用
2. // 2. 开始一个元素(节点)<xxx>
3. - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
4. {
5. if ([elementName isEqualToString:@"video"]) {
6. // 创建新的video对象
7. self.currentVideo = [[Video alloc] init];
39
1. // 使用KVC赋值
2. [self.currentVideo setValue:attributeDict[@"videoId"] forKeyPath:@"videoId"];
3. }
43
1. // 在开始第3个方法前,清空字符串内容
2. [self.elementM setString:@""];
3. }
47
1. // 3. 发现字符(节点中间内容)
2. - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
3. {
4. [self.elementM appendString:string];
5. }
53
1. // 4. 结束节点</xxx>
2. - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
3. {
4. if ([elementName isEqualToString:@"video"]) {
5. // 将当前正在解析的节点添加到数组
6. [self.videoList addObject:self.currentVideo];
7. } else if (![elementName isEqualToString:@"videos"]) {
8. [self.currentVideo setValue:self.elementM forKeyPath:elementName];
9. }
10. }
64
1. // 5. 结束文档解析
2. - (void)parserDidEndDocument:(NSXMLParser *)parser
3. {
4. NSLog(@"结束文档解析 %@", self.videoList);
5. NSLog(@"%@", [NSThread currentThread]);
6. dispatch_async(dispatch_get_main_queue(), ^{
7. //UI线程中刷新UI......
8. });
9. }
74
1. // 6. 在处理网络数据时,千万不要忘记出错处理
2. - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
3. {
4. NSLog(@"错误 %@", parseError);
5. }
JSON解析
解析出来的数据需要进行反序列化,方法如下:
1. SJSONSerialization JSONObjectWithData:data options:0 error:NULL];
JSON解析方便、且轻量级,特别对于手机端,节省流量,快。
JSON格式数据如下:
[javascript] view plaincopy
1. [
2. {"videoId":"1",
3. "name":"苍老师1",
4. "length":"320",
5. "videoURL":"\/苍老师1.mp4",
6. "imageURL":"\/苍老师1.png",
7. "desc":"学iOS找苍老师1",
8. "teacher":"苍老师111"},
9
1. {"videoId":"2",
2. "name":"苍老师2",
3. "length":"2708",
4. "videoURL":"\/苍老师2.mp4",
5. "imageURL":"\/苍老师2.png",
6. "desc":"学iOS找苍老师2",
7. "teacher":"苍老师222"
8. }
9. ]
解析代码如下:
[objc] view plaincopy
• - (void)loadData
• {
• // 1. url
• NSURL *url = [NSURL URLWithString:@"http://localhost/videos.json"];
5
1. // 2. request
2. NSURLRequest *request = [NSURLRequest requestWithURL:url];
8
1. // 3. 发送异步请求
2. [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
11
1. // data是一个json数据
2. // 对data进行反序列化,解析
3. NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
15
1. // 刷新数据,更新UI
2. dispatch_async(dispatch_get_main_queue(), ^{
3. //在主线程中更新UI......
4. });
5. }];
6. }
也可以用MJExtion[第三方]
内存
在Objc当中没有垃圾回收机制,而是使用引用计数来管理内存的释放,oc中有手动引用计数(MRC)和 自动引用计数(ARC)当oc中的对象使用alloc copy new 创建是引用计数就会加1,被retain一次引用计数也会加1,当被release 或 autorelease时 引用计数减1,当引用计数下降为0时,该对象的内存就会被释放,对象被销毁
在ARC中我们已经无需再手动管理内存的释放,ARC并不是oc的特性,而是xcode编译器的特性,xcode会自动帮我们加上release 或autorelease等代码
GCD的工作原理是:让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务。
一个任务可以是一个函数(function)或者是一个block。 GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节。
GCD中的FIFO队列称为dispatch queue,它可以保证先进来的任务先得到执行
dispatch queue分为下面三种:
1. Serial
又称为private dispatch queues,同时只执行一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。
1. Concurrent
又称为global dispatch queue,可以并发地执行多个任务,但是执行完成的顺序是随机的。
1. Main dispatch queue
它是全局可用的serial queue,它是在应用程序主线程上执行任务的
常用的方法
1—》》dispatch_async
为了避免界面在处理耗时的操作时卡死,比如读取网络数据,IO,数据库读写等,我们会在另外一个线程中处理这些操作,然后通知主线程更新界面。
1. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
2. // 耗时的操作
3. dispatch_async(dispatch_get_main_queue(), ^{
4. // 更新界面
5. });
6. });
例子
1. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
2. NSURL * url = [NSURL URLWithString:@"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"];
3. NSData * data = [[NSData alloc]initWithContentsOfURL:url];
4. UIImage *image = [[UIImage alloc]initWithData:data];
5. if (data != nil) {
6. dispatch_async(dispatch_get_main_queue(), ^{
7. self.imageView.image = image;
8. });
9. }
10. });
2—》》dispatch_group_async
dispatch_group_async 实现监听一组任务是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了
1. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2. dispatch_group_t group = dispatch_group_create();
3. dispatch_group_async(group, queue, ^{
注意:如果没有下边这句话,则这三个线程是随机执行的 但dispatch_group_notify一定是最后一个执行的
1. [NSThread sleepForTimeInterval:1];
2. NSLog(@"group1");
3. });
4. dispatch_group_async(group, queue, ^{
// 线程休眠2s
1. [NSThread sleepForTimeInterval:2];
2. NSLog(@"group2");
3. });
4. dispatch_group_async(group, queue, ^{
5. [NSThread sleepForTimeInterval:3];
6. NSLog(@"group3");
7. });
8. dispatch_group_notify(group, dispatch_get_main_queue(), ^{
9. NSLog(@"updateUi");
10. });
11. dispatch_release(group);
dispatch_group_async是异步的方法,运行后可以看到打印结果:
1. 2-09-25 16:04:16.737 gcdTest[43328:11303] group1
2. 2-09-25 16:04:17.738 gcdTest[43328:12a1b] group2
3. 2-09-25 16:04:18.738 gcdTest[43328:13003] group3
4. 2-09-25 16:04:18.739 gcdTest[43328:f803] updateUi
每个一秒打印一个,当第三个任务执行后,upadteUi被打印。
3—》》dispatch_barrier_async
dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
例子代码如下:
1. dispatch_queue_t queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT);
2. dispatch_async(queue, ^{
3. [NSThread sleepForTimeInterval:2];
4. NSLog(@"dispatch_async1");
5. });
6. dispatch_async(queue, ^{
7. [NSThread sleepForTimeInterval:4];
8. NSLog(@"dispatch_async2");
9. });
10. dispatch_barrier_async(queue, ^{
11. NSLog(@"dispatch_barrier_async");
12. [NSThread sleepForTimeInterval:4];
13
1. });
1. dispatch_async(queue, ^{
2. [NSThread sleepForTimeInterval:1];
3. NSLog(@"dispatch_async3");
4. });
打印结果:
1. 2-09-25 16:20:33.967 gcdTest[45547:11203] dispatch_async1
2. 2-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_async2
3. 2-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_barrier_async
4. 2-09-25 16:20:40.970 gcdTest[45547:11303] dispatch_async3
请注意执行的时间,可以看到执行的顺序如上所述。
4—》》dispatch_apply
执行某个代码片段N次。
dispatch_apply(5, globalQ, ^(size_t index) {
// 执行5次
});
###补充内容—-》》多线程编程之NSThread的使用 《《
iOS有三种多线程编程的技术,分别是:
1.、NSThread
1. , GCD 全称:Grand Central Dispatch
3、Cocoa NSOperation
这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的。
NSThread:
优点:NSThread 比其他两个轻量级
缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销
实现的技术:Cocoa threads POSIX threads Multiprocessing Services
【一般使用Cocoa threads 技术】
Cocoa operation
优点:不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上。
Cocoa operation 相关的类是 NSOperation ,NSOperationQueue。NSOperation是个抽象类,使用它必须用它的子类,可以实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。创建NSOperation子类的对象,把对象添加到NSOperationQueue队列里执行。
GCD
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。在iOS4.0开始之后才能使用。GCD是一个替代诸如NSThread的很高效和强大的技术。现在的iOS系统都升级到6了,所以不用担心该技术不能使用。
NSThread的使用
两种直接创建方式:
• (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument
(void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument
获取当前线程
NSThread *current = [NSThread currentThread];
获取主线程
NSThread *main = [NSThread mainThread];
暂停当前线程
// 暂停2s
[NSThread sleepForTimeInterval:2];
// 或者
1. Date *date = [NSDate dateWithTimeInterval:2 sinceDate:[NSDate date]];
[NSThread sleepUntilDate:date];
线程间的通信
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES];
在主线程上执行操作
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];
在当前线程执行操作
[self performSelector:@selector(run) withObject:nil];
隐式创建线程的方法
[self performSelectorInBackground:@selector(run) withObject:nil];
例子 1
1类方法
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];
// 调用完毕后,会马上创建并开启新线程
2实例方法
• Thread* myThread = [[NSThread alloc] initWithTarget:self
selector:@selector(doSomething:)
object:nil];
• 设置线程的优先级(0.0 - 1.0,1.0*)
myThread.threadPriority = 1; [这句话在线程开启前设置]
[myThread start];
参数的意义:
selector :线程执行的方法,这个selector只能有一个参数,而且不能有返回值。
target :selector消息发送的对象
argument:传输给target的唯一参数,也可以是nil
第一种方式会直接创建线程并且开始运行线程
第二种方式是先创建线程对象,然后再运行线程操作,在运行线程操作前可以设置线程的优先级等线程信息
不显式创建线程的方法:
用NSObject的类方法 performSelectorInBackground:withObject: 创建一个线程:
[Obj performSelectorInBackground:@selector(doSomething) withObject:nil];
例子 2
1. -(void)downloadImage:(NSString *) url{
2. NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]];
3. UIImage *image = [[UIImage alloc]initWithData:data];
4. if(image == nil){
5
1. }else{
2. [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];
3. }
4. }
10
1. -(void)updateUI:(UIImage*) image{
2. self.imageView.image = image;
3. }
14
15
• - (void)viewDidLoad
• {
• [super viewDidLoad];
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(downloadImage:) object:kURL];
1. [thread start];
2. }
例子 3
1. tickets = 100;
2. count = 0;
3. theLock = [[NSLock alloc] init];
4. // 锁对象 【NSLock NSCondition 都是用于加锁的】
5. ticketsCondition = [[NSCondition alloc] init];
6. ticketsThreadone = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
7. [ticketsThreadone setName:@"Thread-1"];
8. [ticketsThreadone start];
1. ticketsThreadtwo = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
2. [ticketsThreadtwo setName:@"Thread-2"];
3. [ticketsThreadtwo start];
4. self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
5. self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
6. self.window.rootViewController = self.viewController;
7. [self.window makeKeyAndVisible];
8. return YES;
9. }
22
• - (void)run{
• while (TRUE) {
• // 上锁
• // [ticketsCondition lock];
• [theLock lock];
• if(tickets >= 0){
• [NSThread sleepForTimeInterval:0.09];
• count = 100 - tickets;
• NSLog(@"当前票数是:%d,售出:%d,线程名:%@",tickets,count,[[NSThread currentThread] name]);
• tickets--;
• }else{
• break;
• }
• [theLock unlock];
• // [ticketsCondition unlock];
• }
• }
如果没有线程同步的lock,卖票数可能是-1.加上lock之后线程同步保证了数据的正确性。
上面例子我使用了两种锁,一种NSCondition ,一种是:NSLock。 NSCondition我已经注释了
例子 4
1. tickets = 100;
2. count = 0;
3. theLock = [[NSLock alloc] init];
4. // 锁对象
5. ticketsCondition = [[NSCondition alloc] init];
6. ticketsThreadone = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
7. [ticketsThreadone setName:@"Thread-1"];
8. [ticketsThreadone start];
9
1. ticketsThreadtwo = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
2. [ticketsThreadtwo setName:@"Thread-2"];
3. [ticketsThreadtwo start];
13
1. NSThread *ticketsThreadthree = [[NSThread alloc] initWithTarget:self selector:@selector(run3) object:nil];
2. [ticketsThreadthree setName:@"Thread-3"];
3. [ticketsThreadthree start];
4. self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
5. // Override point for customization after application launch.
6. self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
7. self.window.rootViewController = self.viewController;
8. [self.window makeKeyAndVisible];
9. return YES;
10. }
24
1. -(void)run3{
2. while (YES) {
3. [ticketsCondition lock];
4. [NSThread sleepForTimeInterval:3];
5. [ticketsCondition signal];
6. [ticketsCondition unlock];
7. }
8. }
33
• - (void)run{
• while (TRUE) {
• // 上锁
• [ticketsCondition lock];
• [ticketsCondition wait];
• if(tickets >= 0){
• [NSThread sleepForTimeInterval:0.09];
• count = 100 - tickets;
• NSLog(@"当前票数是:%d,售出:%d,线程名:%@",tickets,count,[[NSThread currentThread] name]);
• tickets--;
• }else{
• break;
• }
• [ticketsCondition unlock];
• }
• }
ï it是等待,我加了一个 线程3 去唤醒其他两个线程锁中的wait
其他同步
我们可以使用指令 @synchronized 来简化 NSLock的使用,这样我们就不必显示编写创建NSLock,加锁并解锁相关代码。
• (void)doSomeThing:(id)anObj
{
@synchronized(anObj)
{
// Everything between the braces is protected by the @synchronized directive.
}
}
NSOperationQueue的使用
// 创建一个操作队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
1. NSOperation添加到queue之后,通常短时间内就会得到运行。但是如果存在依赖,或者整个queue被暂停等原因,也可能需要等待。
当某个NSOperation对象依赖于其它NSOperation对象的完成时,就可以通过addDependency方法添加一个或者多个依赖的对象,只有所有依赖的对象都已经完成操作,当前NSOperation对象才会开始执行操作。另外,通过removeDependency方法来删除依赖对象。
1. peration2 addDependency:operation1];
依赖关系不局限于相同queue中的NSOperation对象,NSOperation对象会管理自己的依赖, 因此完全可以在不同的queue之间的NSOperation对象创建依赖关系
唯一的限制是不能创建环形依赖,比如A依赖B,B依赖A,这是错误的
1. 依赖关系会影响到NSOperation对象在queue中的执行顺序,看下面的例子
//没有设置依赖关系
1. BlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"执行第1次操作,线程:%@", [NSThread currentThread]);
}];
1. BlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"执行第2次操作,线程:%@", [NSThread currentThread]);
}];
// operation1依赖于operation2
1. peration1 addDependency:operation2];
[queue addOperation:operation1];
[queue addOperation:operation2];
/*
[依赖关系]
没有设置依赖关系
先执行operation1,再执行operation2
1. 依赖关系-->[operation1 addDependency:operation2];
先执行operation2,再执行operation1
5
对于添加到queue中的operations,它们的执行顺序取决于2点:
1.首先看看NSOperation是否已经准备好:是否准备好由对象的依赖关系确定
2.然后再根据所有NSOperation的相对优先级来确定。优先级等级则是operation对象本身的一个属性。默认所有operation都拥有“普通”优先级,不过可以通过setQueuePriority:方法来提升或降低operation对象的优先级。优先级只能应用于相同queue中的operations。如果应用有多个operation queue,每个queue的优先级等级是互相独立的。因此不同queue中的低优先级操作仍然可能比高优先级操作更早执行。
注意:优先级不能替代依赖关系,优先级只是对已经准备好的 operations确定执行顺序。先满足依赖关系,然后再根据优先级从所有准备好的操作中选择优先级最高的那个执行。
[设置队列的最大并发操作数量]
队列的最大并发操作数量,意思是队列中最多同时运行几条线程
虽然NSOperationQueue类设计用于并发执行Operations,你也可以强制单个queue一次只能执行一个Operation。setMaxConcurrentOperationCount:方法可以配置queue的最大并发操作数量。设为1就表示queue每次只能执行一个操作。不过operation执行的顺序仍然依赖于其它因素,比如operation是否准备好和operation的优先级等。因此串行化的operation queue并不等同于GCD中的串行dispatch queue
// 每次只能执行一个操作
queue.maxConcurrentOperationCount = 1;
// 或者这样写
[queue setMaxConcurrentOperationCount:1];
[取消Operations]
一旦添加到operation queue,queue就拥有了这个Operation对象并且不能被删除,唯一能做的事情是取消。你可以调用Operation对象的cancel方法取消单个操作,也可以调用operation queue的cancelAllOperations方法取消当前queue中的所有操作。
// 取消单个操作
[operation cancel];
// 取消queue中所有的操作
[queue cancelAllOperations];
[等待Options完成]
为了最佳的性能,你应该设计你的应用尽可能地异步操作,让应用在Operation正在执行时可以去处理其它事情。如果需要在当前线程中处理operation完成后的结果,可以使用NSOperation的waitUntilFinished方法阻塞当前线程,等待operation完成。通常我们应该避免编写这样的代码,阻塞当前线程可能是一种简便的解决方案,但是它引入了更多的串行代码,限制了整个应用的并发性,同时也降低了用户体验。绝对不要在应用主线程中等待一个Operation,只能在第二或次要线程中等待。阻塞主线程将导致应用无法响应用户事件,应用也将表现为无响应。
// 会阻塞当前线程,等到某个operation执行完毕
[operation waitUntilFinished];
除了等待单个Operation完成,你也可以同时等待一个queue中的所有操作,使用NSOperationQueue的waitUntilAllOperationsAreFinished方法。注意:在等待一个 queue时,应用的其它线程仍然可以往queue中添加Operation,因此可能会加长线程的等待时间。
// 阻塞当前线程,等待queue的所有操作执行完毕
[queue waitUntilAllOperationsAreFinished];
[暂停和继续queue]
如果你想临时暂停Operations的执行,可以使用queue的setSuspended:方法暂停queue。不过暂停一个queue不会导致正在执行的operation在任务中途暂停,只是简单地阻止调度新Operation执行。你可以在响应用户请求时,暂停一个queue来暂停等待中的任务。稍后根据用户的请求,可以再次调用setSuspended:方法继续queue中operation的执行
// 暂停queue
[queue setSuspended:YES];
// 继续queue
[queue setSuspended:NO];
*/
Block
block(原理,底层,作用。)代理循环引用(原因,解决)
作用:
Block可以作为函数参数或者函数的返回值,而本身又可以带输入参数或返回值
block,在多线程、异步任务,集合遍历,集合排序、动画专场用得多
block用来保存一段代码
反向(逆传)传值
回调函数
block的标志 :^
int val = 10;
void (^blk)(void) = ^{printf("val=%d\n",val);};
val = 2;
blk();
上面这段代码,输出值是:val = 10.而不是2.block截获自动变量的瞬时值。因为block保存了自动变量的值,所以在执行block语法后,即使改写block中使用的自动变量的值也不会影响block执行时自动变量的值。
尝试改写block中捕获的自动变量,将会是编译错误。我更喜欢把这个理解为:block捕获的自动变量都将转化为const类型。不可修改了 解决办法是将自动变量添加修饰符 __block
__block说明符
前面讲过block所在函数中的,捕获自动变量。但是不能修改它,不然就是编译错误。但是可以改变全局变量、静态变量、全局静态变量。 其实这两个特点不难理解:第一、为何不让修改变量:这个是编译器决定的。理论上当然可以修改变量了,只不过block捕获的是自动变量的副本,名字一样。为了不给开发者迷惑,干脆不让赋值。道理有点像:函数参数,要用指针,不然传递的是副本。 第二、可以修改静态变量的值。静态变量属于类的,不是某一个变量。所以block内部不用调用cself指针。所以block可以调用。 解决block不能保存值这一问题的另外一个办法是使用__block修饰符。
__block int val = 10;
void (^blk)(void) = ^{val = 1;};
block存储区域
这就需要引入三个名词: ● _NSConcretStackBlock ● _NSConcretGlobalBlock● _NSConcretMallocBlock
正如它们名字说的那样,说明了block的三种存储方式:栈、全局、堆
【要点1】如果是定义在函数外面的block是global的,另外如果函数内部的block但是,没有捕获任何自动变量,那么它也是全局的。比如下面这样的代码:
typedef int (^blk_t)(int);
for(...){
blk_t blk = ^(int count) {return count;};
}
虽然,这个block在循环内,但是blk的地址总是不变的。说明这个block在全局段。
【要点2】一种情况在非ARC下是无法编译的: typedef int(^blk_t)(int); blk_t func(int rate){ return ^(int count){return rate*count;} } 这是因为:block捕获了栈上的rate自动变量,此时rate已经变成了一个结构体,而block中拥有这个结构体的指针。即如果返回block的话就是返回局部变量的指针。而这一点恰是编译器已经断定了。在ARC下没有这个问题,是因为ARC使用了autorelease了。
【要点3】有时候我们需要调用block 的copy函数,将block拷贝到堆上。看下面的代码:
-(id) getBlockArray{
int val =10;
return [[NSArray alloc]initWithObjects:
^{NSLog(@"blk0:%d",val);},
^{NSLog(@"blk1:%d",val);},nil];
}
id obj = getBlockArray();
typedef void (^blk_t)(void);
blk_t blk = (blk_t){obj objectAtIndex:0};
blk();
这段代码在最后一行blk()会异常,因为数组中的block是栈上的。因为val是栈上的。解决办法就是调用copy方法。
【要点4】不管block配置在何处,用copy方法复制都不会引起任何问题。在ARC环境下,如果不确定是否要copy block尽管copy即可。ARC会打扫战场。
__block变量存储区域
当block被复制到堆上时,他所捕获的对象、变量也全部复制到堆上。 回忆一下block捕获自动变量的时候,自动变量将编程一个结构体,结构体中有一个字段叫__forwarding,用于指向自动这个结构体。那么有了这个__forwarding指针,无论是栈上的block还是被拷贝到堆上,那么都会正确的访问自动变量的值。
代理循环引用(原因,解决)
1. @interface BrushViewController : BaseViewController
2
1. @property (nonatomic, copy) void (^getCardInfo)(NSDictionary *cardInfo);
4
1. @end
block声明为copy的原因是在代码块里面可能会使用一些本地变量。而block一开始是放在栈上的,只有copy后才会放到堆上。
如果不加copy属性,当其所在栈被释放的时候,这些本地变量将变得不可访问。一旦代码执行到block这段就会导致bad access。
1. brush.getCardInfo=^(NSDictionary *info){
2. [self test];
3. };
像上面这段代码,self其实是一个本地变量而不是block内部变量,如果声明为assign,代码执行到block内部就会出错。
但是这又带来另一个问题,就是self的引用计数+1。这意味着很可能会导致循环引用。self持有brush,brush持有block,block持有self。结果就是内存泄漏。
解决的办法如下:
• __block CurrentViewController* blockSelf = self;
• brush.getCardInfo=^(NSDictionary *info){
• [blockSelf test];
• };
通过这个方式,告诉block这个变量的引用计数不要+1。
如果你使用的是ARC,那么就应该改成下面这样:
• __weak CurrentViewController* blockSelf = self;
• brush.getCardInfo=^(NSDictionary *info){
• [blockSelf test];
• };
Block对外部变量的存取管理
基本数据类型
1、局部变量
局部自动变量,在Block中只读。Block定义时copy变量的值,在Block中作为常量使用,所以即使变量的值在Block外改变,也不影响他在Block中的值。
{
int base = 100;
long (^sum)(int, int) = ^ long (int a, int b) {
return base + a + b;
};
base = 0;
printf("%ld\n",sum(1,2));
// 这里输出是103,而不是3, 因为块内base为拷贝的常量 100
}
2、STATIC修饰符的全局变量
因为全局变量或静态变量在内存中的地址是固定的,Block在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量.
{
static int base = 100;
long (^sum)(int, int) = ^ long (int a, int b) {
base++;
return base + a + b;
};
base = 0;
printf("%ld\n",sum(1,2));
// 这里输出是4,而不是103, 因为base被设置为了0
printf("%d\n", base);
// 这里输出1, 因为sum中将base++了
}
其他特性
1.栈和堆
以下情况中的block位于堆中:
1. void foo()
2. {
3. __block int i = 1024;
4. int j = 1;
5. void (^blk)(void);
6. void (^blkInHeap)(void);
7. blk = ^{ printf("%d, %d\n", i, j);};//blk在栈里
8. blkInHeap = Block_copy(blk);//blkInHeap在堆里
9. }
10
• - (void)fooBar
• {
• _oi = 1;
• OBJ1* oj = self;
• void (^oblk)(void) = ^{ printf("%d\n", oj.oi);};
• void (^oblkInHeap)(void) = [oblk copy];//oblkInHeap在堆中
• }
2.全局区
以下情况中的block位于全局区:
1. static int(^maxIntBlock)(int, int) = ^(int a, int b){return a>b?a:b;};
2. - (void)fooBar
3. {
4. int(^maxIntBlockCopied)(int, int) =[maxIntBlock copy];
5. }
6. void foo()
7. {
8. int(^maxIntBlockCopied)(int, int) = Block_copy(maxIntBlock);
9. }
需要注意的是,这里复制过后的block依旧位于全局区,实际上,复制操作是直接返回了原block对象。
1.复制的行为
对block调用复制,有以下几种情况:
1.对全局区的block调用copy,会返回原指针,并且这期间不处理任何东西(至少目前的内部实现是这样);
2.对栈上的block调用copy,每次会返回新复制到堆上的block的指针,同时,所有__block变量都会被复制至堆一份(多次拷贝,只会生成一份)。
3.对已经位于heap上的block,再次调用copy,只会增加block的引用计数。
为什么我们不讨论retian的行为?原因是并没有Block_retain()这样的函数,而且objc里面的retain消息发送给block对象后,其内部实现是什么都不做。
2.objc类中的block复制
objc类实例方法中的block如果被复制至heap,那么当前实例会被增加引用计数,当这个block被释放时,此实例会被减少引用计数。
但如果这个block没有使用当前实例的任何成员,那么当前实例不会被增加引用计数。这也是很自然的道理,我既然没有用到这个instance的任何东西,那么我干嘛要retian它?
我们要注意的一点是,我看到网上有很多人说block引起了实例与block之间的循环引用(retain-cycle),并且给出解决方案:不直接使用self而先将self赋值给一个临时变量,然后再使用这个临时变量。
但是,大家注意,我们一定要为这个临时变量增加__block标记(多谢第三篇文章回帖网友的提醒)。
关于Block,如果需要了解更多可以参考以下资料:http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/。
网络请求
网络编程:AFN、ASI的区别
最后是重点内容,也是面试时经常会问的问题,两者的区别:
一、底层实现
1> AFN的底层基于OC的NSURLConnection和NSURLSession
2> ASI的底层基于纯C语言的CFNetwork框架
3> ASI的运行性能 高于 AFN
二、对服务器返回的数据处理
1> ASI没有直接提供对服务器数据处理的方式,直接返回data【二进制类型】
2> AFN提供了多种对服务器数据处理的方式
• JSON处理
• XML处理
• 其他处理
三、监听请求的过程
1> AFN提供了success和failure两个block来监听请求的过程(只能监听成功和失败)
• success : 请求成功后调用
• failure : 请求失败后调用
2> ASI提供了3套方案,每一套方案都能监听请求的完整过程
(监听请求开始、接收到响应头信息、接受到具体数据、接受完毕、请求失败)
• 成为代理,遵守协议,实现协议中的代理方法
• 成为代理,不遵守协议,自定义代理方法
• 设置block
四、在文件下载和文件上传的使用难易度
1> AFN
• 不容易监听下载进度和上传进度
• 不容易实现断点续传
• 一般只用来下载不大的文件
2> ASI
• 非常容易实现下载和上传
• 非常容易监听下载进度和上传进度
• 非常容易实现断点续传
• 下载或大或小的文件都行
五、ASI提供了更多的实用功能
1> 控制圈圈要不要在请求过程中转
2> 可以轻松地设置请求之间的依赖:每一个请求都是一个NSOperation对象
3> 可以统一管理所有请求(还专门提供了一个叫做ASINetworkQueue来管理所有的请求对象)
• 暂停\恢复\取消所有的请求
• 监听整个队列中所有请求的下载进度和上传进度
以上就介绍了开发经验: 对AFN和ASI各自使用方法及区别的总结,包括了方面的内容,希望对IOS开发有兴趣的朋友有所帮助。
AFNetworking:
一、2大管理对象
1.AFHTTPRequestOperationManager
• 对NSURLConnection的封装
2.AFHTTPSessionManager
• 对NSURLSession的封装
二、AFHTTPRequestOperationManager的具体使用
1.创建管理者
AFHTTPRequestOperationManager \*mgr = [AFHTTPRequestOperationManager manager];
2.封装请求参数
NSMutableDictionary \*params = [NSMutableDictionary Dictionary];
params[@"username"] = @"哈哈";
params[@"pwd"] = @"123";
3.发送请求
NSString *url = @"http://localhost:8080/LWServer/login";
[mgr POST:url parameters:params
success:^(AFHTTPRequestOperation \*operation, id responseObject) {
// 请求成功的时候调用这个block
NSLog(@"请求成功---%@", responseObject);
} failure:^(AFHTTPRequestOperation \*operation, NSError *error) {
// 请求失败的时候调用调用这个block
NSLog(@"请求失败");
}];
// GET请求
[mgr GET:url parameters:params
success:^(AFHTTPRequestOperation \*operation, id responseObject) {
// 请求成功的时候调用这个block
NSLog(@"请求成功---%@", responseObject);
} failure:^(AFHTTPRequestOperation \*operation, NSError *error) {
// 请求失败的时候调用调用这个block
NSLog(@"请求失败");
}];
三、对服务器返回数据的解析
1.AFN可以自动对服务器返回的数据进行解析
• 默认将服务器返回的数据当做JSON来解析
2.设置对服务器返回数据的解析方式
1> 当做是JSON来解析(默认做法)
• mgr.responseSerializer = [AFJSONResponseSerializer serializer];
• responseObject的类型是NSDictionary或者NSArray
2> 当做是XML来解析
• mgr.responseSerializer = [AFXMLParserResponseSerializer serializer];
• responseObject的类型是NSXMLParser
3> 直接返回data
• 意思是:告诉AFN不要去解析服务器返回的数据,保持原来的data即可
• mgr.responseSerializer = [AFHTTPResponseSerializer serializer];
3.注意
• 服务器返回的数据一定要跟responseSerializer对得上
1> 服务器返回的是JSON数据
• AFJSONResponseSerializer
• AFHTTPResponseSerializer
2> 服务器返回的是XML数据
• AFXMLParserResponseSerializer
• AFHTTPResponseSerializer
3> 服务器返回的是其他数据
• AFHTTPResponseSerializer
ASIHTTPRequest
一、发送请求的2个对象
1.发送GET请求:ASIHttpRequest
2.发送POST请求:ASIFormDataRequest
• 设置参数
// 同一个key只对应1个参数值,适用于普通“单值参数”
• (void)setPostValue:(id <NSObject>)value forKey:(NSString \*)key
// 同一个key(同一个参数名),会对应多个参数值,适用于“多值参数”
• (void)addPostValue:(id <NSObject>)value forKey:(NSString \*)key
二、发送请求
1.同步请求
• startSynchronous
2.异步请求
• startAsynchronous
三、监听请求的过程
1.如何监听请求过程
1> 为代理,遵守<ASIHTTPRequestDelegate>协议,实现协议中的代理方法
request.delegate = self;
• (void)requestStarted:(ASIHTTPRequest *)request;
• (void)request:(ASIHTTPRequest *)request didReceiveResponseHeaders:(NSDictionary *)responseHeaders;
• (void)request:(ASIHTTPRequest *)request didReceiveData:(NSData *)data;
• (void)requestFinished:(ASIHTTPRequest *)request;
• (void)requestFailed:(ASIHTTPRequest *)request;
2> 成为代理,不遵守<ASIHTTPRequestDelegate>协议,自定义代理方法
request.delegate = self;
[request setDidStartSelector:@selector(start:)];
[request setDidFinishSelector:@selector(finish:)];
3> 设置block
[request setStartedBlock:^{
NSLog(@"setStartedBlock");
}];
[request setHeadersReceivedBlock:^(NSDictionary *responseHeaders) {
NSLog(@"setHeadersReceivedBlock--%@", responseHeaders);
}];
[request setDataReceivedBlock:^(NSData *data) {
NSLog(@"setDataReceivedBlock--%@", data);
}];
[request setCompletionBlock:^{
NSLog(@"setCompletionBlock");
}];
[request setFailedBlock:^{
NSLog(@"setFailedBlock");
}];
2.监听的使用注意
• 如果同时设置了block和实现了代理方法,请求过程中,block和代理方法都会调用
• 一般的调用顺序:代理方法 > block
3.如果实现了下面的代理方法,那么responseData\responseString就没有值
• (void)request:(ASIHTTPRequest *)request didReceiveData:(NSData *)data;
四、文件下载
1.一般的下载
1> 设置文件下载的保存路径
request.downloadDestinationPath = filepath;
2> 设置进度监听的代理(要想成为进度监听代理,最好遵守<ASIProgressDelegate>协议)
request.downloadProgressDelegate = self.progressView;
2.断点下载(断点续传)
1> 设置文件下载的临时路径
request.temporaryFileDownloadPath = tempFilepath;
2> 设置支持断点续传
request.allowResumeForFileDownloads = YES;
五、文件上传(设置文件参数)
1.如果知道文件路径,最好就用这个方法(因为简单)
// ASI内部会自动识别文件的MIMEType
[request setFile:file forKey:@"file"];
[request addFile:file forKey:@"file"];
[request setFile:file withFileName:@"basic.pptx" andContentType:@"application/vnd.openxmlformats-officedocument.presentationml.presentation" forKey:@"file"];
// .....
2.如果文件数据是动态产生的,就用这个方法(比如刚拍照完获得的图片数据)
[request setData:data withFileName:@"test.png" andContentType:@"image/png" forKey:@"file"];
六、ASIHttpRequest的常见用法
1.请求超时
@property (atomic, assign) NSTimeInterval timeOutSeconds;
2.获得错误信息
@property (atomic, retain) NSError *error;
3.获得响应数据
// 状态码
@property (atomic, assign,readonly) int responseStatusCode;
// 状态信息
@property (atomic, retain,readonly) NSString *responseStatusMessage;
// 服务器返回的具体数据(NSString格式)
• (NSString *)responseString;
// 服务器返回的具体数据(NSData格式)
• (NSData *)responseData;
UI:tableView循环利用(原因,流程)
在tableView当中,当cell从View中消失时,会被放入到tableView的缓存池当中,下一个cell出现是会从缓存池中拿出对应标识的cell使用,但是缓存池的cell包含着上一次的数据,所以会出现一些奇葩的显示
而想要解决这个办法,就是当cell出现是使用数据给其赋值,将数据和cell绑定,这样可以覆盖缓存池中cell的数据,当然也可以在其显示以前将其数据清空
手势
除了用 touchesBegan / touchesMoved / touchesEnded 这组方法来控制使用者的手指触控外,也可以用 UIGestureRecognizer 的衍生类別来进行判断。用 UIGestureRecognizer 的好处在于有现成的手势,开发者不用自己计算手指移动轨迹。
UIGestureRecognizer的衍生类別有以下几种:
• UITapGestureRecognizer(点一下)
• UIPinchGestureRecognizer (二指往內或往外拨动)
• UIRotationGestureRecognizer(旋转)
• UISwipeGestureRecognizer 滑动,快速移动)
• UIPanGestureRecognizer 拖移,慢速移动)
• UILongPressGestureRecognizer (长按)
// 定义一个 recognizer, 并加到需要偵測该手势的 UIView 元件上
• (void)viewDidLoad {
o SwipeGestureRecognizer* recognizer;
// handleSwipeFrom 是偵測到手势,所要呼叫的方法
recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:selfaction:@selector(handleSwipeFrom)];
// 不同的 Recognizer 有不同的实体变数
// 例如 SwipeGesture 可以指定方向
// 而 TapGesture 則可以指定次數
recognizer.direction = UISwipeGestureRecognizerDirectionUp
[self.view addGestureRecognizer:recognizer];
}
• (void)handleSwipeFrom:(UISwipeGestureRecognizer*)recognizer {
// 触发手勢事件后,在这里作些事情
// 底下是刪除手势的方法
[self.view removeGestureRecognizer:recognizer];
}
有些手势其实是互相关联的,例如 Tap 与 LongPress、Swipe与 Pan,或是 Tap 一次与Tap 兩次。当一個 UIView 同时添加兩个相关联的手势时,到底我这一下手指头按的要算是 Tap 还是 LongPress?如果照預设作法来看,只要「先滿足条件」的就会跳出并呼叫对应方法,举例来说,如果同时注册了 Pan 和 Swipe,只要手指头一移动就会触发 Pan 然后跳出,因而永远都不會发生 Swipe;单点与双点的情形也是一样,永远都只会触发单点,不會有双点。
那么这个问题有解吗?答案是肯定的,UIGestureRecognizer 有个方法叫做requireGestureRecognizerToFail,他可以指定某一个 recognizer,即便自己已经滿足條件了,也不會立刻触发,会等到该指定的 recognizer 确定失败之后才触发。以同时支持单点与双点的手势为例,代码如下:
• (void)viewDidLoad {
// 单击的 Recognizer
• TapGestureRecognizer* singleRecognizer;
singleRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:selfaction:@selector(handleSingleTapFrom)];
singleTapRecognizer.numberOfTapsRequired = 1; // 单击
[self.view addGestureRecognizer:singleRecognizer];
// 双击的 Recognizer
• TapGestureRecognizer* double;
doubleRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:selfaction:@selector(handleDoubleTapFrom)];
doubleTapRecognizer.numberOfTapsRequired = 2; // 双击
[self.view addGestureRecognizer:doubleRecognizer];
// 关键在这一行,如果双击确定偵測失败才會触发单击
[singleRecognizer requireGestureRecognizerToFail:doubleRecognizer];
[singleRecognizer release];
[doubleRecognizer release];
}
UIView动画
SDWebImage的底层。
先介绍一下SDWebImage,我们使用较多的是它提供的UIImageView分类,支持从远程服务器下载并缓存图片
SDWebImage是一个很厉害的图片缓存的框架。既ASIHttp+AsyncImage之后,我一直使用AFNetworking集成的UIImageView+AFNetworking.h,但后者对于图片的缓存实际应用的是NSURLCache自带的cache机制。而NSURLCache每次都要把缓存的raw data 再转化为UIImage,就带来了数据处理和内存方面的更多操作。具体的比较在这里。
IOS框架研究之SDWebImage的原理以及使用流程
SDWebImage
这个类库提供一个UIImageView类别以支持加载来自网络的远程图片。具有缓存管理、异步下载、同一个URL下载次数控制和优化等特征。
SDWebImage 支持异步的图片下载+缓存,提供了 UIImageView+WebCacha 的 category,方便使用。SDWebImage加载图片的流程:
1. 入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage显示,然后 SDWebImageManager 根据 URL 开始处理图片。
2. 进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载
queryDiskCacheForKey:delegate:userInfo:.
1. 先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,
SDImageCacheDelegate回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
1. SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache等前端展示图片。
2. 如果内存缓存中没有,生成 NSInvocationOperation添加到队列开始从硬盘查找图片是否已经缓存。
3. 根据 URLKey在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。
4. 如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate回调
imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。
1. 如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。
2. 共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。 10. 图片下载由 NSURLConnection来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。
什么时候使用自适应Cell
系统给出的的Cell属性不多,只有textLabel ,detailTextLabel ,imageView,accessoryView 这几个是常用的,但是当遇到一个Cell上要展示多个东西的时候就需要自定义Cell了,首先来一个类A 继承自uitableViewCell ,在这个类里边重写init方法,在这个方法里添加各种子控件,这时候我们需要再建一个类来专门设置各个子控件的frame,并在这个类里边计算好对应Cell的整体高度,然后将这个类导入Cell,并将frame赋给对应的子控件,最后要记得在tabeleView 的heightForRowAtIndexPath:方法赋给对应Cell高度。
textLabel
架构
写框架前 要仔细分析各个模块及控制器之间的跳转和依赖的关系,比如分析一下看看这几个控制之间有没有什么样的view是可以重用的,如果有的话,就不要在第一次使用的这个View是将其和控制器绑定在一块,可以自定义一个view,以备以后重用时简便,考虑以后需求修改的可能性,我感觉最好逻辑比较复杂又有tableview或者collectionView时都是用UIViewController作为底层控制器比较好,只是在上面添加你想用的到View即可,要不然以后遇到麻烦,呵呵,蛋碎一地
沙盒
在介绍各种存储方法之前,有必要说明以下沙盒机制。iOS程序默认情况下只能访问程序自己的目录,这个目录被称为“沙盒”。
1.结构
既然沙盒就是一个文件夹,那就看看里面有什么吧。沙盒的目录结构如下:
"应用程序包"
Documents
Library
Caches
Preferences
tmp
2.目录特性
"应用程序包": 这里面存放的是应用程序的源文件,包括资源文件和可执行文件。
NSString *path = [[NSBundle mainBundle] bundlePath];
NSLog(@"%@", path);
Documents: 最常用的目录,iTunes同步该应用时会同步此文件夹中的内容,适合存储重要数据。
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"%@", path);
Library/Caches: iTunes不会同步此文件夹,适合存储体积大,不需要备份的非重要数据。
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"%@", path);
Library/Preferences: iTunes同步该应用时会同步此文件夹中的内容,通常保存应用的设置信息。
tmp: iTunes不会同步此文件夹,系统可能在应用没运行时就删除该目录下的文件,所以此目录适合保存应用中的一些临时文件,用完就删除。
NSString *path = NSTemporaryDirectory();
NSLog(@"%@", path);
数据持久化
plist文件【属性列表】:存储字典或数组
NSKeyedArchiver【归档】 :存储对象,加密(一般用于个人账号信息的存储)
preference 偏好设置【NSUserDefault】:存储一些较小的设置信息。
Sqlite3、FMDB、Core Data :存储数据量较大的数据。
plist文件
plist文件是将某些特定的类,通过XML文件的方式保存在目录中。
可以被序列化的类型只有如下几种:
NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;
1.获得文件路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [path stringByAppendingPathComponent:@"123.plist"];
2.存储
NSArray *array = @[@"123", @"456", @"789"];
[array writeToFile:fileName atomically:YES];
3.读取
NSArray *result = [NSArray arrayWithContentsOfFile:fileName];
NSLog(@"%@", result);
4.注意
• 只有以上列出的类型才能使用plist文件存储。
• 存储时使用writeToFile: atomically:方法。 其中atomically表示是否需要先写入一个辅助文件,再把辅助文件拷贝到目标文件地址。这是更安全的写入文件方法,一般都写YES。
• 读取时使用arrayWithContentsOfFile:方法。
Preference
1.使用方法
//1.获得NSUserDefaults文件
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
//2.向文件中写入内容
[userDefaults setObject:@"AAA" forKey:@"a"];
[userDefaults setBool:YES forKey:@"sex"];
1. erDefaults setInteger:21 forKey:@"age"];
//2.1立即同步
[userDefaults synchronize];
//3.读取文件
NSString *name = [userDefaults objectForKey:@"a"];
BOOL sex = [userDefaults boolForKey:@"sex"];
NSInteger age = [userDefaults integerForKey:@"age"];
NSLog(@"%@, %d, %ld", name, sex, age);
2.注意
• 偏好设置是专门用来保存应用程序的配置信息的,一般不要在偏好设置中保存其他数据。
• 如果没有调用synchronize方法,系统会根据I/O情况不定时刻地保存到文件中。所以如果需要立即写入文件的就必须调用synchronize方法。
• 偏好设置会将所有数据保存到同一个文件中。即preference目录下的一个以此应用包名来命名的plist文件。
NSKeyedArchiver
归档在iOS中是另一种形式的序列化,只要遵循了NSCoding协议的对象都可以通过它实现序列化。由于决大多数支持存储数据的Foundation和Cocoa Touch类都遵循了NSCoding协议,因此,对于大多数类来说,归档相对而言还是比较容易实现的。
1.遵循NSCoding协议
NSCoding协议声明了两个方法,这两个方法都是必须实现的。一个用来说明如何将对象编码到归档中,另一个说明如何进行解档来获取一个新对象。
• 遵循协议和设置属性
//1.遵循NSCoding协议
@interface Person : NSObject //2.设置属性
@property (strong, nonatomic) UIImage *avatar;
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) NSInteger age;
@end
• 实现协议方法
//解档
• (id)initWithCoder:(NSCoder *)aDecoder {
if ([super init]) {
self.avatar = [aDecoder decodeObjectForKey:@"avatar"];
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
//归档
• (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.avatar forKey:@"avatar"];
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
}
• 特别注意
如果需要归档的类是某个自定义类的子类时,就需要在归档和解档之前先实现父类的归档和解档方法。即 [super encodeWithCoder:aCoder] 和 [super initWithCoder:aDecoder] 方法;
2.使用
需要把对象归档是调用NSKeyedArchiver的工厂方法 archiveRootObject: toFile: 方法。
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [[Person alloc] init];
person.avatar = self.avatarView.image;
person.name = self.nameField.text;
person.age = [self.ageField.text integerValue];
[NSKeyedArchiver archiveRootObject:person toFile:file];
需要从文件中解档对象就调用NSKeyedUnarchiver的一个工厂方法 unarchiveObjectWithFile: 即可。
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
if (person) {
self.avatarView.image = person.avatar;
self.nameField.text = person.name;
self.ageField.text = [NSString stringWithFormat:@"%ld", person.age];
}
3.注意
• 必须遵循并实现NSCoding协议
• 保存文件的扩展名可以任意指定
• 继承时必须先调用父类的归档解档方法
FMDB
1.简介
FMDB是iOS平台的SQLite数据库框架,它是以OC的方式封装了SQLite的C语言API,它相对于cocoa自带的C语言框架有如下的优点:
使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码
对比苹果自带的Core Data框架,更加轻量级和灵活
提供了多线程安全的数据库操作方法,有效地防止数据混乱
具体例子【微博】
@implementation ZhuStatusTool
static FMDatabase *_db = nil;
// 这个类第一次使用时就调用 只调用一次
+(void)initialize
{
NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *filepath = [doc stringByAppendingPathComponent:@"status.sqlite"];
_db = [FMDatabase databaseWithPath:filepath];
if ([_db open]) {
BOOL result = [_db executeUpdate:@"create table if not exists t_home_status (id integer primary key autoincrement,status_idstr text not null,access_token text not null,status_dict blob not null)"];
if (result) {
NSLog(@"建表OK");
}
else
{
NSLog(@"建表失败");
}
}
else
{
NSLog(@"建库失败");
}
}
// 首页数据
+(void)loadHomeStatusWithParam:(ZhuHomeStatusParam *)param success:(void (^)(ZhuHomeStatusResult *))success failure:(void (^)(NSError *))failure
{
//先从数据库中取数据
NSArray *statusDictArr = [self homeStatusCachedWithParam:param];
// 如果数据库有数据
if (statusDictArr.count != 0) {
if (success) {
NSArray *statusArr = [ZhuStatus objectArrayWithKeyValuesArray:statusDictArr];
ZhuHomeStatusResult *result = [[ZhuHomeStatusResult alloc]init];
result.statuses = statusArr;
success(result);
}
}
else // 数据库里没有数据
{
//发请求
[ZhuHttpTool getWithUrl:@"https://api.weibo.com/2/statuses/home_timeline.json" param:[param keyValues] success:^(id responseObject) {
// 存储请求过来的数据
[self saveHomeStatus:responseObject[@"statuses"] accessToken:param.access_token];
if (success) {
// responseObject是一条刚刚发的微博字典
ZhuHomeStatusResult *result = [ZhuHomeStatusResult objectWithKeyValues:responseObject];
// 调用外界的block,并把result结果传出去
success(result);
}
} failure:^(NSError *error) {
if (failure) {
failure(error);
}
}];
}
[self getWithUrl:@"https://api.weibo.com/2/statuses/home_timeline.json" param:param resultClass:[ZhuHomeStatusResult class] success:success failure:failure];
}
+(void)saveHomeStatus:(NSArray *)statusDictArr accessToken:(NSString *)accessToken
{
for (NSDictionary *statusDict in statusDictArr
) {
// 字典转二进制
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:statusDict];
[_db executeUpdate:@"insert into t_home_status(status_idstr,status_dict,access_token) values(?,?,?)",statusDict[@"idstr"],data,accessToken];
}
}
+(NSArray *)homeStatusCachedWithParam:(ZhuHomeStatusParam *)param
{
NSMutableArray *statusArr = [NSMutableArray array];
FMResultSet *resultSet = nil;
//从数据库
if (param.since_id) {
/*
order by 是指根据 什么来排序 默认是升序 这里用 desc 指的是降序
limit 限制 就是说每次返回多少数据
access_token 一个access_token 对应一个账号,一部手机可能有多个账号
*/
• sultSet = [_db executeQuery:@"select * from t_home_status where status_idstr > ? and access_token = ? order by status_idstr desc limit ?",param.since_id,param.access_token,param.count];
}
else if (param.max_id)
{
• sultSet = [_db executeQuery:@"select * from t_home_status where status_idstr <= ? and access_token = ? order by status_idstr desc limit ?",param.max_id,param.access_token,param.count];
}
else
{
• sultSet = [_db executeQuery:@"select * from t_home_status where access_token = ? order by status_idstr desc limit ?",param.access_token,param.count];
}
while (resultSet.next) {
// 二进制转字典
NSData *data = [resultSet objectForColumnName:@"status_dict"];
NSDictionary *statusDict = [NSKeyedUnarchiver unarchiveObjectWithData:data];
[statusArr addObject:statusDict];
}
return statusArr;
}
线程安全
在多个线程中同时使用一个FMDatabase实例是不明智的。不要让多个线程分享同一个FMDatabase实例,它无法在多个线程中同时使用。 如果在多个线程中同时使用一个FMDatabase实例,会造成数据混乱等问题。所以,请使用 FMDatabaseQueue,它是线程安全的。以下是使用方法:
• 创建队列。
1
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
• 使用队列
[queue inDatabase:^(FMDatabase *database) {
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_1", [NSNumber numberWithInt:1]];
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_2", [NSNumber numberWithInt:2]];
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_3", [NSNumber numberWithInt:3]];
• ResultSet *result = [database executeQuery:@"select * from t_person"];
while([result next]) {
}
}];
而且可以轻松地把简单任务包装到事务里:
[queue inTransaction:^(FMDatabase *database, BOOL *rollback) {
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_1", [NSNumber numberWithInt:1]];
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_2", [NSNumber numberWithInt:2]];
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_3", [NSNumber numberWithInt:3]];
• ResultSet *result = [database executeQuery:@"select * from t_person"];
while([result next]) {
}
//回滚
*rollback = YES;
}];
如果想要自定义控件 需要先了解各个控件都是干什么用的 如果这个控件满足了你的需求 完全可以直接用 不用自定义
自定义控件经常是使用,一般情况下,自定义一个控件首先你要知道这个控件是干嘛用得,它需不需要显示图片,文字,需不需要点击,需不需要变动frame等,然后根据响应的需求留下对应的接口,比如点击一个自定义控件,那这个控件需要通知控制器它被点击了,简单情况下如果只有一个button,只需要通知一个控制器,就可以通过外部定义一个addTarget方法连接内部的button的addTarget方法,如果view处理的逻辑比较复杂需要使用代理协议来传递更多的信息,参数,如果一处点击需要通知多个控制器进行响应,就需要用到通知中心的通知机制,保证自定义控件与其他控件或控制器之间尽量没有任何关系,调用时就是通过控件在.h中留下的方法(接口)进行操作的,降低耦合性。
适配(Mesory UIView+autolayout)。
适配原来使用的是autoResize,现在在storyboard和xib中一般是使用autolayout
适配时使用的第三方有masonry
基于iOS SDK 的布局分类 UIView+AutoLayout
masonry的代码十分简洁,使用链式语法,使用起来也十分简单方便,下面说一下主要用法,比如布局一个view让它在显示居中,宽高都是100
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.view);
make.size.mas_equalTo(CGSizeMake(100, 100));
}];
这样就完成了一个居中view的布局,这里面可用的属性有top,bottom,left,right,width,height,leading,trailing,centerX,centerY,baseline其实还可以直接使用center,edges
masonry一共有3种布局方法
mas_makeConstraints:创建一个新的约束,在autoLayout中对同一个对象设置多个重复约束会报错
mas_updateConstraints:跟新在block中出现的约束,不会出现重复的约束
mas_remakeConstraints:清除之前的所有约束,仅保留block中的新的约束
设置约束的方法也是三种
mas_equalTo()等于 mas_greaterThanOrEqualTo() 大于等于 mas_lessThanOrEqualTo() 小于等于
UIView+AutoLayout的使用
更接近autoLayout的底层,有三种设置方式,
第一种以autosetdimension开头的,约束size类(就是设置宽高)
第二种以autopin开头,约束位置的(就是设置距离上下左右的距离)
第三种以autoAlign开头,约束对齐(就是设置XY轴对齐的)
语法简单一看就知道了
KVO、KVC
KVO
1.注册
-(void)addObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void*)context
keyPath就是要观察的属性值,options给你观察键值变化的选择,而context方便传输你需要的数据(注意这是一个void型)
1. 实现变化方法:
-(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void*)context
3.只有符合KVC标准的对象才能使用kvo,KVO,即:Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,则对象就会接受到通知。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。
KVO是键值监听,可以监听一个对象属性值的改变(注册监听实现回调)
KVO其实也是“观察者”设计模式的一种应用。我的看法是,这种模式有利于两个类间的解耦合,尤其是对于 业务逻辑与视图控制 这两个功能的解耦合。
KVC是键值编码,可以通过一个字符串的key(属性名)修改对象的属性值 可以改变私有的(只读)属性的值
5、用KVC存取对象属性 [student setValue:@"张三" forKey:@"name"];
NSString *name = [student valueForKey:@“name”];
//如果访问这个类里中的属性中的属性呢?那就用到了键路径
关键字:键路径取值valueForKeyPath 键路径存值:forKeyPath
[student setValue:@"数学课" forKeyPath:@"course.CourseName"];
courseName = [student valueForKeyPath:@"course.CourseName"];
NSLog(@"课程名称:%@", courseName);
3、自动封装基本数据类型 [student setValue:@"88" forKeyPath:@“point"];88是NSInteger型
4、操作集合
NSArray *array = [NSArray arrayWithObjects:student1,student2,student3,nil];
[student setValue:array forKey:@"otherStudent"];
地图(百度地图集成流程,出现的问题怎么解决)。
1. 进入控制台 ,填安全码【安全码 与 程序中Bundel Identifier 要一致】提交创建应用,会自动生成一个AK【这个AK就是开发密匙【key】】
2. 下载百度地图SDK,将所需要的文件拖入程序中
3. 注意:
1. 工程中至少有一个.mm后缀的源文件
1. 为了对iOS8的定位能力做兼容,做了相应的修改,使用过程中注意如下: 需要在info.plist里添加(以下二选一,两个都添加默认使用NSLocationWhenInUseUsageDescription):
NSLocationWhenInUseUsageDescription 允许在前台使用时获取GPS的描述
NSLocationAlwaysUsageDescription 允许永久使用GPS的描述
1. 需要在info.plist中添加:Bundle display name ,且其值不能为空(Xcode6新建的项目没有此配置,若没有会造成manager start failed)
1. 记得导入对应的系统库
环境配置
在TARGETS->Build Settings->Other Linker Flags 中添加-ObjC。
需要导入框架
CoreLocation.framework
SystemConfiguration.framework
OpenGLES.framework
CoreGraphics.framework
QuartzCore.framework
1. 在AppDelegate.h文件中添加BMKMapManager的定义
在AppDelegate.m文件中添加对BMKMapManager的初始化,并填入您申请的授权Key
1. 接下来就可以直接使用百度地图了...
问题:使用百度地图的大头针
友盟(集成流程,注意什么,SSO)。
收藏、最近删除collectionView。(流程)
收藏和最近主要就是使用工具类对数据进行增加和删除,保存数据时要重写数据的equalto方法(因为计算机是根据内存地址比较的,而有时候内存地址虽然不同,但是储存的数据确实一样的,所以一般根据数据的位置标识(例如id值)比较)
如果是做最近,就需要将上一次的数据删除,添加新的数据进来,如果在收藏是需要记录控件的编辑和选中状态,就需要在数据模型中添加相应的属性,利用数据来控制控件的状态
常出现的问题就是使用tableview时会出现循环利用的问题,这个问题就可以使用数据的值来避免,每次加载cell时,他的状态是受数据控制的,数据不会发生循环利用的问题,这个问题也就不会出现了
FMDB(流程)
建库——建表——
真机(流程)
推送(流程!!!)
推送:当程序在没有启动,或后台运行时,仍能接收到新消息的一种途径。
一般意义上说推送就是指远程推送,本地推送一般可以用来提示长时间未进入应用的用户,也可以用来做闹铃。
(一个程序可以推送,首先你要向苹果公司注册推送证书)
下面详细说一下远程推送的流程:
1.当你的程序需要推送时,通过UIApplication中的registerUserNotification注册远程推送,注册后,你的程序会通过iOS系统向APNs服务器请求,APNs服务器接到请求后会将请求设备的device token(设备令牌)发送回你的应用,在UIApplication的代理方法中可以接收到device token,如果请求失败也会通过代理方法返回错误信息
2.当应用程序拿到device token后,就可以将device token回传给应用提供商服务器,服务器就知道了这台设备可以推送消息了,然后将device token储存在服务器内部,device token的生成算法只有苹果公司才知道,所以为了防止苹果修改算法造成推送失败,最好每次启动程序时都请求一次device token,在device token发生改变时,告诉服务器新的device token
(推送一般情况下是程序提供商向用户推送一些最新的消息或者资讯,不过比如QQ,微信等可以在离线的情况下进行消息的提醒,下面以qq推送离线消息为例,相比从服务器推送,qq离线消息的推送是由客户端编辑信息的)
3.现在如果程序要推送消息了,就可以将消息和要发送的对象的账号发送给程序提供商服务器,服务器会通过你要推送的对象的账号信息找到对应绑定的device token,然后将推送消息内容和device token传给APNs服务器
4.APNs服务器在接收到消息内容和device token后会查找已注册的设备然后将对应的信息和device token推送到指定的设备上,设备通过device token中的app id找到要推送的app,然后信息会按照app的推送设置显示信息
二维码
做二维码的话,可以使用的第三发库有ZBar和ZXing 具体使用方法可以去网上查看文档
但是现在iOS中的AVFoundation框架中也集成了二维码扫描,用起来也十分方便,并且扫描速度也更快,还可以使用AVFoundation框架生成二维码
iOS技术的更多相关文章
-
收藏的iOS技术站点汇总(持续更新ing)
大牛博客 objc.io PS:经典,内容深而广 objc中国 NSHipster PS:非常多小细节 NSHipster 中文版 唐巧的技术博客 PS:LZ是唐巧的脑残粉- OneV's Den 王 ...
-
李洪强iOS开发之iOS技术博客
李洪强iOS开发之iOS技术博客 注意:访问博客请直接点击博客,不要点击后面的RSS地址 博客地址 RSS地址 南峰子的技术博客 剑尖博客 图拉鼎 Henry Lee Dev Talk ...
-
iOS 技术支持
iOS 技术支持网址:有问题或建议请留言. 邮箱地址:odeyrossskudder4266848@mail.com iOS program design & system consultat ...
-
40个国人iOS技术博客
40个国人iOS技术博客 博客地址 RSS地址 OneV's Den http://onevcat.com/atom.xml 破船之家 http://beyondvincent.com/atom.xm ...
-
iOS技术博客
iOS 技术提高 原文 http://blog.devtang.com/2014/07/27/ios-levelup-tips/# 1.阅读博客 美团技术博客 https://tech.meituan ...
-
唐巧的iOS技术博客选摘
1. 那些被遗漏的objective-c保留字:http://blog.devtang.com/blog/2013/04/29/the-missing-objc-keywords/ 2. 使用cr ...
-
开始写自己的iOS技术博客了
2015-09-26 中秋节前夕,开始写自己的iOS开发相关的技术博客,还请广大专业的人士批评指教!欢迎纠错和交流! 在来到北京的第二家公司艾亿新融资本管理的子公司——资配易.由于基本没有加班,也算有 ...
-
(转)iOS Wow体验 - 第五章 - 利用iOS技术特性打造最佳体验
本文是<iOS Wow Factor:Apps and UX Design Techniques for iPhone and iPad>第五章译文精选,其余章节将陆续放出.上一篇:Wow ...
-
iOS技术开发-人机交互指南之UI设计基础:iOS App Anatomy
第二篇更多的是从技术的角度对iOS界面组成原理进行了简单的解析,篇幅很短,可稍作了解:更多关于iOS开发入门的内容可参考“设计师应该了解的iOS应用开发基础知识”一文.另外,非常感谢各位朋友在微博上的 ...
随机推荐
-
探索DOMNode
实现代码 <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8& ...
-
python学习笔记-(十二)scoket编程基础
socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. socket起源于Un ...
-
UIControl
//当遇到button上添加图片,不显示图片,而显示蓝色,解决方案 //1.button的类型,改成UIButtonTypeCustom //2.button的set使用setBackgroundIm ...
-
codevs1910 递归函数
难度等级:黄金 codevs1910 递归函数 题目描述 Description 对于一个递归函数w(a, b, c). 如果a <= 0 or b <= 0 or c <= 0就返 ...
-
poj2986A Triangle and a Circle&;&;poj3675Telescope(三角形剖分)
链接 2986是3675的简化版,只有一个三角形. 这题主要在于求剖分后三角形与圆的相交面积,需要分情况讨论. 具体可以看此博客 http://hi.baidu.com/billdu/item/703 ...
-
我的Pandas应用场景(2)
上文交代了一些啰嗦事,本文开始,就要来点实际的了. 先来一个比较简单的场景: Given:一个包括N(极其复杂,这里取3个)个列的DataFrame:df,df包括index: And:对df所有列元 ...
-
SpringCloud的服务消费者 (二):(rest+feign/ribbon)声明式访问注册的微服务
采用Ribbon或Feign方式访问注册到EurekaServer中的微服务.1.Ribbon实现了客户端负载均衡,Feign底层调用Ribbon2.注册在EurekaServer中的微服务api,不 ...
-
用Flow编写更好的js代码
关于本文: 原文地址 翻译地址 译者:野草 本文发表于前端早读课[第897期] 你是否经常在debug那些简单可避免的bug?可能你给函数传参的时候搞错了参数的顺序,或者本来应该传个Number类型的 ...
-
buildroot使用介绍
buildroot是Linux平台上一个构建嵌入式Linux系统的框架.整个Buildroot是由Makefile脚本和Kconfig配置文件构成的.你可以和编译Linux内核一样,通过buildro ...
-
学习Vue 入门到实战——学习笔记
闲聊: 自从进了现在的公司,小颖就再没怎么接触vue了,最近不太忙,所以想再学习下vue,就看了看vue相关视频,顺便做个笔记嘻嘻. 视频地址:Vue 入门到实战1.Vue 入门到实战2 学习内容: ...