-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
133 lines (93 loc) · 65.4 KB
/
search.xml
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[Machine Learning_01_(无)监督学习]]></title>
<url>%2F2017%2F05%2F08%2FMachine-Learning-01%2F</url>
<content type="text"><![CDATA[机器学习定义TomMitchell (1998) : Well-posed Learning Problem: A computer program is said tolearn from experience E with respect to some task T and some performance measureP, if its performance on T, as measured by P, improves with experience E. 例子:对于一个垃圾邮件识别的问题,将邮件分类为垃圾邮件或非垃圾邮件是任务T,查看哪些邮件被标记为垃圾邮件哪些被标记为非垃圾邮件是经验E,正确识别的垃圾邮件或非垃圾邮件的数量或比率是评测指标P。 监督学习对具有概念标记(分类)的训练样本进行学习, 以尽可能对训练样本集外的数据进行标记(分类)预测. 这里所有的标记(分类)是已知的, 因此, 训练样本的歧义性较低. 这种技术高度依赖于事先确定的分类系统给出的信息. ex: 房屋价格预测 -> 回归问题 乳腺癌(良性, 恶性)预测 -> 分类问题 回归问题和分类问题都是监督学习的内容. 无监督学习 我们不告诉计算机怎么做, 而是让计算机自己去学习怎么做. 对没有概念标记(分类)的训练样本进行学习, 以发现训练样本集中的结构性知识. 这里所有的标记(分类)都是未知的, 因此, 训练样本的歧义性高. 无监督学习一般有两种思路: 激励制度当程序执行后对正确的行为做出某种形式的激励. ex: 西洋双陆棋 聚合程序会找到训练数据中的近似点 无监督学习还有一个典型的例子: 鸡尾酒会问题 (声音的分离)这个酒会上有两种声音, 被两个不同的麦克风在不同的地方接收到, 而利用无监督学习可以分离这两种不同的声音.代码只有一行: 1[W, s, v] = svd((repmat(sum(x.*x, 1), size(x, 1), 1).*x)*x'); 参考: https://www.zhihu.com/question/23194489]]></content>
</entry>
<entry>
<title><![CDATA[Centos7 安装MySQL/MariaDB]]></title>
<url>%2F2016%2F11%2F30%2FCentos7%20%E5%AE%89%E8%A3%85MySQL%3AMariaDB%2F</url>
<content type="text"><![CDATA[Centos7 安装MySQL/MariaDB MariaDB数据库管理系统是MySQL的一个分支.在CentOS7中MySQL被MariaDB所代替. 一开始安装的时候执行的yum install mysql, 发现装上之后不能启动, 后来发现在CentOS7中MySQL被MariaDB所代替.然后再删除mysql, 重新安装MariaDB, 但是神奇的是还是不能启动. 最后找到解决方案: 删除/var/lib/mysql文件夹 删除/etc/my.cnf文件夹 卸载yum remove mariadb* 重装yum -y install mariadb* 启动systemctl start mariadb.service 终于不报错了! 接着再进行一些初始化配置 设置开机启动systemctl enable mariadb 设置密码mysql_secure_installation 会提示输入密码: Enter current password for root (enter for none): 此时是没有密码的, 直接回车 然后设置密码Set root password? [Y/n]New password:Re-enter new password: 是否删除匿名用户Remove anonymous users? [Y/n] 直接回车 是否禁止root远程登录Disallow root login remotely? [Y/n] 直接回车 是否删除test数据库Remove test database and access to it? [Y/n] 直接回车 是否重新加载权限表Reload privilege tables now? [Y/n] 直接回车 接下来可以测试登录mysql -uroot -pnewpassword Over!]]></content>
</entry>
<entry>
<title><![CDATA[协程]]></title>
<url>%2F2016%2F11%2F24%2F%E5%8D%8F%E7%A8%8B%2F</url>
<content type="text"><![CDATA[123456789101112131415161718192021222324# -*- coding: utf8 -*-# 消费者def consumer(): r = "" while True: n = yield r if not n: return print("[CONSUMER] Consuming %s..." % n) r = "200 OK"# 生产者def produce(c): c.send(None) n = 0 while n < 5: n = n + 1 print("[PRODUCER] Producing %s..." % n) r = c.send(n) print("[PRODUCER] Consumer return: %s" % r) c.close()c = consumer() produce(c) 每当执行send()函数的时候会来到consumer中yield的下一条语句每当执行到yield时会停住执行produce中send的下一条语句 1234567891011121314def countdown(n): print("Counting down from %d" % n) while n >= 0: newValue = yield n if newValue is not None: n = newValue else: n -= 1c = countdown(5)for x in c: print(x) if x == 5: c.send(3) 输出结果: 5 2 1 0 send给generator的value会成为当前yield的结果 且send的返回结果是下一个yield的结果, 即此处c.send(3)会返回3]]></content>
</entry>
<entry>
<title><![CDATA[利用aiohttp搭建python服务器]]></title>
<url>%2F2016%2F11%2F23%2Faiohttp%E6%9C%8D%E5%8A%A1%E5%99%A8%2F</url>
<content type="text"><![CDATA[利用aiohttp搭建python服务器123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960# -*- coding: utf8 -*-import logging; logging.basicConfig(level=logging.INFO)import asyncio, os, json, timefrom datetime import datetimefrom aiohttp import webshockRecord = ""wwlyRecord = ""result = ""def index(request): global shockRecord global wwlyRecord global result text = '%s' % request.match_info['anything'] # print(text) if 'shock:' in text: tempShock = text[6:] shockRecord = shockRecord + "</br>" + tempShock elif 'wwly:' in text: tempWwly = text[5:] wwlyRecord = wwlyRecord + "</br>" + tempWwly else: result = result + "</br>" + text # print(result) # print(request.url) htmlStr = """ <h3>Record</h3> <div style="width: 200; float: left;"> <h4>shock:</h4> <p style="font-size: 6px;">%s</p> </div> <div style="width: 200; float: left;"> <h4>wwly:</h4> <p style="font-size: 6px;">%s</p> </div> <div style="width: 200; float: left;"> <h4>未知:</h4> <p style="font-size: 6px;">%s</p> </div> """ % (shockRecord, wwlyRecord, result) # htmlStr = htmlStr + '' return web.Response(body=htmlStr.encode('utf-8'), content_type='text/html')@asyncio.coroutinedef init(loop): app = web.Application(loop=loop) app.router.add_route('GET', '/{anything}', index) app.router.add_route('GET', '/*', index) srv = yield from loop.create_server(app.make_handler(), '0.0.0.0', 9000) logging.info('server started at http://0.0.0.0:9000...') return srvloop = asyncio.get_event_loop()loop.run_until_complete(init(loop))loop.run_forever() 上面的代码实现了解析对方的GET请求, 可以把想传递的内容放在url链接后面, 同时会把内容记录在全局变量中并展示出来. 其中:app.router.add_route('GET', '/{anything}', index)为了把所有的url映射到同一个index函数上. app.make_handler(), '0.0.0.0', 9000一开始我绑定的本地ip地址127.0.0.1, 发现本机可以访问, 但是外网不能通过本机ip进行访问. 后来尝试绑定0.0.0.0, 实现了外网通过ip地址访问本地python服务器. web.Response(body=htmlStr.encode('utf-8'), content_type='text/html')text/html为了设置响应体格式, 否则对方一访问ip地址就会下载文件.]]></content>
</entry>
<entry>
<title><![CDATA[搭建hexo博客]]></title>
<url>%2F2016%2F11%2F21%2Finit%20hexo%2F</url>
<content type="text"><![CDATA[搭建hexo博客在之前就萌生了建立博客网站的念头, 之前使用WordPress在新浪云上搭建过一次, 但是效果并不理想. 后来意外发现hexo静态博客工具, 又了解到next这个hexo主题, 于是便再一次在github上搭建了此博客. 由于之前搭建过几次, 所以这次搭建的很顺利, 按照文档修改成自己喜欢的样式. 这次我把源码单独作为一个仓库上传至github, 以免有失. 谨记: 当你在嘲笑一亿是一个小目标时, 你是在嘲笑谁?]]></content>
</entry>
<entry>
<title><![CDATA[UITextField精准限制输入长度]]></title>
<url>%2F2016%2F09%2F27%2FUITextField%E8%BE%93%E5%85%A5%E4%B8%A4%E4%BD%8D%E5%B0%8F%E6%95%B0%2F</url>
<content type="text"><![CDATA[123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { BOOL isHaveDian = YES; if ([textField.text rangeOfString:@"."].location == NSNotFound) { isHaveDian = NO; } if ([string length] > 0) { unichar single=[string characterAtIndex:0];//当前输入的字符 if ((single >='0' && single<='9') || single=='.')//数据格式正确 { //首字母不能为0和小数点 if ([textField.text length]==0) { if (single == '.') { // 第一个数字不能为小数点 [textField.text stringByReplacingCharactersInRange:range withString:@""]; return NO; } if (single == '0') { // 亲,第一个数字不能为0 [textField.text stringByReplacingCharactersInRange:range withString:@""]; return NO; } } if (single=='.') { if(!isHaveDian) // text中还没有小数点 { isHaveDian=YES; return YES; } else { // 已经输入过小数点 [textField.text stringByReplacingCharactersInRange:range withString:@""]; return NO; } } else { if (isHaveDian) // 存在小数点 { // 判断小数点的位数 NSRange ran=[textField.text rangeOfString:@"."]; NSInteger tt = range.location-ran.location; if (tt <= 2){ return YES; } else { // 最多输入两位小数 return NO; } } else { return YES; } } } else { // 输入的数据格式不正确 [textField.text stringByReplacingCharactersInRange:range withString:@""]; return NO; } } else { return YES; }}]]></content>
</entry>
<entry>
<title><![CDATA[UITextField精准限制输入长度]]></title>
<url>%2F2016%2F09%2F27%2FUITextField%E7%B2%BE%E5%87%86%E9%99%90%E5%88%B6%E8%BE%93%E5%85%A5%E9%95%BF%E5%BA%A6%2F</url>
<content type="text"><![CDATA[首先需要对 textField 进行通知监听, 通知为UITextFieldTextDidChangeNotification 1234[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(encourageNameFieldDidChange:) name:UITextFieldTextDidChangeNotification object:textField]; 绑定的方法实现如下 12345678910111213141516171819202122232425262728293031- (void)encourageNameFieldDidChange:(NSNotification *)notification { NSString *toBeString = self.encourageNameField.text; UITextInputMode *currentInputMode = self.encourageNameField.textInputMode; if (currentInputMode == nil) { if (toBeString.length > 10) { self.encourageNameField.text = [toBeString substringToIndex:10]; } return; } NSString *lang = [currentInputMode primaryLanguage]; if ([lang isEqualToString:@"zh-Hans"]) { // 简体中文输入 UITextRange *selectedRange = [self.encourageNameField markedTextRange]; // 获取高亮部分 UITextPosition *position = [self.encourageNameField positionFromPosition:selectedRange.start offset:0]; // 没有高亮选择的字, 则对已输入的文字进行字数统计和限制 if (!position) { if (toBeString.length > 10) { self.encourageNameField.text = [toBeString substringToIndex:10]; } } // 有高亮选择的字符串, 暂不对文字进行统计和限制 else { } } else { if (toBeString.length > 10) { self.encourageNameField.text = [toBeString substringToIndex:10]; } }}]]></content>
</entry>
<entry>
<title><![CDATA[遍历目录删除指定文件夹(递归)]]></title>
<url>%2F2016%2F08%2F12%2F%E9%81%8D%E5%8E%86%E7%9B%AE%E5%BD%95%E5%88%A0%E9%99%A4%E6%8C%87%E5%AE%9A%E6%96%87%E4%BB%B6%E5%A4%B9(%E9%80%92%E5%BD%92)%2F</url>
<content type="text"><![CDATA[遍历目录删除指定文件夹(递归) 最新公司项目要从 SVN 上迁移至 Git 上, 项目经过了那么多迭代, 文件夹比较大, 所以需要删除文件夹内的 SVN 缓存文件, 正好最近在学习 python, 因此就写了这么一个小脚本. 12345678910111213141516171819202122232425# -*- coding: UTF-8 -*-import os, sysdef deleteSVN(directory, postfix=''): # os.remove(directory) if os.path.isdir(directory): svn = os.path.join(directory, postfix) # os.remove(svn) if os.path.exists(svn): os.rmdir(svn) print('remove') temps = [os.path.join(directory, temp) for temp in os.listdir(directory) if os.path.isdir(os.path.join(directory, temp))] if len(temps) == 0: print('over') return for temp2 in temps: print('again') deleteSVN(temp2, postfix)# 获取用户在终端输入的参数, 第一个是当前文件名directory = sys.argv[1] postfix = sys.argv[2]deleteSVN(directory, postfix)# deleteSVN(os.path.join(os.getcwd(), 'Python'), '.svn')]]></content>
</entry>
<entry>
<title><![CDATA[应用内检查更新]]></title>
<url>%2F2016%2F07%2F22%2F%E5%BA%94%E7%94%A8%E5%86%85%E6%A3%80%E6%9F%A5%E6%9B%B4%E6%96%B0%2F</url>
<content type="text"><![CDATA[苹果官方不允许应用提示更新, 不过大多 APP 都采用各种方式避开苹果这个限制. 我采用的方式是向苹果服务器发送请求以获取最新版本号和当前版本号对比来提示用户进行更新, 此方法暂时没有审核失败过. 123456789101112131415161718192021222324252627282930313233343536373839404142static NSString const * appID = @"";- (NSDictionary *)checkAppUpdate{ NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://itunes.apple.com/lookup?id=%@", appID]]; NSString * file = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; if (!file || file.length <= 0) { return nil; } NSString *oldVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; //版本号 NSRange substr = [file rangeOfString:@"\"version\":\""]; NSRange range1 = NSMakeRange(substr.location+substr.length,10); NSRange substr2 =[file rangeOfString:@"\"" options:NSCaseInsensitiveSearch range:range1]; NSRange range2 = NSMakeRange(substr.location+substr.length, substr2.location-range1.location); NSString *newVersion =[file substringWithRange:range2]; BOOL isUpdate = NO; if ([oldVersion compare:newVersion options:NSNumericSearch] == NSOrderedAscending) { isUpdate = YES; } NSString *updateLog = @""; if (isUpdate && (file != nil)) { //更新日志 NSRange logStr = [file rangeOfString:@"\"releaseNotes\":\""]; //最多查找1000字 NSRange range1 = NSMakeRange(logStr.location+logStr.length,file.length - logStr.location-logStr.length); NSRange logStr2 =[file rangeOfString:@"\"" options:NSCaseInsensitiveSearch range:range1]; NSRange range2 = NSMakeRange(logStr.location+logStr.length, logStr2.location-range1.location); updateLog =[file substringWithRange:range2]; updateLog = [updateLog stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"]; } NSDictionary *appInfo = @{@"version":newVersion,@"update_log":updateLog,@"update":@(isUpdate)}; return appInfo;} 12345-(void)showAppUpdateViewWithTitle: (NSString *)title message: (NSString *)msg { UIAlertView * alert = [[UIAlertView alloc]initWithTitle:title message:msg delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"访问 Store", nil]; alert.tag = 110; [alert show];}]]></content>
</entry>
<entry>
<title><![CDATA[多线程之NSOperation简介]]></title>
<url>%2F2016%2F05%2F21%2F%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B9%8BNSOperation%E7%AE%80%E4%BB%8B%2F</url>
<content type="text"><![CDATA[多线程之NSOperation简介 在iOS开发中,为了提升用户体验,我们通常会将操作耗时的操作放在主线程之外的线程进行处理。对于正常的简单操作,我们更多的是选择代码更少的GCD,让我们专注于自己的业务逻辑开发。NSOperation在ios4后也基于GCD实现,但是相对于GCD来说可控性更强,并且可以加入操作依赖。 NSOperation是一个抽象的基类,表示一个独立的计算单元,可以为子类提供有用且线程安全的建立状态,优先级,依赖和取消等操作。系统已经给我们封装了NSBlockOperation和NSInvocationOperation这两个实体类。使用起来也非常简单,不过我们更多的使用是自己继承并定制自己的操作。 NSOperation定义123456789101112131415161718192021222324252627282930313233343536- (void)start;- (void)main;@property (readonly, getter=isCancelled) BOOL cancelled;- (void)cancel;@property (readonly, getter=isExecuting) BOOL executing;@property (readonly, getter=isFinished) BOOL finished;@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);@property (readonly, getter=isReady) BOOL ready;- (void)addDependency:(NSOperation *)op;- (void)removeDependency:(NSOperation *)op;@property (readonly, copy) NSArray *dependencies;typedef NS_ENUM(NSInteger, NSOperationQueuePriority) { NSOperationQueuePriorityVeryLow = -8L, NSOperationQueuePriorityLow = -4L, NSOperationQueuePriorityNormal = 0, NSOperationQueuePriorityHigh = 4, NSOperationQueuePriorityVeryHigh = 8};@property NSOperationQueuePriority queuePriority;@property (copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);@property (copy) NSString *name NS_AVAILABLE(10_10, 8_0); 状态NSOperation提供了ready cancelled executing finished这几个状态变化,我们的开发也是必须处理自己关心的其中的状态。这些状态都是基于keypath的KVO通知决定,所以在你手动改变自己关心的状态时,请别忘了手动发送通知。这里面每个属性都是相互独立的,同时只可能有一个状态是YES。finished这个状态在操作完成后请及时设置为YES,因为NSOperationQueue所管理的队列中,只有isFinished为YES时才将其移除队列,这点在内存管理和避免死锁很关键。 依赖NSOperation中我们可以为操作分解为若干个小的任务,通过添加他们之间的依赖关系进行操作,这点在设计上是很有意义的。比如我们最常用的图片异步加载,第一步我们是去通过网络进行加载,第二步我们可能需要对图片进行下处理(调整大小或者压缩保存)。我们可以直接调用- (void)addDependency:(NSOperation*)op;这个方法添加依赖: 123[imgRsizingOperation addDependency:networkOperation];[operationQueue addOperation:networkOperation];[operationQueue addOperation:imgRsizingOperation]; 这点我们必须要注意的是不能添加相互依赖,像A依赖B,B依赖A,这样会导致死锁!还有一点必须要注意的时候,在每个操作完成时,请将isFinished设置为YES,不然后续的操作是不会开始执行的。 执行执行一个operation有两种方法,第一种是自己手动的调用start这个方法,这种方法调用会在当前调用的线程进行同步执行,所以在主线程里面自己一定要小心的调用,不然就会把主线程给卡死,还不如直接用GCD呢。第二种是将operation添加到operationQueue中去,这个也是我们用得最多的也是提倡的方法。NSOperationQueue会在我们添加进去operation的时候尽快进行执行。当然如果NSOperationQueue的maxConcurrentOperationCount如果设置为1的话,进相当于FIFO了。 队列是怎么调用我们的执行的操作的呢?如果你只是想弄一个同步的方法,那很简单,你只要重写main这个函数,在里面添加你要的操作。如果想定义异步的方法的话就重写start方法。在你添加进operationQueue中的时候系统将自动调用你这个start方法,这时将不再调用main里面的方法。 取消NSOperation允许我们调用-(void)cancel取消一个操作的执行。当然,这个操作并不是我们所想象的取消。这个取消的步骤是这样的,如果这个操作在队列中没有执行,那么这个时候取消并将状态finished设置为YES,那么这个时候的取消就是直接取消了。如果这个操作已经在执行了,那么我们只能等其操作完成。当我们调用cancel方法的时候,他只是将isCancelled设置为YES。所以,在我们的操作中,我们应该在每个操作开始前,或者在每个有意义的实际操作完成后,先检查下这个属性是不是已经设置为YES。如果是YES,则后面操作都可以不用在执行了。 completionBlockiOS4后添加了这个block,在这个操作完成时,将会调用这个block一次,这样也非常方便的让我们对view进行更新或者添加自己的业务逻辑代码。 优先级operationQueue有maxConcurrentOperationCount设置,当队列中operation很多时而你想让后续的操作提前被执行的时候,你可以为你的operation设置优先级 12345NSOperationQueuePriorityVeryLow = -8L,NSOperationQueuePriorityLow = -4L,NSOperationQueuePriorityNormal = 0,NSOperationQueuePriorityHigh = 4,NSOperationQueuePriorityVeryHigh = 8 简单示例代码最后我们看看一个简单的小示例,在.m文件里面我们将重写finished executing两个属性。我们重写set方法,手动发送keyPath的KVO通知。在start函数中,我们首先判断是否已经取消,如果取消的话,我们将直接return,并将finished设置为YES。如果没有取消操作,我们将_executing设置为YES,表示当前operation正在执行,继续执行我们的逻辑代码。在执行完我们的代码后,别忘了设置operation的状态,将_executing设置为NO,并将finished设置为YES,这样我们就已经很简单的完成了我们的多线程操作任务。 1234567891011121314151617181920212223242526272829303132333435363738@interface TestOperation ()@property (nonatomic, assign) BOOL finished;@property (nonatomic, assign) BOOL executing;@end@implementation TestOperation@synthesize finished = _finished;@synthesize executing = _executing;- (void)start{ if ([self isCancelled]) { _finished = YES; return; } else { _executing = YES; //start your task; //end your task _executing = NO; _finished = YES; }}- (void)setFinished:(BOOL)finished { [self willChangeValueForKey:@"isFinished"]; _finished = finished; [self didChangeValueForKey:@"isFinished"];}- (void)setExecuting:(BOOL)executing { [self willChangeValueForKey:@"isExecuting"]; _executing = executing; [self didChangeValueForKey:@"isExecuting"];}]]></content>
</entry>
<entry>
<title><![CDATA[GCD和自定义NSOperation的对比]]></title>
<url>%2F2016%2F05%2F21%2FGCD%E5%92%8C%E8%87%AA%E5%AE%9A%E4%B9%89NSOperation%E7%9A%84%E5%AF%B9%E6%AF%94%2F</url>
<content type="text"><![CDATA[GCD 技术是一个轻量的,底层实现隐藏的神奇技术,我们能够通过GCD和block轻松实现多线程编程,有时候,GCD相比其他系统提供的多线程方法更加有效,当然,有时候GCD不是最佳选择,另一个多线程编程的技术 NSOprationQueue 让我们能够将后台线程以队列方式依序执行,并提供更多操作的入口,这和 GCD 的实现有些类似。 这种类似不是一个巧合,在早期,MacOX 与 iOS 的程序都普遍采用Operation Queue来进行编写后台线程代码,而之后出现的GCD技术大体是依照前者的原则来实现的,而随着GCD的普及,在iOS 4 与 MacOS X 10.6以后,Operation Queue的底层实现都是用GCD来实现的。 那这两者直接有什么区别呢? GCD是底层的C语言构成的API,而NSOperationQueue及相关对象是Objc的对象。在GCD中,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构;而Operation作为一个对象,为我们提供了更多的选择; 在NSOperationQueue中,我们可以随时取消已经设定要准备执行的任务(当然,已经开始的任务就无法阻止了),而GCD没法停止已经加入queue的block(其实是有的,但需要许多复杂的代码); NSOperation能够方便地设置依赖关系,我们可以让一个Operation依赖于另一个Operation,这样的话尽管两个Operation处于同一个并行队列中,但前者会直到后者执行完毕后再执行; 我们能将KVO应用在NSOperation中,可以监听一个Operation是否完成或取消,这样子能比GCD更加有效地掌控我们执行的后台任务; 在NSOperation中,我们能够设置NSOperation的priority优先级,能够使同一个并行队列中的任务区分先后地执行,而在GCD中,我们只能区分不同任务队列的优先级,如果要区分block任务的优先级,也需要大量的复杂代码; 我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将block任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。 总的来说,Operation queue 提供了更多你在编写多线程程序时需要的功能,并隐藏了许多线程调度,线程取消与线程优先级的复杂代码,为我们提供简单的API入口。从编程原则来说,一般我们需要尽可能的使用高等级、封装完美的API,在必须时才使用底层API。但是我认为当我们的需求能够以更简单的底层代码完成的时候,简洁的GCD或许是个更好的选择,而Operation queue 为我们提供能更多的选择。]]></content>
</entry>
<entry>
<title><![CDATA[GCD与多线程]]></title>
<url>%2F2016%2F05%2F19%2FGCD%E4%B8%8E%E5%A4%9A%E7%BA%BF%E7%A8%8B%2F</url>
<content type="text"><![CDATA[前言 GCD 的简单介绍 纯C语言, 提供了非常多且强大的函数 会自动利用更多的CPU内核 程序员只需要告诉GCD想要执行什么任务, 不需要编写任何线程管理代码 在ARC中,编译器会自动管理GCD的内存, 不需要考虑内存释放 MRC中需要程序员手动调用 dispatch_release() 操作, 全局并发队列不需要考虑内存释放 GCD的两个核心概念队列队列管理开发者提交的任务, GCD队列始终以FIFO(先进先出)的方式来处理任务—-但由于任务的执行时间并不相同, 因此先处理的任务并不一定先结束. 队列可以是串行队列,也可以是并发队列. 队列底层会维护一个线程池来处理用户提交的任务,线程池的作用就是执行队列管理的任务。串行队列底层的线程池只要维护一个线程即可,并发队列的底层则需要维护多个线程。 串行队列 串行队列底层的线程池只有一个线程, 每次只提供一个线程处理一个任务, 所以必须前一个任务执行完成后,才能执行下一个任务. 并发队列 (dispatch_async) 线程池提供多个线程来执行任务, 可以按FIFO的顺序并发启动, 可同时处理多个任务, 因此会有多个任务并发执行. 任务用户提交给队列的工作单元, 这些任务将会提交给队列底层维护的线程池执行, 因此会以多线程的方式执行. GCD 的介绍 创建队列 将任务提交给队列 队列的创建和访问12345678//创建队列dispatch_queue_t queue = dispatch_get_global_queue(0, 0);//创建任务dispatch_block_t task = ^{ NSLog(@"--- %@", [NSThread currentThread]);};//异步执行dispatch_async(queue, task); 123dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"--- %@", [NSThread currentThread]); }); 同步执行 sync1void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block); 异步执行 async1void dispatch_async(dispatch_queue_t queue, dispatch_block_t block); 创建队列的常见方式获取系统默认的全局并发队列(与并发队列区别: 不能设置名字, 也就不能追踪错误) 第一个参数: 根据指定服务质量(即优先级), 有4种 (2 0 -2 和 BACKGROUND), 默认是0 (DEFAULT) 第二个参数: 额外的旗标, 暂未使用, 为以后准备, 一般用 0 1234dispatch_queue_t queue = dispatch_get_global_queue(0, 0);``` 获取系统主线程关联的串行队列(主队列/全局串行队列) dispatch_queue_t queue = dispatch_get_main_queue();12创建串行队列 dispatch_queue_t queue = dispatch_queue_create(<#const char *label#>, DISPATCH_QUEUE_SERIAL);12创建并发队列 dispatch_queue_t queue = dispatch_queue_create(const char *label, DISPATCH_QUEUE_CONCURRENT);12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849> 组合方式: 同步异步决定开不开线程,串行并行决定任务按不按顺序执行或者开多少线程---串行队列, 同步执行 ----> 在当前线程执行(不开新线程), 任务按顺序执行, 不能嵌套, 否则死锁 !---串行队列, 异步执行 ----> 开启一个新线程执行, 任务在新开辟的子线程中按顺序执行 ---并发队列, 同步执行 ----> 在当前线程执行(不开新线程), 任务按顺序执行---并发队列, 异步执行 ----> 开启多个新线程, 任务随机执行---主队列, 同步执行 ----> 死锁: 主线程先执行完主线程上的代码, 才会执行主队列的任务,同步执行会等第一个任务(此时任务在主线程)执行完成才会继续往后执行. > 解决方法: 把队列任务放在子线程中执行, 即在外部创建一个异步执行的全局并发队列---主队列, 异步执行 ----> 在主线程执行, 任务按顺序执行, 主线程先执行完主线程上的代码, 才会执行主队列的任务(队列中的任务优先级低) ---全局并发队列, 同步执行 ----> 在当前线程执行(不开新线程), 任务按顺序执行---全局并发队列, 异步执行 ----> 开启多个新线程执行, 任务随机执行---注意点: 当线程执行完任务之后,会被放在线程池中, 不会被立即销毁, 可能会被再次复用, 但一段时间之后没有复用就会被销毁.### GCD与NSThrea对比- GCD 通过block执行代码, 更加简单, 便于维护, 而NSThread 通过@selector 执行代码, 代码比较分散- 使用GCD不需要管理线程生命周期- 如果要开多个线程, NSThread 需要实例化多个线程对象.## GCD 的应用### 线程阻塞 dispatch_barrier主要用于在多个异步操作完成之后,对非线程安全的对象 (类似于NSMutableArray) 进行统一更新, 适合大规模的I/O操作.使用dispatch_barrier_async 添加的block会在之前添加的block全部运行结束之后,才会在同一个线程顺序执行, 从而保证对非线程安全的对象进行正确的操作. 参数不能用全局队列,只能用自定义队列.![](/img/GCD与多线程/GCD01.png)### 延迟执行 dispatch_after void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);```经过多少纳秒, 队列才开始调度任务 一次性执行 dispatch_once多用于单例, 整个项目中只会执行一次, 内部也有一把锁, 可以保证线程安全.]]></content>
</entry>
<entry>
<title><![CDATA[指针与数组浅谈]]></title>
<url>%2F2016%2F02%2F24%2F%E6%8C%87%E9%92%88%E4%B8%8E%E6%95%B0%E7%BB%84%2F</url>
<content type="text"><![CDATA[指针指针与地址 指针在内存中占用8(64位),并且,指针里面能存地址,指针有自己的地址。 指针里面的存的地址可以改变,但是地址本身不能变化。 指针有类型概念,地址只是一个16进制的常量。 指针本身可以移动指向新的数据空间,并且指针移动一位字节数不一样(由类型决定)。 指针有两层含义:1. 表示一个能存地址的变量(等效于指针变量)2. 还含有数据类型的概念。 指针的内存布局先看一个例子: 1int *p; 这里定义了一个指针p,一个“int *”类型的模子在内存上咔出了8个字节(根据编译器环境不同而不同,64位是8个字节,32位是4个字节)的空间,然后这个空间命令为p,同时限定这4个字节的空间里面只能存储某个内存地址,即使你存入别的任何数据,都被当做地址来处理,而且这个内存地址开始的连续4个字符上只能存储某个int类型的数据。 如上图所示,我们把p称为指针变量,p里存储的内存地址处的内存称为p所指向的内存。指针变量p里存储的任何数据都将被当作地址来处理。 我们可以这么理解:一个基本的数据类型(包括结构体等自定义类型)加上”*“号就构成了一个指针类型的模子。这个模子的大小是一定的,与”*“号前面的数据类型无关。”“号前面的数据类型只是说明指针所指向的内存里存储的数据类型。所以,在64位系统下,不管什么样的指针类型,其大小都是8byte,sizeof(void )也是8个字节。 注意int *p = NULL 和 *p = NULL的区别 1int *p = NULL; p的值为 0x00000000。解释为:定义一个指针变量p,其指向的内存里面保存的是int类型的数据,在定义变量p的同时把p的值设置为0x00000000,而不是把*p的值设置为0x00000000。这个过程叫做初始化,是在编译的时候进行的。 然后再看下面的代码: 12int *p; //定义一个指针变量p,指向内存里面保存的是int类型的数据,但是此时p本身的值不知道,也就是说现在变量p保存的可能是一个非法地址*p = NULL; //给*p赋值为NULL,即给p指向的内存赋值为NULL,但是由于p指向的内存可能是非法的,所以编译器会报告一个内存错误 因此,我们可以改写上面的代码,使p指向一块合法的内存 123int i = 10;int *p = &i;*p = NULL; 调试的时候可以发现:p所指向的内存地址存储的数据从10变成了0,但是p本身的值,也就是内存地址并没有变。 备注:NULL是一个宏定义 1#define NULL 0 如何将数值存储到指定的内存地址假设现在需要往内存0x12ff7c地址上存入一个整型数0x100。我们怎么才能做到呢?我们知道可以通过一个指针向其指向的内存地址写入数据,那么这里的内存地址0x12ff7c其本质就是一个指针。所以我们可以用下面的方法: 12int *p = (int *)0x12ff7c; //将地址0x12ff7c赋值给指针变量p的时候必须强制转换*p = 0x100; 也可以这么写: 1*(int *)0x12ff7c = 0x100; 数组数组的内存布局先看一个例子 1int a[5]; 上面定义了一个数组,其包含了5个int型的数据,我们可以用a[0], a[1]等来访问数组里面的每一个元素,那么这些元素的名字就是a[0], a[1]…吗?我们看一个图: 如上图所示,当我们定义一个数组a时,编译器根据指定的元素个数和元素的类型分配确定大小(元素类型大小*元素个数)的一块内存,并把这块内存的名字命名为a,名字a一旦与这块内存匹配就不能改变。a[0], a[1]等为a的元素,但并非元素的名字。数组的每一个元素都是没有名字的。那现在再看一下sizeof和数组的几个问题: 在64位系统环境下:(sizeof关键字求值是在编译时) 1234567891011#include <stdio.h>int main(int argc, char const *argv[]){ int a[5]; printf("%lu\n", sizeof(a)); // 20 printf("%lu\n", sizeof(&a)); // 8 printf("%lu\n", sizeof(a[0])); // 4 printf("%lu\n", sizeof(&a[0])); // 8 return 0;} &a[0]与&a的区别 a[0]是一个元素, a是整个数组,虽然&a[0]和&a的值一样,但其意义不一样。前者是数组首元素的首地址,而后者是数组的首地址。比如:湖南的省政府在长沙,而长沙的市政府也在长沙,两个政府都在长沙,但其代表的意义完全不同。这里也是同一个意思。 字符数组字符串是一个以’\0’结尾的字符数组,是一串字符。定义及初始化:char arr[] = “abc”; 或 char arr[4] = {‘a’, ‘b’, ‘c’, ‘d’, ‘\0’};输出:printf(“%s\n”, s); 或 printf(“%s\n”, &arr[0]);赋值:strcpy(字符变量名,”字符串”); 特点 后面必须有’\0’结尾,否则只是普通的字符数组,但是’\0’不会输出,只表示字符串结束。 字符串输出占位用%s,必须遇到’\0’才会结束,否则会继续输出更高位地址值的字符。 strlen函数用于计算一个字符串的长度(字符常量),使用必须引入。 strlen不会计算’\0’,且碰到’\0’结束,但是sizeof不受’\0’映像,且长度会包含’\0’. 字符串一定是字符数组,但字符数组不一定是字符串。 123456789#include <stdio.h>int main(int argc, char const *argv[]){ char s[] = "abcd"; char s1[] = {'a', 'b', 'c'}; printf("%s\n", s1); // adcabcd 在内存访问到'\0'为止,所以连s一起输出 return 0;} 指针与数组以指针的形式访问和以下标的形式访问 指针与数组之间似是而非的特点。例如,有如下定义: A) char *p = "abcdef"; B) char a[] = "123456"; 以指针的形式访问和以下标的形式访问指针 A定义了一个指针变量p,p本身在栈上占8个字节,p里面存储的是一块内存的首地址。这块内存在静态区,其空间大小为7个byte,这块内存没有名字,对这块内存的访问完全是匿名的访问。比如要读取字符’e’,我们有两种方式: 1)以指针的形式:*(p+4) 先取出p里存储的地址值,假设为0x0000FF00,然后加上4个字符的偏移量,得到新的地址0x0000FF04,然后取出0x000FF04地址上的值。 2)以下标的形式:p[4] 编译器总是把下标的形式的操作解析为以指针的形式的操作。p[4]这个操作会被解析成:先取出p里存储的地址值,然后加上中括号中4个元素的偏移量,计算出新的地址,然后从新的地址中取出值,也就是说以下标的形式访问在本质上与指针的的形式访问没有区别,只是写法上不同罢了。 以指针的形式访问和以下标的形式访问数组 B定义了一个数组a,a拥有7个char类型的元素,其空间大小为7。数组a本身在栈上面。对a的元素的访问必须先根据数组的名字a找到数组首元素的首地址,然后根据偏移量找到相应的值。这是一种典型的“具名 + 匿名”访问。 指针和数组是两个完全不一样的东西,只是它们都可以“以指针形式”或“以下标形式”进行访问。一个是完全的匿名访问,一个是典型的具名+匿名访问。一定要注意的是这个“以XXX的形式的访问”这种表达方式。 偏移量的单位是元素的个数而不是byte数,计算地址时需注意。 a和&a的区别我们看一个例子: 12345678#include <stdio.h>int main(int argc, char const *argv[]){ int a[5] = {1,2,3,4,5}; int *ptr = (int *)(&a+1); printf("%d, %d\n", *(a+1), *(ptr-1)); // 2 5 return 0;} 这个例子主要考察关于指针加减操作的理解 对指针进行加1操作,得到的是下一个元素的地址,而不是原有地址值直接加1.所以一个类型为T的指针的移动,以sizeof(T)为移动单位。因此,a是一个一维数组,数组中有5个元素:ptr是一个int型的指针。 &a+1:取数组a的首地址,该地址的值加上sizeof(a)的值,即 &a + 5*sizeof(int),也就是下一个数组的首地址,显然当前指针已经越过了数组的界限。 (int )(&a+1):则是把上一步计算出来的地址,强制转化为int 类型,赋值给ptr。 (a + 1):a,&a的值是一样的,但意思不一样,a是数组首元素的首地址,也就是a[0]的首地址,&a是数组的首地址,a+1是数组下一元素的首地址,即a[1]的首地址。&a+1是下一个数组的首地址,所以输出2. *(ptr - 1):因为ptr是指向a[5],并且ptr是int类型,所以\(ptr-1)是指向a[4],输出5。 指针与数组的特性总结 指针 数组 保存数据的地址,任何存入指针变量p的数据都会被当做地址来处理。p本身的地址由编译器另外存储,存储在哪里,我们并不知道。 保存数据,数组名a代表的是数组首元素的首地址而不是数组的首地址。&a才是整个数组的首地址。a本身的地址由编译器另外存储,存储在哪里,我们并不知道。 间接访问数据,首先取得指针变量p的内容,把它作为地址,然后从这个地址提取数据或向这个地址写入数据。指针可以以指针的形式访问(p+i); 也可以以下标的形式访问p[i]。但本质都是先取p的内容然后加上isizeof(类型)个byte作为数据的真正地址。 直接访问数据,数组名a是整个数组的名字,数组内每个元素并没有名字。只能通过“具名+匿名”的方式来访问某个元素,不能把数组当一个整体来进行读写操作。数组可以以指针的形式访问(a+i); 也可以以下标的形式访问 a[i]。但其本质都是a所代表的数组首元素的首地址加上isizeof(类型)个byte作为数据的真正地址。 通常用于动态数据结构 通常用于存储固定数目且数据类型相同的元素。 相关的函数为malloc和free 隐式分配和删除 通常指向匿名数据(当然也可指向具名数据) 自身即为数组名 指针数组和数组指针指针数组和数组指针的内存布局 指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“存储指针的数组”的简称。 数组指针:首先它是一个指针,它指向一个数组,在64位系统下永远是占4个字节,至于它指向的数组占多少字节,并不知道。它是“指向数组的指针”的简称。 12A) int *p1[10]; //指针数组B) int (*p2)[10]; //数组指针 这里有一个符号优先级的问题,‘[]’的优先级比‘*’要高。 p1先于‘[]’结合,构成一个数组的定义,数组名为p1,int修饰的数组的内容,即数组的每个元素。总的来讲,这是一个数组,其包含10个指向int类型数据的指针,即指针数组。 p2中‘()’的优先级比‘[]’高,‘*’号和p2构成一个指针的定义,指针变量名为p2,int修饰的是数组的内容,即每个数组的每个元素。数组在这里并没有名字,是一个匿名数组。总的讲,p2是一个指针,它指向一个包含10个int类型数据的数组,即数组指针。 也许可以这么理解数组指针 通常定义指针是在数据类型后面加上指针变量名,那p2定义如下: int (*)[10] p2; int () a与&a之间的区别1234567891011121314#include <stdio.h>int main(int argc, char const *argv[]){ char a[5] = {'A', 'B', 'C', 'D'}; char (*p1)[3] = &a; char (*p2)[3] = a; char (*p3)[5] = &a; char (*p4)[5] = a; /* 输出 D D BCD BCD 乱码 乱码 偏移单位由数组指针中的数组长度决定 */ printf("%s\n%s\n%s\n%s\n%s\n%s\n", *(p1+1), *(p2+1), *p1+1, *p2+1, *(p3+1), *(p4+1)); return 0;} 地址的强制转换先看一个例子 12345678struct Test{ int Num; char *pcName; short sDate; char cha[2]; short sBa[4];} *p; 假设p的值为0x100000,那么: p + 0x1 = 0x100018 (unsigned long)p + 0x1 = 0x100001 (unsigned int *)p + 0x1 = 0x100004 首先需要明白一个知识点,指针变量与一个整数相加减并不是用指针变量里的地址直接加减这个整数。这个整数的单位不是byte二十元素的个数。所以: p+0x1的值为0x100000+sizeof(Test) * 0x1。至于此结构体的大小为20byte,所以p + 0x1的值为:0x100014。 (unsigned long)p+0x1的值涉及到强制类型转换,将指针变量p保存的值强制类型转换成无符号的长整型数。任何数值一旦被强制转换,其类型就改变了。所以这个表达式其实就是一个无符号的长整型数加另一个整数,所以其值为:0x1000001。 (unsigned int *)p+0x1中,这里的p被强制转换成一个指向无符号整型的指针,所以其值为:0x1000000+sizeof(unsigned int) * 0x1,等于0x100004。 二维数组与指针先看一个例子: 1234int a[5][5];int (*p)[4];p = a;//&p[4][2] - &a[4][2]的值是? 答案是 -4 &a[4][2]表示的是: &a[0][0]+4*5*sizeof(int) + 2*sizeof(int)。 p[4]相对于p[0]来说是向后移动了4个“包含4个int 类型元素的数组”, 即&p[4]表示的是&p[0]+4*4*sizeof(int)。由于p被初始化为&a[0],那么&p[4][2]表示的是&a[0][0] + 4*4*sizeof(int) + 2*sizeof(int)。 所以,&p[4][2]和&a[4][2]的值相差4个int类型的元素。可以用下面的内存布局图来表示: 二级指针二级指针的内存布局char **p; 定义了一个二级指针变量 p。p 是一个指针变量,毫无疑问在 64 位系统下占 8 个 byte。 它与一级指针不同的是,一级指针保存的是数据的地址,二级指针保存的是一级指针的地址。 注意: 任何指针变量都可以被初始化为NULL。 数组参数与指针参数数组作为参数传递到函数里面时,传入的是地址,占8位。数组并没有传递至函数内部。 C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。非数组形式的数据实参均以传值形式(对实参做一份拷贝并传递给被调用的函数,函数不能修改作为实参的实际变量的值,而只能修改传递给它的那份拷贝)调用。同样,函数的返回值也不能是一个数组,而只能是指针。明确一个概念:函数本身是没有类型的,只有函数的返回值才有类型。 数组参数 12345678910void fun(cahr a[]){ char c = a[3];}int main(){ char b[100] = "abcdefg"; fun(b); return 0;} 指针参数 12345678910void fun(char *p){ char c = p[3]; //或者是 char c = *(p+3);}int main() { char *p2 = "abcdefg"; fun(p2); return 0;} 上面的例子是对实参做一份拷贝并传递给被调用的函数,即对p2做一份拷贝,传递到函数内部的并非p2本身。 1234567891011void getMemory(char *p, int num){ p = (char *)malloc(num*sizeof(char));}int main() { char *str = NULL; GetMemory(str, 10); strcpy(str, "hello"); free(str); //free 并没有起作用,内存泄漏 return 0; } 在运行 strcpy(str,”hello”)语句的时候发生错误。这时候观察 str 的值,发现仍然为 NULL,也就是说 str 本身并没有改变。 所以,我们可以这么做: 123456789101112//第一: 用 return。char * GetMemory(char * p, int num) { p = (char *)malloc(num*sizeof(char)); return p; }int main() { char *str = NULL; str = GetMemory(str,10); strcpy(str, "hello"); free(str); return 0; } 123456789101112// 第二:用二级指针。void GetMemory(char ** p, int num) { *p = (char *)malloc(num*sizeof(char)); return p; }int main() { char *str = NULL; GetMemory(&str,10); strcpy(str,”hello”); free(str); return 0; } 注意,这里的参数是&str而非str。这样的话传递过去的是str的地址,是一个值。在函数内部,用钥匙 “”来开锁:(&str),其值就是str,所以malloc分配的内存地址是真的赋值给了str本身。 二维数组参数与二维指针参数1void fun(char a[3][4]); 可以把 a[3][4]理解为一个一维数组 a[3],其每个元素都是一个含有 4 个 char 类型数据的数组。“C 语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。”在这里同样适用。所以我们可以把这个函数声明改写成: void fun(char (*p)[4]); //括号绝对不能省略,这样才能保证编译器把p解析为一个指向包含4个char类型数据元素的数组,即一维数组a[3]的元素。 同样,一维数组“[]”内的数字完全可以省略,不过第二维的维数不能省略。 void fun(char a[][4]);或者写为(这是因为参数*p[4],对于 p 来说,它是一个包含 4 个指针的一维数组,同样把这个一维数组也改写为指针的形式,那就得到上面的写法。): void fun(char **p); 二维数组和二维指针参数的等效关系: 数组参数 等效的指针参数 数组的数组:char a[3][4] 数组的指针:char (*p)[10] 指针数组: char *a[5] 指针的指针:char **p 需要注意:C 语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。这条规则并不是递归的,也就是说只有一维数组才是如此,当数组超过一维时,将第一维改写为指向数组首元素首地址的指针之后,后面的维再也不可改写。]]></content>
</entry>
<entry>
<title><![CDATA[消息传递]]></title>
<url>%2F2016%2F02%2F19%2F%E6%B6%88%E6%81%AF%E4%BC%A0%E9%80%92%2F</url>
<content type="text"><![CDATA[消息传递/objc_msgSendOC中沿用smalltalk 语法,通过 [] 调用方法, 用 OC 术语来说, 叫做”传递消息“, 消息有 “名称” 或 “选择器(selector)”. C 语言使用”静态绑定“的函数调用方式, 也就是说: 在编译期就能决定运行时所应调用的函数, 编译器在编译代码的时候就已经知道程序中有对应函数,直接生成调用这些函数的指令,而函数地址实际上是硬编码在指令中. 在 OC 中,如果向某对象传递消息,那就会使用动态绑定机制来决定需要调用的方法. 在底层,所有方法都是普通的 C 语言函数,然后对象收到消息之后,究竟该调用哪个方法则完全由运行期决定,甚至可以在程序运行时改变,这些特性使得 OC 成为一门真正的动态语言. 给对象发消息可以这样写: 1id returnValue = [someObject messageName: parameter]; 编译器看到此消息后,将其转换为一条标准的 C 语言函数调用, 所调用的函数乃是消息机制中的核心函数 –> objc_msgSend, 其原型如下: 1void objc_msgSend(id self, SEL cmd, ...) 这是个参数个数可变的函数,编译器会把上面的消息转换为如下函数: 1id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter); objc_msgSend 函数会一句接收者和选择器的类型来调用适当的方法:a. 该方法需要在接收者所属的类中搜寻其”方法列表”b. 找到与选择器名称相符的方法就跳转至实现代码c. 找不到则沿着集成体系继续向上查找, 等找到合适的方法之后再进行跳转d. 如果最终还是找不到相符的方法, 那就执行”消息转发”操作 之所以能跳转至实现代码是因为 OC 对象的每个方法都可以视为简单的 C 函数: 1<return_type> Class_selector(id self, SEL _cmd, ...) 注意: objc_msgSend 会将匹配结果缓存在”快速映射表”里面, 每个类都有这样一块缓存, 再向该类发送与选择器相同的消息执行速度就会很快, 当然这种”快速执行路径”还是不如”静态绑定的函数调用操作”迅速。 每个类里都有一张表格, 其中的指针都会指向这种函数, 而选择器的名称则是查表时所用的”键”, objc_msgSend 等函数正是通过这张表格来寻找应该执行的方法并跳转至其实现的.此函数和 objc_msgSend 函数很像, 是为了利用”尾调用优化”技术,令跳转至方法实现这一操作变得更简单快捷. 尾调用优化 函数调用会在内存形成一个”调用记录”,又称”调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用记录上方,还会形成一个B的调用记录。等到B运行结束,将结果返回到A,B的调用记录才会消失。如果函数B内部还调用函数C,那就还有一个C的调用记录栈,以此类推。所有的调用记录,就形成一个”调用栈”(call stack)。尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用记录,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用记录,取代外层函数的调用记录就可以了。 如果某函数的最后一项操作是调用另外一个函数, 那就可以运用”尾调用优化”技术。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。这就是”尾调用优化”的意义。 上面所描述的是大部分消息的调用过程, 还有很多特殊情况,需要交给 OC 运行环境中的另一些函数来处理:objc_msgSend_stret: 待发送的消息返回结构体则可以交由此函数处理.(只有当 CPU 的寄存器能够容纳得下消息返回类型时此函数才处理消息) objc_msgSend_fpret: 消息返回的是浮点数时交由此函数处理. objc_msgSendSuper: 给超类发消息,如 [super message: parameter], 那么就交由此函数处理. 消息转发/message forwarding 机制在编译期,当对象收到无法解读的消息时,就会启动“消息转发(message forwarding)”机制,程序员可以经由此过程告诉对象应该如何处理未知消息。 消息转发具体过程: 征询接收者,看所属的类是否能动态添加方法以处理当前这个未知的选择器(selector) –> 这叫做动态方法解析 运行期系统会请求接收者以其他手段来处理与消息相关的方法调用: 首先请接收者看看有没有其他对象能处理此消息,有则把消息转给那个对象,消息转发过程结束。 没有备援的接收者则启动完整的消息转发机制,运行期系统会把与消息有关的全部细节都封装到 NSInvocation 对象中,再给接收者最后一次几乎,令其设法解决当前还未处理的这条消息。 动态方法解析当对象收到无法解读的消息后,首先调用其所属类的下列类方法: 1+ (BOOL)resolveInstanceMethod: (SEL)selector 该方法参数就是那个未知的选择器,表示这个类是否能新增一个 实例方法用以处理此选择器,本类有机会新增一个处理此选择器的方法。—-> 前提: 相关方法的实现代码已经写好,只等着运行的时候动态插在类里面就可以了。此方案常用来实现@dynamic 属性。 备援接收者第二次机会能处理未知的选择器,在这一步中,运行期系统会问它:能不能把这条消息转给其他接收者来处理(快速消息转发 –> 简单、快速,但只能发给一个对象): 1- (id)forwardingTargetForSelector: (SEL)selector 此方法返回备援对象,可以通过此方案用“组合”来模拟出“多重继承”的某些特性。 完整的消息转发(标准消息转发)创建 NSInvocation 对象,把尚未处理的消息及相关细节封于其中,包括选择器,目标和参数。在触发 NSInvocation 对象时,“消息派发系统”将亲自出马把消息指派给目标对象。调用下列方法来转发消息: 1- (void)forwardInvocation: (NSInvocation *)invocation 在触发消息前,先以某种方式改变消息内容,比如追加另外一个参数,或是改变选择器等。 实现此方法时,若发现某调用操作不应由本类处理,则需调用超类的同名方法,这样继承体系中的每个类都有机会处理此调用请求,直至 NSObject,如果最后调用了 NSObject 类的方法,那么该方法还会继而调用“doesNotRecognizeSelector:”以抛出异常,表明选择器最终未能得到处理。 消息转发机制处理消息的步骤 接收者在每一步中均有机会处理消息,越往后处理消息的代价越高。 要点: 若对象无法响应某个选择器, 则进入消息转发流程。 通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再加入类中。 对象可以把其无法解读的某些选择器转交给其他对象来处理。 经过上述两步之后,如果还是没办法处理选择器,则启动完整的消息转发机制。 示例代码: 12345678910111213141516171819202122232425262728293031#pragma mark - 快速消息转发 --> 简单、快速、但仅能转发给一个对象- (id)forwardingTargetForSelector:(SEL)aSelector { Person *per = [Person new]; // 把消息转发给 person 对象 if ([per respondsToSelector:aSelector]) { // 如果 person 对象可以响应此消息则返回 person 对象 return per; } // 否则返回 nil return nil;}#pragma mark - 标准消息转发 --> 稍复杂、较慢、但转发操作实现可控,可以实现多对象转发- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { Person *per = [Person new]; NSMethodSignature *signature = [super methodSignatureForSelector:aSelector]; if (signature == nil) { signature = [per methodSignatureForSelector:aSelector]; } NSUInteger argCount = [signature numberOfArguments]; for (int i = 0; i < argCount; ++i) { NSLog(@" -- "); } return signature;}- (void)forwardInvocation:(NSInvocation *)anInvocation { Person *per = [Person new]; SEL seletor = [anInvocation selector]; if ([per respondsToSelector:seletor]) { [anInvocation invokeWithTarget:per]; }}]]></content>
</entry>
<entry>
<title><![CDATA[应用内检查更新]]></title>
<url>%2F2015%2F11%2F25%2F%E7%BB%98%E5%88%B6%E6%96%87%E6%9C%AC%E5%92%8C%E5%9B%BE%E7%89%87%2F</url>
<content type="text"><![CDATA[绘制文本1234567891011121314151617181920NSString *str = @"这是一个测试文本!"; //创建属性字典 NSMutableDictionary *dict = [NSMutableDictionary dictionary]; //字体大小 dict[NSFontAttributeName] = [UIFont systemFontOfSize:30]; //字体颜色 dict[NSForegroundColorAttributeName] = [UIColor redColor]; //线条宽度,数值越小,空心字越明显 dict[NSStrokeWidthAttributeName] = @5; //设置阴影,偏移量(5, 3),模糊程度3,颜色为绿色 CGContextSetShadowWithColor(ctx, CGSizeMake(5, 3), 3, [UIColor greenColor].CGColor); /* drawAtPoint以point的位置决定起始点,不会自动换行,而drawInRect由rect的区域决定,自动换行 */// [str drawAtPoint:CGPointMake(50, 50) withAttributes:dict]; [str drawInRect:rect withAttributes:dict]; 绘制图片123456789101112 // UIKit绘图,画图片 UIImage *image = [UIImage imageNamed:@"头像"]; // 设置矩形剪裁区域,一定要在渲染之前 // 把超出剪裁区域的部分全部剪裁掉 UIRectClip(CGRectMake(0, 0, 50, 50)); // 平铺// [image drawAsPatternInRect:rect]; // 默认的尺寸就是图片的尺寸// [image drawAtPoint:CGPointZero]; [image drawInRect:CGRectMake(0, 0, 100, 100)]; 绘制扇形实现斜射放大123456789CGContextRef ctx = UIGraphicsGetCurrentContext();for (int i = 0; i < 10; ++i) { CGContextBeginPath(ctx); // 开始定义路径 // 添加一段圆弧,最后一个参数1代表逆时针,0代表顺时针 CGContextAddArc(ctx, i * 25, i * 25, (i+1)*8, M_PI * 1.5, M_PI, 0); //设置填充颜色 CGContextSetRGBFillColor(ctx, 1, 0, 1, (10 - i) * 0.1); CGContextFillPath(ctx);}]]></content>
</entry>
<entry>
<title><![CDATA[事件传递与事件处理]]></title>
<url>%2F2015%2F11%2F24%2F%E4%BA%8B%E4%BB%B6%E4%BC%A0%E9%80%92%E4%B8%8E%E4%BA%8B%E4%BB%B6%E5%A4%84%E7%90%86%2F</url>
<content type="text"><![CDATA[事件处理基本了解 iOS中的事件分为三大类:触摸事件,加速计事件,远程控制事件。 事件的第一接收者是runloop,然后传递给application –> delegate 响应者对象:只有继承了UIResponder 的对象才能接收并处理事件,称之为响应者对象(只要能处理事件的对象)。 触摸事件分类: 1234touchesBegan: withEvent: touchesMoved: withEvent: 手指在视图上移动的时候调用,调用频率高。touchesEnded: withEvent:touchesCancelled: withEvent: 触摸结束前,被打断触摸过程则调用此事件,比如来电话。 UIView 是UIResponder 的子类,可以处理触摸事件 事件传递事件的产生和传递 发⽣触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中,先进先出 UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow) 主窗口会在视图层次结构中找到⼀个最合适的视图来处理触摸事件,但是这仅仅是整个事件处理过程的第⼀步 找到合适的视图控件后,就会调用视图控件的touches⽅法来作具体的事件处理 view不能接收事件的三种情况: 不能进行用户交互,enable = NO 隐藏,hidden = YES 透明度小于等于0.01 子控件不能接收父控件也不能接收的事件 如何找到最合适的控件来处理事件: 自己能否接收触摸事件?否,事件传递到此结束 触摸点是否在自己身上?否,事件传递到此结束 从后往前遍历自己的子控件,重复前两个步骤 (这里的从后往前是指添加顺序,时间上的顺序) 如果没有符合条件的子控件,那么就自己处理 hitTest方法的底层实现:123456789101112131415161718192021222324- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { //判断能否接收事件 if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) { return nil; } //判断触摸点在不在自己上 if (![self pointInside:point withEvent:event]) { return nil; } //从后往前遍历子控件 for (NSInteger i = self.subviews.count-1; i >= 0; --i) { //取出子控制器 UIView *view = self.subviews[i]; //把当前控件坐标系的点转换成子控件坐标系的点 CGPoint hitPoint = [view convertPoint:point fromView:self]; //让子控件寻找合适的view UIView *hitView = [view hitTest:hitPoint withEvent:event]; if (hitView) { //找到合适的view return hitView; } } //没有找到比自己合适的view,返回自己 return self;} 应用scrollView上的view响应手势scrollView上面添加view做为子控件,view上面添加拖拽或轻扫手势,让view手势有效,需要如下设置 1234567- (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.delaysContentTouches = NO; self.canCancelContentTouches = YES; } return self;} 123- (BOOL)touchesShouldCancelInContentView:(UIView *)view { return NO;}]]></content>
</entry>
<entry>
<title><![CDATA[控制器View的创建 (loadView)]]></title>
<url>%2F2015%2F11%2F18%2F%E6%8E%A7%E5%88%B6%E5%99%A8View%E7%9A%84%E5%88%9B%E5%BB%BA%20%EF%BC%88loadView%EF%BC%89%2F</url>
<content type="text"><![CDATA[在创建控制器的时候会自动创建view 12345self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];//加载storyboardUIStoryboard *sb = [UIStoryboard storyboardWithNibName:@"Main" bundle:nil];self.window.rootViewController = [sb instantiateInitialViewController];[self.window makeKeyAndVisible]; UIViewController 中有一个方法:loadView,它会去加载指定的storyboard中所描述的控制器的view 作用:创建控制器view 调用:当我们第一次使用到控制器的view时就会去调用loadView 只要重写loadView方法,就需要自己创建控制器的view,系统不再自动创建 loadView底层:(Xcode 7之前) 判断loadView方法中有没有创建view 然后判断有没有指定storyboard,若有,则加载指定storyboard描述的控制器的view, 再判断有没有指定nibName,有,则加载nibName描述的控制器的view 如果没有指定nibName: 即nibName为nil时,尝试先去找和控制器同名,但是不带Controller的xib 再然后寻找带Controller的xib loadView底层:(Xcode 7或之后) 判断loadView方法中有没有创建view,有,则加载自定义view 然后判断有没有指定storyboard,若有,则加载指定storyboard描述的控制器的view, 再判断有没有指定nibName,有,则加载nibName描述的控制器的view 如果没有指定nibName: 即nibName为nil时,尝试先去找和控制器同名带Controller的xib 再然后寻找不带Controller的xib 默认控制器view的颜色是几乎透明的透明(alpha = 0)具有穿透效果,类似于hidden = YES;而默认创建的view为clear color,没有穿透效果]]></content>
</entry>
<entry>
<title><![CDATA[控制器的加载]]></title>
<url>%2F2015%2F11%2F18%2F%E6%8E%A7%E5%88%B6%E5%99%A8%E7%9A%84%E5%8A%A0%E8%BD%BD%2F</url>
<content type="text"><![CDATA[纯代码创建控制器12ViewController *vc = [[ViewController alloc] init];self.window.rootViewController = vc; 加载storyboard中的控制器方法一:加载初始显示的控制器,即箭头指向的控制器) 加载storyboard,用UIStoryboard加载,类似于加载xib文件,传入文件名不需要带后缀,nil:主bundle,代表[NSBundle mainBundle] 1UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@Main" bundle:nil]; 12ViewController *vc = [storyboard instantiateInitialViewController];self.window.rootViewController = vc; 方法二:绑定标识符 1UIStoryboard *storyboard = [UIStoryboard instantiateInitialViewControllerWithIdentifier:@"vc"]; 加载xib文件的控制器创建xib文件后,需要设置关联类,然后自己添加一个view,并进行连线,否则不能加载viewController 123self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];self.window.rootViewController = [[ViewController alloc] initWithNibName:@"VC" bundle:nil];[self.window makeKeyAndVisible];]]></content>
</entry>
</search>