-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
224 lines (158 loc) · 193 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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[浅谈第三方 SDK 和自定义 crash 上报冲突问题]]></title>
<url>%2Fposts%2F35916%2F</url>
<content type="text"><![CDATA[简介在开发 iOS 应用,解决 Crash 问题始终是一个难题。Crash 分为两种,一种是由 EXC_BAD_ACCESS 引起的,原因是访问了不属于本进程的内存地址,有可能是访问已被释放的内存;另一种是未被捕获的 Objective-C 异常,导致程序向自身发送了 UNIX 信号而崩溃。对于这两种 Crash 的捕获,精准高效的收集线上崩溃可以帮助我们更好的解决问题和提高用户体验,现在比较成熟的崩溃收集工具也比较多,比如:友盟统计,Crashlytics,腾讯的 bugly 等等。也可以通过自定义 crash 上报,来处理异常。 问题但是当自定义 crash 上报收集工具与第三方 crash 收集工具共存的时候,发现自定义 crash 上报收集工具,不能捕获到 exception。 原因12/* Set the uncaught exception handler */NSSetUncaughtExceptionHandler(&uncaught_exception_handler); 由于 crash 的捕获机制只会保存最后一个注册的 handle,不管是第三方还是自定义的上报收集工具,一般会常用注册 ExceptionHandler 的方式,来捕获 exception,但是并没有将异常进行抛出,所以,当工程中使用了第三方上报收集工具进行异常统计的时候,我们自己写的异常捕获有可能会失效。 解决办法解决冲突的做法是在注册自己的 handle 之前保存已注册的处理函数,便于发生崩溃后能将 crash 信息连续的传递下去。 首先保存第三方的 ExceptionHandler,然后在设置自己处理 exception 的 ExceptionHandler,在自己的 ExceptionHandler 处理完异常之后,再将异常塞给之前的第三方 ExceptionHandler。 代码思路简单解释12345static NSUncaughtExceptionHandler *_previousHandler; // 保存第三方处理异常的 handler _previousHandler = NSGetUncaughtExceptionHandler(); // 设置自己处理异常的 handler NSSetUncaughtExceptionHandler(&UncaughtExceptionHandlerYourself); 处理自己的异常12void UncaughtExceptionHandlerYourself (NSException *exception) {} 处理完自己的逻辑之后就需要把 exception 赋值给我们之前保存的handler,否则第三方就无法统计到崩溃的数据。1_previousHandler(exception); 完整版代码如下123456789101112131415161718192021222324252627282930313233343536373839404142434445464748struct sigaction my_action;static struct sigaction registered_action;static NSUncaughtExceptionHandler *previousHandle;void signal_handler(int signal) { NSLog(@"This is where we save the application data during a signal %d",signal);}void exception_handler(NSException *exception) { NSString * url = [NSString stringWithFormat:@"========异常错误报告========\nname:%@\nreason:\n%@\ncallStackSymbols:\n%@",[exception name],[exception reason],[[exception callStackSymbols] componentsJoinedByString:@"\n"]]; NSLog(@"%@",url); // 处理完以后,调用前者的handler处理exception,让他们也有数据,如果不做处理,之前的注册的都将无法获取到exception if (previousHandle) { previousHandle(exception); } }void registerCrashHandle() { previousHandle = NSGetUncaughtExceptionHandler(); NSSetUncaughtExceptionHandler(&exception_handler); my_action.sa_handler = &signal_handler; sigemptyset(&my_action.sa_mask); sigaction(SIGABRT, &my_action, &registered_action);}static struct sigaction existActions[32];static int fatal_signals[] = { SIGILL, SIGBUS, SIGABRT, SIGPIPE,};- (void)checkRegisterCrashHandler { struct sigaction oldAction; for (int idx = 0; idx < sizeof(fatal_signals) / sizeof(int); idx++) { sigaction(fatal_signals[idx], NULL, &oldAction); if (oldAction.sa_handler != &signal_handler) { existActions[fatal_signals[idx]] = oldAction; struct sigaction myAction; myAction.sa_handler = &signal_handler; sigemptyset(&myAction.sa_mask); sigaction(SIGABRT, &myAction, NULL); } }} 在didFinishLaunchingWithOptions函数调用 12345- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {//在实际测试时候我延时了2秒钟之后才会获取到异常,正是因为项目用了友盟被优先调用了registerCrashHandle()} 总结,通过这种方法,第三方和我们上报收集工具,都可以正常的收集崩溃数据。第三方上报收集工具捕获到异常,再处理完自己的逻辑后,将 exception 抛了出来,我们自己的崩溃日志收集库收到第三方上报收集工具抛出来的 exception,处理完自己的逻辑后再将 exception 抛给第三方上报收集工具的 handler。 参考文章https://www.jianshu.com/p/c8f731d18518https://www.jianshu.com/p/a1d02e2a9048]]></content>
</entry>
<entry>
<title><![CDATA[IGListKit介绍简单使用]]></title>
<url>%2Fposts%2F31852%2F</url>
<content type="text"><![CDATA[本文主要介绍IGListKit的Objective-C的用法,目前GitHub上的Demo和文档都是Swift版本的,介于我们开发语言是Objective-C所以我也查了网上相关的一些篇幅介绍IGListKit的使用但都已经是过时的版本了(Swift不是很熟练所以有部分Swift代码还不能顺利翻译为OC),特别说明的是熟练于OC和Swift开发的应该看着官方的Demo就能直接写出OC版本了,这里主要给懒人在这里啰嗦的,毕竟大家都很懒😂。。。。 简书地址Demo下载 IGListKit的介绍这是我从网上抄了一部分关于IGListKit的介绍 IGListKit 这个库是在 try! Swift NYC 这个会议上,这是由 Instagram 开发的应用在自身 App 上的一个 UI 组件库。基于UICollectionView的框架,使用数据驱动,旨在创造一个更快更灵活的列表控件。这个框架设计的非常好,完美符合高内聚、低耦合,意在替代UICollectionView,在我的使用中也确实体会到了它的优势。IGListKit 并不是由 Swift 开发的一个库,它依然使用了 Objective-C 为主要语言开发。我想是因为 Instagram 作为已经五年以上的 App,也就是一定是一个 Objective-C 项目。它运作良好,没有特殊原因没必要用 Swift 重写。所以它的核心 UI 组件库 IGListKit 依然是 Objective-C 也是正常的。 IGListKit 优势 不要再调用 performBatchUpdates(_:, completion:) 或者 reloadData() 。 具有可重用的单元和组件的更好的体系结构 创建具有多个数据类型的集合 解耦的差异算法 充分的单元测试 为你的模块自定义你的差异行为 可扩展的API 使用Objective-C编写,具有全Swift互操作支持 IGListKit应用使用IGListKit后UICollectionView的协议UICollectionDataSource和UICollectionViewDelegate不用再使用,取而代之的是SectionController来实现对应的方法,由IGListAdapter把UICollectionView绑定在一起。 IGListAdapter的初始化1234567891011self.collectionView=[[UICollectionView alloc]initWithFrame:CGRectZero collectionViewLayout:[[UICollectionViewFlowLayout alloc]init]]; self.collectionView.backgroundColor=[UIColor whiteColor]; IGListAdapterUpdater*updater=[[IGListAdapterUpdater alloc]init]; _adapter=[[IGListAdapter alloc]initWithUpdater:updater viewController:self workingRangeSize:0]; _adapter.collectionView=self.collectionView; _adapter.dataSource=self; [self.view addSubview:self.collectionView]; IGListAdapter的协议123456789101112131415161718192021- (nullable UIView *)emptyViewForListAdapter:(nonnull IGListAdapter *)listAdapter { //没有数据时候的View UILabel *label=[UILabel new]; label.text=@"没有数据"; label.textAlignment=NSTextAlignmentCenter; return label;}- (nonnull IGListSectionController *)listAdapter:(nonnull IGListAdapter *)listAdapter sectionControllerForObject:(nonnull id)object { DemoSectionController *section=[[DemoSectionController alloc]init]; return section; }- (nonnull NSArray<id<IGListDiffable>> *)objectsForListAdapter:(nonnull IGListAdapter *)listAdapter { //数据源 return self.dataArray; } IGListSectionController的方法1234567891011121314151617181920212223242526272829303132- (NSInteger)numberOfItems{ //每个section返回多少行根据数据来写 一般可能就是1 return 3;}- (CGSize)sizeForItemAtIndex:(NSInteger)index{ return CGSizeMake(self.collectionContext.containerSize.width, 46); }-(UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index{ DemoCollectionViewCell*cell=[self.collectionContext dequeueReusableCellOfClass:[DemoCollectionViewCell class] forSectionController:self atIndex:index]; [cell setModel:_object row:index]; return cell; }- (void)didSelectItemAtIndex:(NSInteger)index { NSLog(@"点击Section%ld row%ld",(long)self.section,(long)index);}- (void)didUpdateToObject:(id)object{ //数据传输到这里 _object =(DemoModel *)object; } IGListKit结构 IGListDiffable and EqualityIGListDiffable使用的是Paul Heckel 的A technique for isolating differences between files 的算法,简单来说这个算法就是计算collectionView前后数据变化增删改移关系的一个算法,算是IGListKit的特色特点之一 。 实现差异比较需要自定义model遵守IGListDiffable协议并实现12345678910111213- (nonnull id<NSObject>)diffIdentifier { return self; }- (BOOL)isEqualToDiffableObject:(nullable id<IGListDiffable>)object { if (self == object) { return YES; } else if (self == nil || object == nil) { return NO; } return YES;}]]></content>
</entry>
<entry>
<title><![CDATA[记一次js调起QQ临时对话]]></title>
<url>%2Fposts%2F61865%2F</url>
<content type="text"><![CDATA[本以为很简单个功能还是遇到了些坑 QQ临时会话说明从浏览器唤起QQ进行聊天,是很多公司或者企业会用到的一种客服方式,然而很多时候,一些手机端浏览器并不支持直接跳转到QQ,或者不支持从App内嵌的网页中跳转到QQ页面 QQ推广如果QQ开启一个临时需要对话则需要开通QQ推广,网址http://shang.qq.com/v3/index.html然后登录开通QQ推广功能是免费的呃 代码实现部分支持PC浏览器和苹果安卓手机浏览器123456789101112131415161718192021<a href="javascript:void(0);" onclick="chatQQ()">咨询客服</a><script> function chatQQ(){ /*550057210对应的就是需要聊天的客服*/ var kefu101 = "http://wpa.qq.com/msgrd?v=3&uin=550057210&site=oicqzone.com&menu=yes"; var kefu102 = "mqqwpa://im/chat?chat_type=wpa&uin=550057210&version=1&src_type=web&web_src=oicqzone.com"; if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent) || /(Android)/i.test(navigator.userAgent)) { // window.open(kefu102); window.location.href=kefu102; }else { // window.open(kefu101); window.location.href=kefu101; } }</script> 用windows.open经过测试安卓的浏览器不能正常跳转QQ会打开腾讯的网页,但 window.location.href可以正常跳转,这两种方法iOS则没什么问题。]]></content>
</entry>
<entry>
<title><![CDATA[App托管和企业签名web封包]]></title>
<url>%2Fposts%2F45048%2F</url>
<content type="text"><![CDATA[在世界杯决赛的热烈进行中,我们正式启动App托管平台https://51zan.cc,网站可以托管安卓和苹果App,速度快价格便宜简单实用。https://51zan.cc已通过备案托管于阿里云,速度杠杠的,安全可靠。51分发可以合并安卓和iOS应用为一个下载链接,其实呢主要还是托管企业证书签名的iOS应用的安卓则辅之,用苹果手机的童靴都知道苹果下载应用只能在App Store下载,不能像安卓一样可以任何形式安装到手机上。 如果各位童靴的公司有应用或者游戏不能通过App Store的审核又想让大众用户下载怎么办呢,(吐槽下我们的App已经连续被拒了3次了哭晕了🙄)别担心我们的服务之一就是给App用企业证书签名让苹果手机用户可以像安卓一样通过一条链接就能安装App到手机,当然了是收费的了,联系我可以优惠哦😘。 通过签名服务的App可以直接在我们的51分发托管了,可谓是一条龙服务了。 我们的另外一项服务就是web封包,简单说就是把一个兼容手机浏览器的网页做成一个App可以是安卓可以是苹果,当然我们会负责打包上线,不过审不收费,详情还是联系我了解呗。详情咨询QQ:550057210]]></content>
</entry>
<entry>
<title><![CDATA[免费获取Office365提供的5T云盘]]></title>
<url>%2Fposts%2F16454%2F</url>
<content type="text"><![CDATA[目前,提供网盘服务的厂家一个一个关闭,360云盘,Uc网盘等等无一幸免。免费大容量网盘供应商只剩百度一家独大,用户边骂边用,还不得不用,更新之后在线观看还出了一个会员加速无等待,真的是zang的一批。 众所周知,微软的 Onedrive 个人版容量只有5G,如果要升级容量,就必须购买 Office365 服务。但有种情况例外,Office365 服务对学生用户是免费的,只要你使用教育网邮箱注册就会获得免费的大容量 Onedrive 存储空间。 那么问题来了,如何获取一个教育网邮箱?今天就给大家提供个方法,使用教育网邮箱免费获取 Office365 服务。 一、什么是Office365? Office 365 是一种订阅式的跨平台办公服务,基于云平台提供多种服务,并包括最新版的Office套件,支持在多个设备上安装Office应用。Office 365采取订阅方式,可灵活按年或月续费,获得最新服务和软件 Office 2016与Office 365不同,它是微软推出的Office套件,适用于单台PC或Mac,包括Word、Excel、PowerPoint、OneNote、Outlook、Access、Publisher、Onedrive等应用程序(在不同版本中的应用程序不同)。Office 2016采取永久授权方式, 一次性购买,可无限期使用,不能自动更新获得新功能 没错,Office 365包含了Onedrive,但是Office 365不是免费的,是收费收费收费的,但是如果你是在校的大学生,就可以免费使用Office 365,可见国外的月亮就是圆,重视教育undefined。那么如何判断你是大学生呢?就是你必须有一个教育邮箱,相信很多造价猿刚入大学的时候,学校会给你个邮箱用来部署作业和课学安排,邮件的地址必须是edu(教育)域名:[email protected]@[email protected] 类似这个样子可以免费申请Office 365,免费获得Onedrive 1TB空间,如果你的大学不是使用edu域名的,说明不是什么正规大学,不过也没关系,下面我教大家如何获得免费的Office365教育账户。 二、免费获得Office365教育账户 1、首先打开http://get365.pw/。这个是最快最有效的获取教育邮箱的途径。这是微软公司认证过的一个教育域名,从该站点获取临时邮箱用来接收验证码,生成地址后保留网页,还要查收验证码。获取一个教育邮箱,用于接收验证码,打开get365.pw 2、打开 Office365 教育版注册页面https://signup.microsoft.com/signup?sku=student,填写刚刚得到的邮箱,点击注册 3、切换到get365.pw页面,这时你会收到一个验证码 4、输入相关信息 5、后面按图操作 三、利用GoodSync自动同步到Onedrive 1、GoodSync下载地址:点我下载,直接下一步安装即可 四、总结 如何获得Office365的Onedrive免费网盘,关键一个可以收到验证码的教育邮箱,您可能会问,你之前不是说必须edu后缀的教育邮箱,[email protected]并不是edu后缀啊,这个get365.pw是一个大佬已经经过微软公司认证过的一个教育域名,为了保险起见,建议大家如果你有edu后缀的邮箱,还是用edu的,不过只有1TB,存资料足够了。获取edu教育邮箱的几中途径: 1、淘宝购买,关键词:edu教育、Onedrive教育、office365教育版、office365 edu 分别搜索,不过要让卖家测试是否能用GoodSync授权,我买过2个由于各种原因没有授权成功 2、注册国内外学校的大学邮箱,国内几乎都无法注册,如果你有表姐表妹啥的可以借她们账户啊,反正只是用来接收一个验证码。国外的有很多,不过都是英文,你可以用浏览器自带的翻译辅助注册 3、善用搜索引擎,自己动手,丰胸足食Q:Office365网站怎么登录A:https://login.microsoftonline.com Q:GoodSync无法授权Office365账户A:如果是新注册,请等待2小时后,一定使用我提供的下载地址版本,我之前用的一个版本也是无法授权PS:题外话 get365安全性还未知,管理员可以看到你的资料,但是,这个已经发出来几个月了,注册的人数没有十万也有八万了,你认为管理员有时间专门看你的资料吗?但是管理员能看到你的资料是真的。相信你也不会放什么私密的资料在云盘。想用微软的OneDrive的方法还有:申请个国外的edu,那么虽然大部分也有管理,毕竟是学校的,要比私人的域名资料安全上稍微有点保障。悄悄告诉你:有些国外的edu是谷歌账户,拿这些申请微软的365是没有全局的,这样你不担心谁能看到你资料了吧,使用下面的链接注册即可 https://www.microsoft.com/zh-cn/education/products/office/default.aspx]]></content>
</entry>
<entry>
<title><![CDATA[教你一招如何快速下载一整部电视剧、电影、磁力链接到百度云]]></title>
<url>%2Fposts%2F62088%2F</url>
<content type="text"><![CDATA[如何快速下载一整部电视剧、电影、磁力链接到百度云,这里用到两个工具百度云,Tampermonkey Tampermonkey的安装方法以及介绍看我的一篇Tampermonkey安装文章 安装成功后,打开脚本大全,找到一键下载,安装脚本,这个脚本就是本文用的一键导入到百度云离线下载的关键 这是我安装的一些脚本 脚本安装成功了可以设置一些离线下载的云盘 开始本篇的下载流程打开一个种子搜索网站http://diggbts.com/,我搜索到九州牧云记打开如下图,磁力链接后边会显示我设置的百度云离线下载图标 点击后会自动跳转到百度网盘开始自动导入离线下载,下边图示是下载一个电影的,过程如同整部电视剧一样 当然了遇到的某些资源可能也下载不了的就是资源有问题了。 这么强大的磁力链接下载功能我可阻止不了你拿来干什么😂😂]]></content>
</entry>
<entry>
<title><![CDATA[“Mac应用”已损坏,打不开解决办法]]></title>
<url>%2Fposts%2F38676%2F</url>
<content type="text"><![CDATA[在MAC下安装一些软件时提示”来自身份不明开发者”,其实这是MAC新系统启用了新的安全机制。默认只信任 Mac App Store 下载的软件和拥有开发者 ID 签名的应用程序。换句话说就是 MAC 系统默认只能安装靠谱渠道(有苹果审核的 Mac App Store)下载的软件或被认可的人开发的软件。 这当然是为了用户不会稀里糊涂安装流氓软件中招,但没有开发者签名的 “老实软件” 也受影响了,安装就会弹出下图所示警告框:“打不开 xxx,因为它来自身份不明的开发者”。 教程修改系统配置:系统偏好设置… -> 安全性与隐私。系统偏好设置 安全性与隐私默认的第一项App Store换成第三项任何来源 认证 弹出确认框选择允许任何来源 修改为任何来源(没有此选项看文章结尾处)注意:如果没有这个选项的话(macOS Sierra 10.12及以上),打开终端(不知道在哪的可以直接桌面右上角搜索关键字:终端),执行sudo spctl --master-disable即可 特别注意:终端输入密码默认不显示(字面意思就是你虽然输入了密码,但是你会发现什么都没有,不要紧张这是苹果的保护机制,你继续输完你的密码回车就OK了)]]></content>
</entry>
<entry>
<title><![CDATA[电脑浏览器必装插件Tampermonkey]]></title>
<url>%2Fposts%2F13557%2F</url>
<content type="text"><![CDATA[Tampermonkey油猴插件是Chrome上最流行的用户脚本管理插件了,当然现在可以安装到各大浏览器了(悄悄地告诉你现在安卓的火狐浏览器也可以安装了其他请自测),可以通过安装脚本实现破解vip视频、百度网盘资源直接下载等实用功能,堪称神器。没接触过的用户还不知道Tampermonkey油猴插件怎么使用,本文详细介绍油猴插件的安装和脚本使用方法。 软件简介:Tampermonkey,这是一个伟大的Chrome扩展。可以说,就算Chrome没有其他扩展,只有Tampermonkey,Chrome依然能吸引到一大群死忠。当然,也只有高手中的高手,才能领略到Tampermonkey的优美。 Tampermonkey的魅力其实是脚本文件的魅力。现代的网页特效缤纷,外观出彩,其中CSS样式表和JavaScript脚本起到了不可或缺的作用。比如说,PConline的首页上,焦点图的切换、装机系统的实现等效果就是通过CSS和JS脚本来实现的。 Tampermonkey通过加载第三方的脚本文件,改变页面中的CSS和JS元素,可以让整个网页大变样,也可以在网页中增加额外的功能。 当然,Chrome原生也是可以加载第三方脚本文件的,不过没有Tampermonkey那样支持良好。Tampermonkey除了能对脚本文件提供更好的支持以外,还有Tamperfire功能,能够根据站点寻找相适应的脚本文件。 Tampermonkey下载只要支持的浏览器打开http://tampermonkey.net/就可以直接安装了以下是常用浏览器安装: 搜狗浏览器: http://ie.sogou.com/app/search/Tampermonkey 360安全浏览器: https://ext.se.360.cn/webstore/search/tampermonkey 360极速浏览器:https://ext.chrome.360.cn/webstore/search/tampermonkey 火狐浏览器: https://addons.mozilla.org/zh-CN/firefox/addon/tampermonkey/?src=search 遨游浏览器: http://extension.maxthon.cn/detail/index.php?view_id=1680&category_id= 欧朋浏览器: https://addons.opera.com/zh-cn/extensions/details/tampermonkey-beta/?display=en用谷歌浏览器的估计大部分会翻墙了如果不会,我觉得有必要卸载了。可以离线安装可以在线商店安装 推荐几个安装的脚本:百度网盘直接下载助手:https://greasyfork.org/zh-CN/scripts/23635解决百度云大文件下载限制:https://greasyfork.org/zh-CN/scripts/17800优化百度-搜狗-谷歌搜索结果之重定向去除-去广告:https://greasyfork.org/zh-CN/scripts/14178知乎免登陆:https://greasyfork.org/zh-CN/scripts/6489vip视频在线解析:https://greasyfork.org/zh-CN/scripts/27349网页强制复制:https://greasyfork.org/zh-CN/scripts/218网盘提取工具:https://greasyfork.org/zh-CN/scripts/18733 更多脚本:Greasy Fork - 安全、有用的用户脚本大全:https://greasyfork.org/zh-CN 脚本使用每个脚本的下载页面的下面一般都有使用教程 直接应该可以看懂 号外号外最后来围观下购物省钱的网站吧,百万商家优惠券领不完,直通车 最后看下我的安装列表 挥泪求关注]]></content>
</entry>
<entry>
<title><![CDATA[整理了pc和手机观看各大视频网站的VIP视频的方法]]></title>
<url>%2Fposts%2F39117%2F</url>
<content type="text"><![CDATA[电脑1:利用Tampermonkey插件Tampermonkey 是一款免费的浏览器扩展和最为流行的用户脚本管理器,它适用于 Chrome, Microsoft Edge, Safari, Opera Next, 和 Firefox。 不区分Mac电脑和Windows电脑使用。 Tampermonkey下载地址,安装到浏览器,各大浏览器的安装方法我就不一一说明了可以度娘,只提供下谷歌浏览器的方法吧,直达地址,这提供离线安装的方法,不再详细说怎么翻墙了,有需要的可以留言。顺便推荐下让广告商哭晕的广告屏蔽插件AdBlock ,安装好了 Tampermonkey就开始说怎么开启观看vip视频的方法吧, 了解了Tampermonkey就知道它其实还是给浏览器扩展插件用的,接下直接点击这里,如果顺利可以看到如下界面 可以看到包含了[优酷|腾讯|乐视|爱奇艺|芒果|AB站|音悦台]等VIP或会员视频在哪里可以找到这些插件呢 ?https://greasyfork.org/zh-CN/scripts,可以看看肯定能找到自己的需求。看下我安装的插件视频插件安装好了就打开爱奇艺试试吧,找到九州牧云记60集是VIP看到左上角的黄色图标了吗这里提供了很多视频解析网站,推荐百域阁这个方法呢虽然麻烦但是最为稳定了。你搞懂了吗……没有???再看一遍!!!还不会留言吧 2:有范VIP插件直达地址http://vip.ufanw.com/,这个我也不多介绍了,同样适合大部分浏览器安装好就可以使用了,上边的方法一搞定了这个太随意了。不过访问时候谷歌报危险了。。。。。话说这种类似插件多不胜数。。。。。 安卓手机版方法1你造嘛Tampermonkey同样可以安装到手机的火狐浏览器上使用的,方法同上边介绍的 方法2还记得刚才说的百域阁吗?看视频的时候把地址拷贝出来,放到http://api.baiyug.cn/vip/index.php?url= 后边就行了http://api.baiyug.cn/vip/index.php?url=视频地址, 哎举个例子吧 ,找到羞羞的铁拳http://www.iqiyi.com/v_19rr7pmvcw.html?fc=82992814760eeac6是VIP的,那么拼接下http://api.baiyug.cn/vip/index.php?url=http://www.iqiyi.com/v_19rr7pmvcw.html?fc=82992814760eeac6在手机和电脑都可以看了,哈哈哈 方法3那么多的视频视频解析网站用法都一样,就是拿到视频的地址后放到各个视频解析网站直接播放就可以了,推荐个小源VIP解析 苹果版方法1刚才安卓的方法2看了吗同样适合苹果就是拼接地址放到手机浏览器就可以看了 方法2哎,这个小神器,我都不想说苹果商店搜索VIP浏览器打开就可以看各个网站VIP视频了,不想找卧槽….地址砸你脸上 扯淡我就是懒想直接打开视频就要看百域阁:http://baiyug.cn/,打开即可找到目前火的视频,如果网速可以的话是能看的,你懂得…不稳定 其实安卓肯定有类似苹果这个VIP浏览器的只是没有安卓机器也没找,如果是手机打开百域阁,是可以看到右上边下载app的,各位可以自己试试 号外号外最后来围观下购物省钱的网站吧,百万商家优惠券领不完,直通车]]></content>
</entry>
<entry>
<title><![CDATA[解决手机和电脑迅雷下载速度限制]]></title>
<url>%2Fposts%2F53144%2F</url>
<content type="text"><![CDATA[1.widows版本迅雷这里提供的是迅雷迅雷U享版,官方介绍迅雷U享版是一款面向会员用户推出的会员专属的版本,把界面上所有跟下载无关的部分全部去掉了,它没有广告,没有插件,没有强制升级,没有浏览器,只专注一个核心功能,那就是下载。迅雷U享版,纯净无广告,只为下载而生!极致简洁,仅保留最基本的下载功能,回归下载初心。 再来一个苹果电脑上未加速的下载速度对比看的出速度相差甚远,对比了多个相同资源发现U享版速度可以提升二倍不止。 使用U享版方法先去官方下载原版迅雷并安装http://down.sandai.net/ThunderVIP/ThunderVIP3.1.1.186.exe然后下载破解补丁 下边是补丁地址链接: https://pan.baidu.com/s/1qYHqHg4 密码: ikb9 2.安卓版安卓破解迅雷高速通道下载地址链接: https://pan.baidu.com/s/1gf1tX4J 密码: hn2j 3.苹果版用过苹果迅雷的都知道商店下架很久了如果想用可以使用企业版本的迅雷,直接复制后边的完整地址到Safari浏览器即可安装了http://ithunder-ota.a.88cdn.com/download-guide/index.html?from=gongzhonghao然后打开会提醒允许同意开发者即可,不知道如何操作点击这里查看方法 4.Mac版由于苹果电脑上的破解资源少还没找到…..我用的是虚拟机装的windows系统😑 号外号外最后来围观下购物省钱的网站吧,百万商家优惠券领不完,直通车]]></content>
</entry>
<entry>
<title><![CDATA[iOS各种线程锁的介绍及使用]]></title>
<url>%2Fposts%2F41966%2F</url>
<content type="text"><![CDATA[线程锁在平时的开发中经常使用到多线程,在使用多线程的过程中,难免会遇到资源竞争的问题,那我们怎么来避免出现这种问题那?线程安全是什么?当一个线程访问数据的时候,其他的线程不能对其进行访问,直到该线程访问完毕。简单来讲就是在同一时刻,对同一个数据操作的线程只有一个。只有确保了这样,才能使数据不会被其他线程影响。而线程不安全,则是在同一时刻可以有多个线程对该数据进行访问,从而得不到预期的结果。比如写文件和读文件,当一个线程在写文件的时候,理论上来说,如果这个时候另一个线程来直接读取的话,那么得到的结果可能是你无法预料的。怎么来保证线程安全?通常我们使用锁的机制来保证线程安全,即确保同一时刻只有同一个线程来对同一个数据源进行访问。YY大神 的 不再安全的 OSSpinLock 这边博客中列出了各种锁以及性能比较:这里性能比较的只是加锁立马解锁的时间消耗,并没有计算竞争时候的时间消耗。锁的介绍及简单使用1.@synchronized@synchronized是 iOS 中最常见的锁,用法很简单: 123456789101112131415161718192021222324- (void)viewDidLoad { [super viewDidLoad]; [self synchronized];}- (void)synchronized { NSObject * cjobj = [NSObject new]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @synchronized(cjobj){ NSLog(@"线程1开始"); sleep(3); NSLog(@"线程1结束"); } }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); @synchronized(cjobj){ NSLog(@"线程2"); } });} 控制台输出: 1232017-10-18 11:35:13.459194+0800 Thread-Lock[24855:431100] 线程1开始2017-10-18 11:35:16.460210+0800 Thread-Lock[24855:431100] 线程1结束2017-10-18 11:35:16.460434+0800 Thread-Lock[24855:431101] 线程2 从上面的控制台输出时间可以看出来,在线程 1 内容全部输出之后,才输出了线程 2 的内容,“线程1结束”与“线程2”都是在“线程1开始”3 秒后输出的。@synchronized(cjobj) 指令使用的 cjobj 为该锁的唯一标识,只有当标识相同时,才为满足互斥,如果线程 2 中的 @synchronized(cjobj) 改为 @synchronized(self) ,那么线程 2 就不会被阻塞,@synchronized 指令实现锁的优点就是我们不需要在代码中显式的创建锁对象,便可以实现锁的机制,但作为一种预防措施,@synchronized 块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。@sychronized(cjobj){} 内部 cjobj 被释放或被设为 nil 不会影响锁的功能,但如果 cjobj 一开始就是 nil,那就会丢失了锁的功能了。2.NSLock先看看iOS中NSLock类的.h文件,从代码中可以看出,该类分成了几个子类:NSLock、NSConditionLock、NSRecursiveLock、NSCondition,然后有一个 NSLocking 协议: 1234@protocol NSLocking- (void)lock;- (void)unlock;@end 虽然 NSLock、NSConditionLock、NSRecursiveLock、NSCondition 都遵循的了 NSLocking 协议,但是它们并不相同。2.1 NSLockNSLock 实现了最基本的互斥锁,遵循了 NSLocking 协议,通过 lock 和 unlock 来进行锁定和解锁。源码内容: 1234567891011@interface NSLock : NSObject <NSLocking> {@private void *_priv;}- (BOOL)tryLock;- (BOOL)lockBeforeDate:(NSDate *)limit;@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));@end 用法: 12345678910111213141516171819202122232425- (void)viewDidLoad { [super viewDidLoad]; [self nslock];}- (void)nslock { NSLock * cjlock = [NSLock new]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cjlock lock]; NSLog(@"线程1加锁成功"); sleep(2); [cjlock unlock]; NSLog(@"线程1解锁成功"); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); [cjlock lock]; NSLog(@"线程2加锁成功"); [cjlock unlock]; NSLog(@"线程2解锁成功"); });} 控制台输出: 12342017-10-19 15:03:58.868708+0800 Thread-Lock[39059:846493] 线程1加锁成功2017-10-19 15:04:00.872714+0800 Thread-Lock[39059:846493] 线程1解锁成功2017-10-19 15:04:00.872722+0800 Thread-Lock[39059:846492] 线程2加锁成功2017-10-19 15:04:00.873000+0800 Thread-Lock[39059:846492] 线程2解锁成功 123456789101112131415161718192021- (void)nslock { NSLock * cjlock = [NSLock new]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cjlock lock]; NSLog(@"线程1加锁成功"); sleep(2); [cjlock unlock]; NSLog(@"线程1解锁成功"); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if ([cjlock tryLock]) { NSLog(@"线程3加锁成功"); [cjlock unlock]; NSLog(@"线程3解锁成功"); }else { NSLog(@"线程3加锁失败"); } });} 控制台输出: 1232017-10-19 15:05:38.627767+0800 Thread-Lock[39118:849171] 线程1加锁成功2017-10-19 15:05:38.627767+0800 Thread-Lock[39118:849169] 线程3加锁失败2017-10-19 15:05:40.629969+0800 Thread-Lock[39118:849171] 线程1解锁成功 123456789101112131415161718192021- (void)nslock { NSLock * cjlock = [NSLock new]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cjlock lock]; NSLog(@"线程1加锁成功"); sleep(2); [cjlock unlock]; NSLog(@"线程1解锁成功"); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(3); if ([cjlock tryLock]) { NSLog(@"线程4加锁成功"); [cjlock unlock]; NSLog(@"线程4解锁成功"); }else { NSLog(@"线程4加锁失败"); } });} 控制台输出: 123452017-10-19 15:07:14.872279+0800 Thread-Lock[39166:851060] 线程1加锁成功2017-10-19 15:07:16.876108+0800 Thread-Lock[39166:851060] 线程1解锁成功2017-10-19 15:07:17.876208+0800 Thread-Lock[39166:851052] 线程4加锁成功2017-10-19 15:07:17.876527+0800 Thread-Lock[39166:851052] 线程4解锁成功 1234567891011121314151617181920- (void)nslock { NSLock * cjlock = [NSLock new]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cjlock lock]; NSLog(@"线程1加锁成功"); sleep(2); [cjlock unlock]; NSLog(@"线程1解锁成功"); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if ([cjlock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]) { NSLog(@"线程5加锁成功"); [cjlock unlock]; NSLog(@"线程5解锁成功"); }else { NSLog(@"线程5加锁失败"); } });} 控制台输出: 12342017-10-19 15:08:39.705131+0800 Thread-Lock[39204:852782] 线程1加锁成功2017-10-19 15:08:41.708717+0800 Thread-Lock[39204:852782] 线程1解锁成功2017-10-19 15:08:41.708717+0800 Thread-Lock[39204:852784] 线程5加锁成功2017-10-19 15:08:41.708935+0800 Thread-Lock[39204:852784] 线程5解锁成功 注意:lock与unlock操作必须在同一线程,否则结果不确定甚至会引起死锁 由以上内容总结: 除 lock 和 unlock 方法外,NSLock 还提供了 tryLock 和 lockBeforeDate:两个方法。 由上面的结果可以看到 tryLock 并不会阻塞线程,[cjlock tryLock] 能加锁返回 YES,不能加锁返回 NO,然后都会执行后续代码。 这里顺便提一下 trylock 和 lock 使用场景:当前线程锁失败,也可以继续其它任务,用 trylock 合适;当前线程只有锁成功后,才会做一些有意义的工作,那就 lock,没必要轮询 trylock。以下的锁都是这样。 lockBeforeDate: 方法会在所指定 Date 之前尝试加锁,会阻塞线程,如果在指定时间之前都不能加锁,则返回 NO,指定时间之前能加锁,则返回 YES。 由于是互斥锁,当一个线程进行访问的时候,该线程获得锁,其他线程进行访问的时候,将被操作系统挂起,直到该线程释放锁,其他线程才能对其进行访问,从而却确保了线程安全。但是如果连续锁定两次,则会造成死锁问题。2.2 NSRecursiveLockNSRecursiveLock 是递归锁,顾名思义,可以被一个线程多次获得,而不会引起死锁。它记录了成功获得锁的次数,每一次成功的获得锁,必须有一个配套的释放锁和其对应,这样才不会引起死锁。NSRecursiveLock 会记录上锁和解锁的次数,当二者平衡的时候,才会释放锁,其它线程才可以上锁成功。源码内容: 1234567891011@interface NSRecursiveLock : NSObject <NSLocking> {@private void *_priv;}- (BOOL)tryLock;- (BOOL)lockBeforeDate:(NSDate *)limit;@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));@end 用法: 123456789101112131415161718192021222324- (void)viewDidLoad { [super viewDidLoad]; [self nsrecursivelock];}- (void)nsrecursivelock{ NSRecursiveLock * cjlock = [[NSRecursiveLock alloc] init]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^RecursiveBlock)(int); RecursiveBlock = ^(int value) { [cjlock lock]; NSLog(@"%d加锁成功",value); if (value > 0) { NSLog(@"value:%d", value); RecursiveBlock(value - 1); } [cjlock unlock]; NSLog(@"%d解锁成功",value); }; RecursiveBlock(3); });} 控制台输出: 12345678910112017-10-19 16:15:40.584213+0800 Thread-Lock[39579:894111] 3加锁成功2017-10-19 16:15:40.584387+0800 Thread-Lock[39579:894111] value:32017-10-19 16:15:40.584552+0800 Thread-Lock[39579:894111] 2加锁成功2017-10-19 16:15:40.584635+0800 Thread-Lock[39579:894111] value:22017-10-19 16:15:40.584810+0800 Thread-Lock[39579:894111] 1加锁成功2017-10-19 16:15:40.585267+0800 Thread-Lock[39579:894111] value:12017-10-19 16:15:40.585714+0800 Thread-Lock[39579:894111] 0加锁成功2017-10-19 16:15:40.585906+0800 Thread-Lock[39579:894111] 0解锁成功2017-10-19 16:15:40.586138+0800 Thread-Lock[39579:894111] 1解锁成功2017-10-19 16:15:40.586217+0800 Thread-Lock[39579:894111] 2解锁成功2017-10-19 16:15:40.586314+0800 Thread-Lock[39579:894111] 3解锁成功 由以上内容总结:如果用 NSLock 的话,cjlock 先锁上了,但未执行解锁的时候,就会进入递归的下一层,而再次请求上锁,阻塞了该线程,线程被阻塞了,自然后面的解锁代码不会执行,而形成了死锁。而 NSRecursiveLock 递归锁就是为了解决这个问题。2.3 NSConditionLockNSConditionLock 对象所定义的互斥锁可以在使得在某个条件下进行锁定和解锁,它和 NSLock 类似,都遵循 NSLocking 协议,方法都类似,只是多了一个 condition 属性,以及每个操作都多了一个关于 condition 属性的方法,例如 tryLock、tryLockWhenCondition:,所以 NSConditionLock 可以称为条件锁。 只有 condition 参数与初始化时候的 condition 相等,lock 才能正确进行加锁操作。 unlockWithCondition: 并不是当 condition 符合条件时才解锁,而是解锁之后,修改 condition 的值。源码内容: 123456789101112131415161718@interface NSConditionLock : NSObject <NSLocking> {@private void *_priv;}- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;@property (readonly) NSInteger condition;- (void)lockWhenCondition:(NSInteger)condition;- (BOOL)tryLock;- (BOOL)tryLockWhenCondition:(NSInteger)condition;- (void)unlockWithCondition:(NSInteger)condition;- (BOOL)lockBeforeDate:(NSDate *)limit;- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));@end 用法: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647- (void)viewDidLoad { [super viewDidLoad]; [self nsconditionlock];}- (void)nsconditionlock { NSConditionLock * cjlock = [[NSConditionLock alloc] initWithCondition:0]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cjlock lock]; NSLog(@"线程1加锁成功"); sleep(1); [cjlock unlock]; NSLog(@"线程1解锁成功"); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); [cjlock lockWhenCondition:1]; NSLog(@"线程2加锁成功"); [cjlock unlock]; NSLog(@"线程2解锁成功"); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(2); if ([cjlock tryLockWhenCondition:0]) { NSLog(@"线程3加锁成功"); sleep(2); [cjlock unlockWithCondition:2]; NSLog(@"线程3解锁成功"); } else { NSLog(@"线程3尝试加锁失败"); } }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if ([cjlock lockWhenCondition:2 beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]) { NSLog(@"线程4加锁成功"); [cjlock unlockWithCondition:1]; NSLog(@"线程4解锁成功"); } else { NSLog(@"线程4尝试加锁失败"); } });} 控制台输出: 123456782017-10-19 15:09:44.010992+0800 Thread-Lock[39230:853946] 线程1加锁成功2017-10-19 15:09:45.012045+0800 Thread-Lock[39230:853946] 线程1解锁成功2017-10-19 15:09:46.012692+0800 Thread-Lock[39230:853947] 线程3加锁成功2017-10-19 15:09:48.016536+0800 Thread-Lock[39230:853947] 线程3解锁成功2017-10-19 15:09:48.016564+0800 Thread-Lock[39230:853944] 线程4加锁成功2017-10-19 15:09:48.017039+0800 Thread-Lock[39230:853944] 线程4解锁成功2017-10-19 15:09:48.017040+0800 Thread-Lock[39230:853945] 线程2加锁成功2017-10-19 15:09:48.017215+0800 Thread-Lock[39230:853945] 线程2解锁成功 由以上内容总结: 在线程 1 解锁成功之后,线程 2 并没有加锁成功,而是继续等了 1 秒之后线程 3 加锁成功,这是因为线程 2 的加锁条件不满足,初始化时候的 condition 参数为 0,而线程 2 加锁条件是 condition 为 1,所以线程 2 加锁失败。 lockWhenCondition 与 lock 方法类似,加锁失败会阻塞线程,所以线程 2 会被阻塞着。 tryLockWhenCondition: 方法就算条件不满足,也会返回 NO,不会阻塞当前线程。 lockWhenCondition:beforeDate:方法会在约定的时间内一直等待 condition 变为 2,并阻塞当前线程,直到超时后返回 NO。 锁定和解锁的调用可以随意组合,也就是说 lock、lockWhenCondition:与unlock、unlockWithCondition: 是可以按照自己的需求随意组合的。2.4、NSConditionNSCondition 是一种特殊类型的锁,通过它可以实现不同线程的调度。一个线程被某一个条件所阻塞,直到另一个线程满足该条件从而发送信号给该线程使得该线程可以正确的执行。比如说,你可以开启一个线程下载图片,一个线程处理图片。这样的话,需要处理图片的线程由于没有图片会阻塞,当下载线程下载完成之后,则满足了需要处理图片的线程的需求,这样可以给定一个信号,让处理图片的线程恢复运行。 NSCondition 的对象实际上作为一个锁和一个线程检查器,锁上之后其它线程也能上锁,而之后可以根据条件决定是否继续运行线程,即线程是否要进入 waiting 状态,如果进入 waiting 状态,当其它线程中的该锁执行 signal 或者 broadcast 方法时,线程被唤醒,继续运行之后的方法。 NSCondition 可以手动控制线程的挂起与唤醒,可以利用这个特性设置依赖。源码内容: 123456789101112@interface NSCondition : NSObject <NSLocking> {@private void *_priv;}- (void)wait; //挂起线程- (BOOL)waitUntilDate:(NSDate *)limit; //什么时候挂起线程- (void)signal; // 唤醒一条挂起线程- (void)broadcast; //唤醒所有挂起线程@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));@end 用法: 123456789101112131415161718192021222324252627282930313233- (void)viewDidLoad { [super viewDidLoad]; [self nscondition];}- (void)nscondition { NSCondition * cjcondition = [NSCondition new]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ [cjcondition lock]; NSLog(@"线程1线程加锁"); [cjcondition wait]; NSLog(@"线程1线程唤醒"); [cjcondition unlock]; NSLog(@"线程1线程解锁"); }); dispatch_async(dispatch_get_global_queue(0, 0), ^{ [cjcondition lock]; NSLog(@"线程2线程加锁"); if ([cjcondition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]) { NSLog(@"线程2线程唤醒"); [cjcondition unlock]; NSLog(@"线程2线程解锁"); } }); dispatch_async(dispatch_get_global_queue(0, 0), ^{ sleep(2); [cjcondition signal]; });} 控制台输出: 12342017-10-19 17:15:48.410316+0800 Thread-Lock[40011:943638] 线程1线程加锁2017-10-19 17:15:48.410757+0800 Thread-Lock[40011:943640] 线程2线程加锁2017-10-19 17:15:50.414288+0800 Thread-Lock[40011:943638] 线程1线程唤醒2017-10-19 17:15:50.414454+0800 Thread-Lock[40011:943638] 线程1线程解锁 12345//如果 [cjcondition signal]; 改成 [cjcondition broadcast]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ sleep(2); [cjcondition broadcast]; }); 控制台输出: 1234562017-10-19 17:18:08.054109+0800 Thread-Lock[40056:946099] 线程1线程加锁2017-10-19 17:18:08.054304+0800 Thread-Lock[40056:946096] 线程2线程加锁2017-10-19 17:18:10.056071+0800 Thread-Lock[40056:946099] 线程1线程唤醒2017-10-19 17:18:10.056231+0800 Thread-Lock[40056:946099] 线程1线程解锁2017-10-19 17:18:10.056244+0800 Thread-Lock[40056:946096] 线程2线程唤醒2017-10-19 17:18:10.056445+0800 Thread-Lock[40056:946096] 线程2线程解锁 由以上内容总结: 在加上锁之后,调用条件对象的 wait 或 waitUntilDate: 方法来阻塞线程,直到条件对象发出唤醒信号或者超时之后,再进行之后的操作。 signal 和 broadcast 方法的区别在于,signal 只是一个信号量,只能唤醒一个等待的线程,想唤醒多个就得多次调用,而 broadcast 可以唤醒所有在等待的线程。3.dispatch_semaphoredispatch_semaphore 使用信号量机制实现锁,等待信号和发送信号。 dispatch_semaphore 是 GCD 用来同步的一种方式,与他相关的只有三个函数,一个是创建信号量,一个是等待信号,一个是发送信号。 dispatch_semaphore 的机制就是当有多个线程进行访问的时候,只要有一个获得了信号,其他线程的就必须等待该信号释放。常用相关API: 123dispatch_semaphore_create(long value);dispatch_semaphore_wait(dispatch_semaphore_t _Nonnull dsema, dispatch_time_t timeout);dispatch_semaphore_signal(dispatch_semaphore_t _Nonnull dsema); 用法: 123456789101112131415161718192021222324- (void)viewDidLoad { [super viewDidLoad]; [self dispatch_semaphore];}- (void)dispatch_semaphore { dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 6 * NSEC_PER_SEC); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_semaphore_wait(semaphore, overTime); NSLog(@"线程1开始"); sleep(5); NSLog(@"线程1结束"); dispatch_semaphore_signal(semaphore); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); dispatch_semaphore_wait(semaphore, overTime); NSLog(@"线程2开始"); dispatch_semaphore_signal(semaphore); });} 控制台输出: 1232017-10-19 18:30:37.672490+0800 Thread-Lock[40569:993613] 线程1开始2017-10-19 18:30:42.673845+0800 Thread-Lock[40569:993613] 线程1结束2017-10-19 18:30:42.674165+0800 Thread-Lock[40569:993612] 线程2开始 //如果 overTime 改成 3 秒控制台输出: 1232017-10-19 18:32:32.078186+0800 Thread-Lock[40634:995921] 线程1开始2017-10-19 18:32:35.082943+0800 Thread-Lock[40634:995920] 线程2开始2017-10-19 18:32:37.083115+0800 Thread-Lock[40634:995921] 线程1结束 由以上内容总结: dispatch_semaphore 和 NSCondition 类似,都是一种基于信号的同步方式,但 NSCondition 信号只能发送,不能保存(如果没有线程在等待,则发送的信号会失效)。而 dispatch_semaphore 能保存发送的信号。dispatch_semaphore 的核心是 dispatch_semaphore_t 类型的信号量。 dispatch_semaphore_create(1) 方法可以创建一个 dispatch_semaphore_t 类型的信号量,设定信号量的初始值为 1。注意,这里的传入的参数必须大于或等于 0,否则 dispatch_semaphore_create 会返回 NULL。 dispatch_semaphore_wait(semaphore, overTime); 方法会判断 semaphore 的信号值是否大于 0。大于 0 不会阻塞线程,消耗掉一个信号,执行后续任务。如果信号值为 0,该线程会和 NSCondition 一样直接进入 waiting 状态,等待其他线程发送信号唤醒线程去执行后续任务,或者当 overTime 时限到了,也会执行后续任务。 dispatch_semaphore_signal(semaphore); 发送信号,如果没有等待的线程接受信号,则使 signal 信号值加一(做到对信号的保存)。 一个 dispatch_semaphore_wait(semaphore, overTime); 方法会去对应一个 dispatch_semaphore_signal(semaphore); 看起来像 NSLock 的 lock 和 unlock,其实可以这样理解,区别只在于有信号量这个参数,lock unlock 只能同一时间,一个线程访问被保护的临界区,而如果 dispatch_semaphore 的信号量初始值为 x ,则可以有 x 个线程同时访问被保护的临界区。4.pthread_mutex 与 pthread_mutex(recursive)pthread 表示 POSIX thread,定义了一组跨平台的线程相关的 API,POSIX 互斥锁是一种超级易用的互斥锁,使用的时候: 只需要使用 pthread_mutex_init 初始化一个 pthread_mutex_t, pthread_mutex_lock 或者 pthread_mutex_trylock 来锁定 , pthread_mutex_unlock 来解锁, 当使用完成后,记得调用 pthread_mutex_destroy 来销毁锁。常用相关API: 12345pthread_mutex_init(pthread_mutex_t *restrict _Nonnull, const pthread_mutexattr_t *restrict _Nullable);pthread_mutex_lock(pthread_mutex_t * _Nonnull);pthread_mutex_trylock(pthread_mutex_t * _Nonnull);pthread_mutex_unlock(pthread_mutex_t * _Nonnull);pthread_mutex_destroy(pthread_mutex_t * _Nonnull); 用法: 12345678910111213141516171819202122232425262728//pthread_mutex- (void)viewDidLoad { [super viewDidLoad]; [self pthread_mutex];}- (void)pthread_mutex { __block pthread_mutex_t cjlock; pthread_mutex_init(&cjlock, NULL); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ pthread_mutex_lock(&cjlock); NSLog(@"线程1开始"); sleep(3); NSLog(@"线程1结束"); pthread_mutex_unlock(&cjlock); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); pthread_mutex_lock(&cjlock); NSLog(@"线程2"); pthread_mutex_unlock(&cjlock); });} 控制台输出: 1232017-10-23 14:50:29.842180+0800 Thread-Lock[74478:1647362] 线程1开始2017-10-23 14:50:32.846786+0800 Thread-Lock[74478:1647362] 线程1结束2017-10-23 14:50:32.847001+0800 Thread-Lock[74478:1647359] 线程2 1234567891011121314151617181920212223242526272829303132333435//pthread_mutex(recursive)- (void)viewDidLoad { [super viewDidLoad]; [self pthread_mutex_recursive];}- (void)pthread_mutex_recursive { __block pthread_mutex_t cjlock; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&cjlock, &attr); pthread_mutexattr_destroy(&attr); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^RecursiveBlock)(int); RecursiveBlock = ^(int value) { pthread_mutex_lock(&cjlock); NSLog(@"%d加锁成功",value); if (value > 0) { NSLog(@"value = %d", value); sleep(1); RecursiveBlock(value - 1); } NSLog(@"%d解锁成功",value); pthread_mutex_unlock(&cjlock); }; RecursiveBlock(3); });} //控制台输出: 12345678910112017-10-23 15:31:51.599693+0800 Thread-Lock[74723:1668089] 3加锁成功2017-10-23 15:31:51.599912+0800 Thread-Lock[74723:1668089] value = 32017-10-23 15:31:52.602002+0800 Thread-Lock[74723:1668089] 2加锁成功2017-10-23 15:31:52.602317+0800 Thread-Lock[74723:1668089] value = 22017-10-23 15:31:53.604669+0800 Thread-Lock[74723:1668089] 1加锁成功2017-10-23 15:31:53.604957+0800 Thread-Lock[74723:1668089] value = 12017-10-23 15:31:54.607778+0800 Thread-Lock[74723:1668089] 0加锁成功2017-10-23 15:31:54.608109+0800 Thread-Lock[74723:1668089] 0解锁成功2017-10-23 15:31:54.608391+0800 Thread-Lock[74723:1668089] 1解锁成功2017-10-23 15:31:54.608622+0800 Thread-Lock[74723:1668089] 2解锁成功2017-10-23 15:31:54.608945+0800 Thread-Lock[74723:1668089] 3解锁成功 由以上内容总结: 它的用法和 NSLock 的 lock unlock 用法一致,而它也有一个 pthread_mutex_trylock 方法,pthread_mutex_trylock 和 tryLock 的区别在于,tryLock 返回的是 YES 和 NO,pthread_mutex_trylock 加锁成功返回的是 0,失败返回的是错误提示码。 pthread_mutex(recursive) 作用和 NSRecursiveLock 递归锁类似。如果使用 pthread_mutex_init(&theLock, NULL); 初始化锁的话,上面的代码的第二部分会出现死锁现象,使用递归锁就可以避免这种现象。5. OSSpinLockOSSpinLock 是一种自旋锁,和互斥锁类似,都是为了保证线程安全的锁。但二者的区别是不一样的,对于互斥锁,当一个线程获得这个锁之后,其他想要获得此锁的线程将会被阻塞,直到该锁被释放。但自选锁不一样,当一个线程获得锁之后,其他线程将会一直循环在哪里查看是否该锁被释放。所以,此锁比较适用于锁的持有者保存时间较短的情况下。只有加锁,解锁,尝试加锁三个方法。常用相关API: 12345678typedef int32_t OSSpinLock;// 加锁void OSSpinLockLock( volatile OSSpinLock *__lock );// 尝试加锁bool OSSpinLockTry( volatile OSSpinLock *__lock );// 解锁void OSSpinLockUnlock( volatile OSSpinLock *__lock ); 用法: 123456789101112131415161718192021222324252627#import <libkern/OSAtomic.h>- (void)viewDidLoad { [super viewDidLoad]; [self osspinlock];}- (void)osspinlock { __block OSSpinLock theLock = OS_SPINLOCK_INIT; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ OSSpinLockLock(&theLock); NSLog(@"线程1开始"); sleep(3); NSLog(@"线程1结束"); OSSpinLockUnlock(&theLock); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ OSSpinLockLock(&theLock); sleep(1); NSLog(@"线程2"); OSSpinLockUnlock(&theLock); });} 控制台输出: 1232017-10-23 16:02:48.865501+0800 Thread-Lock[75025:1684487] 线程1开始2017-10-23 16:02:51.868736+0800 Thread-Lock[75025:1684487] 线程1结束2017-10-23 16:02:52.922911+0800 Thread-Lock[75025:1684486] 线程2 YY大神 @ibireme 的文章也有说这个自旋锁存在优先级反转问题,具体文章可以戳 不再安全的 OSSpinLock,而 OSSpinLock 在iOS 10.0中被 <os/lock.h> 中的 os_unfair_lock 取代。 6.os_unfair_lock 自旋锁已经不再安全,然后苹果又整出来个 os_unfair_lock,这个锁解决了优先级反转问题。 常用相关API:123456789// 初始化os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT);// 加锁os_unfair_lock_lock(unfairLock);// 尝试加锁BOOL b = os_unfair_lock_trylock(unfairLock);// 解锁os_unfair_lock_unlock(unfairLock);os_unfair_lock 用法和 OSSpinLock 基本一直,就不一一列出了。 总结 应当针对不同的操作使用不同的锁,而不能一概而论哪种锁的加锁解锁速度快。 其实每一种锁基本上都是加锁、等待、解锁的步骤,理解了这三个步骤就可以帮你快速的学会各种锁的用法。 @synchronized 的效率最低,不过它的确用起来最方便,所以如果没什么性能瓶颈的话,可以选择使用 @synchronized。 当性能要求较高时候,可以使用 pthread_mutex 或者 dispath_semaphore,由于 OSSpinLock 不能很好的保证线程安全,而在只有在 iOS10 中才有 os_unfair_lock ,所以,前两个是比较好的选择。既可以保证速度,又可以保证线程安全。 对于 NSLock 及其子类,速度来说 NSLock < NSCondition < NSRecursiveLock < NSConditionLock 。 -]]></content>
</entry>
<entry>
<title><![CDATA[Python装饰器的简单理解]]></title>
<url>%2Fposts%2F30847%2F</url>
<content type="text"><![CDATA[装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能如下边例子 w1函数是一个闭包这里就是一个装饰函数,为了验证f1函数是否需要被执行 就作为函数innerfunc的参数传入w1 ,执行innerfunc()调用inner函数 验证通过则执行函数func()即 f1() 无参数的函数装饰1234567891011121314151617def w1(func): def inner(): print("验证权限") if True: func() return inner def f1(): print("----f1----")def f2(): print("f2")innerfunc = w1(f1)innerfunc() 然后看懂了这个例子就改为装饰器的写法 12345678910111213141516171819def w1(func): def inner(): print("验证权限") if True: func() return inner@w1 # @w1 = w1(f1)def f1(): print("----f1----")@w1def f2(): print("----f2----")f1()f2() 打印 验证权限 —-f1—- 验证权限 —-f2—- 上边可以看出 1之前的innerfunc = w1(f1)就换成了 @w1写在f1函数体上边 即为完整的装饰器 这样就实现了不改变f1 或者f2的代码的情况下 给函数增加了其他方法 这里的装饰器比较简单就是 if True: 验证通过即执行函数 再看个简单例子 1234567891011121314151617def makeBlod(fn): print("函数不执行的时候只要解释器执行@makeBlod就执行此函数了只是暂不调用wrapped函数") def wrapped(): return "<br>" + fn() + "</br>" return wrappeddef makeItalic(fn): def wrapped(): return "<i>" + fn() + "</i>" return wrapped#Python解释器执行到这里就开始执行装饰器 而不会等到函数执行的时候再去执行装饰器@makeBlod@makeItalicdef test(): return "hello world"print(test()) 输出1<br><i>hello world</i></br> 这里注意虽然@makeBlod装饰器先执行但是解释器在往下执行的时候遇到装饰器@makeItalic 就会先等@makeItalic执行wrapped函数完再执行@makeBlod装饰器的wrapped函数 对于有固定参数个数的函数进行装饰1234567891011121314def func(funcName): print("---func--1--") def func_in(a,b):#如果a,b没定义那么会导致test(2,4)调用失败 print("---func_in--1---") funcName(a,b)#如果a,b没定义那么会导致test(a,b)函数调用失败 print("---func_in--2---") print("---func--2--") return func_in@funcdef test(a, b): print("----test--a=%d,b=%d---"%(a,b))test(2,4) 对于无固定参数个数的函数进行装饰123456789101112131415161718192021def func(funcName): print("---func--1--") def func_in(*args,**kwargs): print("---func_in--1---") funcName(*args,**kwargs) print("---func_in--2---") print("---func--2--") return func_in@funcdef test(a, b ,c): print("----test--a=%d,b=%d,c=%d---"%(a,b,c))test(2,4,5)@funcdef test2(a, b, c, d): print("----test--a=%d,b=%d,c=%d,d=%d---"%(a,b,c,d))test2(2,4,5,6) 对有返回值的函数进行装饰1234567891011121314151617def func(funcName): print("---func--1--") def func_in(): print("---func_in--1---") returnZhi = funcName() print(returnZhi) return returnZhi#带有返回值得函数一定要return此值给源函数 print("---func_in--2---") print("---func--2--") return func_in@funcdef test(): print("----test---") return "haha"ret = test()print("test返回值 %s" % ret) 打印123456---func--1-----func--2-----func_in--1-------test---hahatest返回值 haha 通用装饰器适合有无参数有无返回值12345678910111213141516171819202122232425262728293031323334353637def func(funcName): print("---func--1--") def func_in(*args, **kwargs): print("---func_in--1---") returnZhi = funcName(*args, **kwargs) return returnZhi#带有返回值得函数一定要return此值给源函数 print("---func_in--2---") print("---func--2--") return func_in@func#无参数有返回值def test(): print("----test---") return "haha"ret = test()print("test返回值 %s" % ret)#有参数有返回值@funcdef test1(a): print("----test1=%d---"% a) return "hahatest1"ret1 = test1(10)print("test1返回值 %s" % ret1)@func#无参数无返回值def test2(): print("----test2---")ret2 = test2()print("test2返回值 %s" % ret2)#有参数无返回值@funcdef test3(a): print("----test3=%d---"%a)print(test3(3)) 打印1234567891011121314151617181920---func--1-----func--2-----func_in--1-------test---test返回值 haha---func--1-----func--2-----func_in--1-------test1=10---test1返回值 hahatest1---func--1-----func--2-----func_in--1-------test2---test2返回值 None---func--1-----func--2-----func_in--1-------test3=3---None 装饰器带有参数1234567891011121314151617181920212223def func_arg(arg): def func(funcName): def func_in(): if arg == "嘿嘿": print("嘿嘿记录日志 %s" % arg) funcName() else: print("哈哈记录日志 %s" % arg) funcName() return func_in return func#先执行func_arg("heihei")函数,返回的是func函数的引用然后就是使用@func就行装饰@func_arg("哈哈")#无参数有返回值def test(): print("----test---")test()@func_arg("嘿嘿")#无参数有返回值def test2(): print("----test2---")test2() 打印1234哈哈记录日志 哈哈----test---嘿嘿记录日志 嘿嘿----test2--- 更多理解可以参考http://python.jobbole.com/82344/https://www.zhihu.com/question/26930016视频教程链接: https://pan.baidu.com/s/1eR403LK 密码: rmqj]]></content>
</entry>
<entry>
<title><![CDATA[iOS属性修饰符用copy、strong修饰的终极解释]]></title>
<url>%2Fposts%2F10477%2F</url>
<content type="text"><![CDATA[Copy,Strong的区别需要了解点内存管理的知识,Strong是ARC下引入的修饰,相当于手动管理内存(MRC)下的retain,在相关代码下,常常看到有的人用copy修饰NSString,NSArray,NSDictionary..等存在可变与不可变之分的对象,常常会用copy,而不是strong,下面代码来解释一下strong与copy的区别:先说明一下什么叫做浅拷贝,什么叫做深拷贝;浅Copy:可以理解为指针的复制,只是多了一个指向这块内存的指针,共用一块内存。深Copy:理解为内存的复制,两块内存是完全不同的,也就是两个对象指针分别指向不同的内存,互不干涉。首先在类延展中声明两个属性变量12@property (nonatomic, strong)NSString * stringStrong; //strong修饰的字符串对象 @property (nonatomic, copy)NSString * stringCopy; //copy修饰的字符串对象 接着创建两个不可变字符串(NSString)123//新创建两个NSString对象 NSString * strong1 = @"I am Strong!"; NSString * copy1 = @"I am Copy!"; 将两个属性分别进行赋值123//初始化两个字符串 self.stringStrong = strong1; self.stringCopy = copy1; 分别打印一下四个变量的内存地址:1234NSLog(@"strong1 = %p",strong1); NSLog(@"stringStrong = %p",self.stringStrong); NSLog(@"copy1 = %p",copy1); NSLog(@"stringCopy = %p",self.stringCopy); 结果如下:可以看出,此时无论是strong修饰的字符串还是copy修饰的字符串,都进行了浅Copy. 2016-02-29 18:59:06.332 StrongOrCopy[5046:421886] strong1 = 0x10a0b30782016-02-29 18:59:06.332 StrongOrCopy[5046:421886] stringStrong = 0x10a0b30782016-02-29 18:59:06.332 StrongOrCopy[5046:421886] copy1 = 0x10a0b30982016-02-29 18:59:06.332 StrongOrCopy[5046:421886] stringCopy = 0x10a0b3098 如果创建两个不可变字符串对象(NSMutableString)呢 123//新创建两个NSMutableString对象 NSMutableString * mutableStrong = [NSMutableString stringWithString:@"StrongMutable"]; NSMutableString * mutableCopy = [NSMutableString stringWithString:@"CopyMutable"]; 分别对属性再次进行赋值12self.stringStrong = mutableStrong; self.stringCopy = mutableCopy; 分别打印一下四个变量的地址:结果如下:这时就发现了,用strong修饰的字符串依旧进行了浅Copy,而由copy修饰的字符串进行了深Copy,所以mutableStrong与stringStrong指向了同一块内存,而mutableCopy和stringCopy指向的是完全两块不同的内存。 2016-02-29 18:59:06.332 StrongOrCopy[5046:421886] mutableStrong = 0x7fccba425d602016-02-29 18:59:06.332 StrongOrCopy[5046:421886] stringStrong = 0x7fccba425d602016-02-29 18:59:06.332 StrongOrCopy[5046:421886] mutableCopy = 0x7fccba40d7c02016-02-29 18:59:06.333 StrongOrCopy[5046:421886] stringCopy = 0x7fccba4149e0 那么有什么用呢,实例来看一下有什么区别:首先是对不可变字符串进行操作:1234567891011//新创建两个NSString对象 NSString * strong1 = @"I am Strong!"; NSString * copy1 = @"I am Copy!"; //初始化两个字符串 self.stringStrong = strong1; self.stringCopy = copy1; //两个NSString进行操作 [strong1 stringByAppendingString:@"11111"]; [copy1 stringByAppendingString:@"22222"]; 分别对在字符串后面进行拼接,当然这个拼接对原字符串没有任何的影响,因为不可变自字符串调用的方法都是有返回值的,原来的值是不会发生变化的.打印如下,对结果没有任何的影响: 2016-02-29 19:15:26.729 StrongOrCopy[5146:439360] strong1 = I am Strong!2016-02-29 19:15:26.729 StrongOrCopy[5146:439360] stringStrong = I am Strong!2016-02-29 19:15:26.729 StrongOrCopy[5146:439360] copy1 = I am Copy!2016-02-29 19:15:26.729 StrongOrCopy[5146:439360] stringCopy = I am Copy! 然后是对可变字符串进行操作: 1234567891011//新创建两个NSMutableString对象 NSMutableString * mutableStrong = [NSMutableString stringWithString:@"StrongMutable"]; NSMutableString * mutableCopy = [NSMutableString stringWithString:@"CopyMutable"]; //初始化两个字符串 self.stringStrong = mutableStrong; self.stringCopy = mutableCopy; //两个MutableString进行操作 [mutableStrong appendString:@"Strong!"]; [mutableCopy appendString:@"Copy!"]; 再来看一下结果:对mutableStrong进行的操作,由于用strong修饰的stringStrong没有进行深Copy,导致共用了一块内存,当mutableStrong对内存进行了操作的时候,实际上对stringStrong也进行了操作; 相反,用copy修饰的stringCopy进行了深Copy,也就是说stringCopy与mutableCopy用了两块完全不同的内存,所以不管mutableCopy进行了怎么样的变化,原来的stringCopy都不会发生变化.这就在日常中避免了出现一些不可预计的错误。 2016-02-29 19:20:27.652 StrongOrCopy[5245:446189] stringStrong = StrongMutableStrong!2016-02-29 19:20:27.652 StrongOrCopy[5245:446189] mutableStrong = StrongMutableStrong!2016-02-29 19:20:27.652 StrongOrCopy[5245:446189] stringCopy = CopyMutable2016-02-29 19:20:27.652 StrongOrCopy[5245:446189] mutableCopy = CopyMutableCopy! 这样看来,在不可变对象之间进行转换,strong与copy作用是一样的,但是如果在不可变与可变之间进行操作,那么楼主比较推荐copy,这也就是为什么很多地方用copy,而不是strong修饰NSString,NSArray等存在可变不可变之分的类对象了,避免出现意外的数据操作. 参考自NSString什么时候用copy,什么时候用strong属性用copy、strong修饰的区别]]></content>
</entry>
<entry>
<title><![CDATA[iOS11导航栏自定义按钮偏移问题]]></title>
<url>%2Fposts%2F28111%2F</url>
<content type="text"><![CDATA[##关于iOS11导航栏自定义按钮偏移问题 先看iOS10之下返回按钮正常的图 下边是iOS11下返回按钮偏离的图 iOS10之前想必大部分都是用以下代码解决偏离问题的 12345678UIButton *leftButton = [UIButton buttonWithType:UIButtonTypeCustom]; leftButton.frame = CGRectMake(0, 0, 44, 44); [leftButton setImage:[UIImage imageNamed:image] forState:UIControlStateNormal]; [leftButton setImage:[UIImage imageNamed:selectImage] forState:UIControlStateHighlighted]; UIBarButtonItem *spacer = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil]; spacer.width = -10; UIBarButtonItem *leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:leftButton]; self.navigationItem.leftBarButtonItems=@[spacer,leftBarButtonItem]; 升级后无论width怎么改变已经失效了于是查了下找到如下解决办法过改变按钮的 contentEdgeInsets和imageEdgeInsets的值成功改变了按钮的偏移问题,只设置contentEdgeInsets也可以。12345678UIButton *leftButton = [UIButton buttonWithType:UIButtonTypeCustom]; leftButton.frame = CGRectMake(0, 0, 44, 44); [leftButton setImage:[UIImage imageNamed:image] forState:UIControlStateNormal]; [leftButton setImage:[UIImage imageNamed:selectImage] forState:UIControlStateHighlighted]; leftButton.contentEdgeInsets =UIEdgeInsetsMake(0, -16,0, 0); leftButton.imageEdgeInsets =UIEdgeInsetsMake(0, -11,0, 0); UIBarButtonItem *leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:leftButton]; self.navigationItem.leftBarButtonItems=@[leftBarButtonItem]; 用以上代码即可解决问题 或者像我一样不改以前的方法只加了判断处理 1234567if (@available(iOS 11.0, *)) { leftButton.contentEdgeInsets =UIEdgeInsetsMake(0, -16,0, 0); leftButton.imageEdgeInsets =UIEdgeInsetsMake(0, -11,0, 0); self.navigationItem.leftBarButtonItems=@[leftBarButtonItem]; }else{ self.navigationItem.leftBarButtonItems=@[spacer,leftBarButtonItem]; }]]></content>
</entry>
<entry>
<title><![CDATA[python之HTMLParser解析HTML文档]]></title>
<url>%2Fposts%2F4627%2F</url>
<content type="text"><![CDATA[###python之HTMLParser解析HTML文档 HTMLParser是Python自带的模块,使用简单,能够很容易的实现HTML文件的分析。本文主要简单讲一下HTMLParser的用法. 使用时需要定义一个从类HTMLParser继承的类,重定义函数: handle_starttag( tag, attrs) handle_startendtag( tag, attrs) handle_endtag( tag) handle_data(data) 1. 获取标签属性tag是的html标签,attrs是 (属性,值)元组(tuple)的列表(list). 如一个标签为: 那么它的attrs列表为[(‘type’, ‘hidden’), (‘name’, ‘NXX’), (‘id’, ‘IDXX’), (‘value’, ‘VXX’)]HTMLParser自动将tag和attrs都转为小写。 下面给出的例子抽取了html中的所有链接: 1234567891011121314151617181920212223242526from HTMLParser import HTMLParser class MyHTMLParser(HTMLParser): def __init__(self): HTMLParser.__init__(self) self.links = [] def handle_starttag(self, tag, attrs): #print "Encountered the beginning of a %s tag" % tag if tag == "a": if len(attrs) == 0: pass else: for (variable, value) in attrs: if variable == "href": self.links.append(value) if __name__ == "__main__": html_code = """ <a href="www.google.com"> google.com</a> <A Href="www.pythonclub.org"> PythonClub </a> <A HREF = "www.sina.com.cn"> Sina </a> """ hp = MyHTMLParser() hp.feed(html_code) hp.close() print(hp.links) 输出为: [‘www.google.com', ‘www.pythonclub.org', ‘www.sina.com.cn'] 如果想抽取图形链接:<img src='http://www.google.com/intl/zh-CN_ALL/images/logo.gif' />就要重定义 handle_startendtag( tag, attrs) 函数 2. 获取标签内容 test1.html文件内容如下:12345678<html><head><title> XHTML 与 HTML 4.01 标准没有太多的不同</title></head><body>i love you</body></html> 2.1 第一个例子123456789101112131415161718192021import HTMLParser class TitleParser(HTMLParser.HTMLParser): def __init__(self): HTMLParser.HTMLParser.__init__(self) # self.taglevels=[] self.handledtags = ['title','body'] self.processing = None def handle_starttag(self,tag,attrs): print '--------------' print 'handle start func',tag def handle_endtag(self,tag): print '================' print 'handle end func',tag if __name__ == '__main__': fd=open('test1.html') tp=TitleParser() tp.feed(fd.read()) 运行结果: 12345678910111213141516--------------handle start func html--------------handle start func head--------------handle start func title=======================handle end func title=======================handle end func head--------------handle start func body=======================handle end func body=======================handle end func html 相信大家已经看出来了,解析时碰到<>,自动调用handle_starttag();碰到</>,自动调用handle_endtag() 2.2 添加handle_data方法1234567891011121314151617181920212223242526272829import HTMLParser class TitleParser(HTMLParser.HTMLParser): def __init__(self): HTMLParser.HTMLParser.__init__(self) # self.taglevels=[] self.handledtags = ['title','body'] self.processing = None def handle_starttag(self,tag,attrs): print '--------------' print 'handle start func',tag def handle_data(self,data): print '####' print 'handle data func' if data == '\n': print r'\n' else: print data, def handle_endtag(self,tag): print '=======================' print 'handle end func',tag if __name__ == '__main__': fd=open('test1.html') tp=TitleParser() tp.feed(fd.read()) 运行结果: 12345678910111213141516171819202122232425262728293031323334353637--------------handle start func html####handle data func\n--------------handle start func head####handle data func\n--------------handle start func title####handle data func XHTML 与 HTML 4.01 标准没有太多的不同 =======================handle end func title####handle data func\n=======================handle end func head####handle data func\n--------------handle start func body####handle data func i love you=======================handle end func body####handle data func\n=======================handle end func html 说明: 每一个标签,无论<> 还是</>,均会调用handle_data() html中第一行、第二行分别为和,后面无具体数据,只有回车换行,所用调用handle_data(),打印结果为换行;同理。 2.2.1 解析需要的内容123456789101112131415161718192021222324252627import HTMLParser class TitleParser(HTMLParser.HTMLParser): def __init__(self): HTMLParser.HTMLParser.__init__(self) self.handledtags = ['title','body'] self.processing = None self.data = [] def handle_starttag(self,tag,attrs): if tag in self.handledtags: self.processing = tag def handle_data(self,data): if self.processing: self.data.append(data) def handle_endtag(self,tag): if tag == self.processing: self.processing = None if __name__ == '__main__': fd = open('test1.html') tp = TitleParser() tp.feed(fd.read()) for each in tp.data: print each 运行结果: XHTML 与 HTML 4.01 标准没有太多的不同 i love you 2.3 解析豆瓣热门电影实例1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465from html.parser import HTMLParserfrom urllib import requestimport ssl# 取消ssl验证ssl._create_default_https_context = ssl._create_unverified_contextclass MyHTMLParser(HTMLParser): def __init__(self): HTMLParser.__init__(self) self.movies = [] def handle_starttag(self, tag, attrs): print('star: <%s> 属性 %s' % (tag ,attrs)) def _attr(attrlist, attrname): for each in attrlist: if attrname == each[0]: return each[1] if tag == 'li' and _attr(attrs, 'data-title'): movie = {} movie['actors'] = _attr(attrs, 'data-actors') movie['actors'] = _attr(attrs, 'data-actors') movie['director'] = _attr(attrs, 'data-director') movie['duration'] = _attr(attrs, 'data-dutation') movie['title'] = _attr(attrs, 'data-title') movie['rate'] = _attr(attrs, 'data-rate') print(_attr(attrs, 'data-actors')) self.movies.append(movie) def handle_endtag(self, tag): print('end: </%s>' % tag) def handle_startendtag(self, tag, attrs): print('startendtag :<%s/> 结尾属性 %s' % (tag,attrs)) def handle_data(self, data): print('所有data %s' % data) def handle_comment(self, data): print('<!--', data, '-->') def handle_entityref(self, name): print('&%s;' % name) def handle_charref(self, name): print('&#%s;' % name)def movieparser(url): myparser = MyHTMLParser() with request.urlopen(url) as f: data = f.read().decode('utf-8') myparser.feed(data) myparser.close() return myparser.moviesif __name__ == '__main__': url = 'https://movie.douban.com/' movies = movieparser(url) for each in movies: print('%(title)s|%(rate)s|%(actors)s|%(director)s|%(duration)s' % each) 运行结果: 12345678910111213141516171819202122232425262728293031323334353637猩球崛起3:终极之战 War for the Planet of the Apes|7.1|安迪·瑟金斯 / 伍迪·哈里森 / 史蒂夫·茨恩|马特·里夫斯|None王牌保镖 The Hitman's Bodyguard|7.3|瑞恩·雷诺兹 / 塞缪尔·杰克逊 / 加里·奥德曼|帕特里克·休斯|None羞羞的铁拳||艾伦 / 马丽 / 沈腾|宋阳|None看不见的客人 Contratiempo|8.7|马里奥·卡萨斯 / 阿娜·瓦格纳 / 何塞·科罗纳|奥里奥尔·保罗|None缝纫机乐队||大鹏 / 乔杉 / 娜扎|大鹏|None蜘蛛侠:英雄归来 Spider-Man: Homecoming|7.5|汤姆·霍兰德 / 小罗伯特·唐尼 / 玛丽莎·托梅|乔·沃茨|None英伦对决 The Foreigner||成龙 / 皮尔斯·布鲁斯南 / 刘涛|马丁·坎贝尔|None空天猎||李晨 / 范冰冰 / 王千源|李晨|None追龙 追龍||甄子丹 / 刘德华 / 姜皓文|王晶|None托马斯大电影之了不起的比赛 Thomas & Friends: The Great Race||马克·莫拉根 / 大卫·拜德拉 / 奥利维娅·科尔曼|大卫·斯特登|None惊天解密 Unlocked|5.7|劳米·拉佩斯 / 托妮·科莱特 / 奥兰多·布鲁姆|迈克尔·艾普特|None敦刻尔克 Dunkirk|8.6|菲恩·怀特海德 / 汤姆·格林-卡尼 / 杰克·劳登|克里斯托弗·诺兰|None战狼2|7.4|吴京 / 弗兰克·格里罗 / 吴刚|吴京|None捍卫者||白恩 / 吕星辰 / 赫子铭|廖希|None刀剑神域:序列之争 劇場版 ソードアート・オンライン -オーディナル・スケール|7.2|松冈祯丞 / 户松遥 / 伊藤加奈惠|伊藤智彦|None极致追击||奥兰多·布鲁姆 / 吴磊 / 任达华|Charles|None天梯:蔡国强的艺术|8.6|蔡国强 / 蔡文悠 / 蔡文浩|凯文·麦克唐纳|None昆塔:反转星球||李正翔 / 洪海天 / 陶典|李炼|None理查大冒险 Richard the Stork||尼科莱特·克雷比茨 / Marc / Jason|托比·格恩科尔|None银魂 銀魂|7.4|小栗旬 / 菅田将晖 / 桥本环奈|福田雄一|None画室惊魂||罗翔 / 杨欣 / 陈美行|邢博|None钢铁飞龙之再见奥特曼||侯勇 / 大张伟 / 金晨|王巍|None海边的曼彻斯特 Manchester by the Sea|8.6|卡西·阿弗莱克 / 卢卡斯·赫奇斯 / 米歇尔·威廉姆斯|肯尼斯·罗纳根|None星际特工:千星之城 Valérian and the City of a Thousand Planets|7.2|戴恩·德哈恩 / 卡拉·迪瓦伊 / 克里夫·欧文|吕克·贝松|None纯洁心灵·逐梦演艺圈|2.0|朱哲健 / 李彦漫 / 陈思瀚|毕志飞|None建军大业||刘烨 / 朱亚文 / 黄志忠|刘伟强|None声之形 聲の形|6.9|入野自由 / 早见沙织 / 松冈茉优|山田尚子|None地球:神奇的一天 Earth: One Amazing Day|8.2|成龙 / 罗伯特·雷德福|理查德·戴尔|None奋斗|7.5|陈燕燕 / 郑君里 / 袁丛美|史东山|None谜证||苗侨伟 / 袁嘉敏 / 桑平|汪洋|None初恋时光||黄又南 / 邓紫衣 / 叶山豪|殷国君|None疯狂旅程||刘亮 / 白鸽 / 陆进|龙野|None十万个冷笑话2|7.7|山新 / 郝祥海 / 李姝洁|卢恒宇|None心理罪|5.4|廖凡 / 李易峰 / 万茜|谢东燊|None魔都爱之十二星座||李梓溪 / 孙立洋 / 马璐|唐昱|None请勿靠近||马可 / 李毓芬 / 郑雅文|张显|None三生三世十里桃花|4.0|刘亦菲 / 杨洋 / 罗晋|赵小丁|None]]></content>
</entry>
<entry>
<title><![CDATA[系统性学习Moya+Alamofire+RxSwift+ObjectMapper的配合使用]]></title>
<url>%2Fposts%2F10604%2F</url>
<content type="text"><![CDATA[系统性学习Moya+Alamofire+RxSwift+ObjectMapper的配合使用 主要是练习Moya的熟练使用,全文涉及到CYLTabBarController搭建简单易用的框架、Swift和OC互相调用、FLEX显示界面层级UI的属性、ObjectMapper解析数据、Kingfisher加载网络图片、MBProgressHUD融合到请求里自动显示与隐藏请求等待、MJRefresh作为刷新简单写了一个类别、SDCycleScrollView显示轮播图、Then的使用,最终实现了一个简单的界面…更加深入技术还在探究中,先放上本文的Demo 既然是介绍Moya的就主要先来介绍它吧,Moya是对 Alamofire的进一步封装,简化了网络请求,方便维护,方便单元测试,使用Moya项目中网络请求类的部分可能长这样,所有的请求集中放在一起,集体化管理很方便 点击查看官方教程 Moya发送简单的网络请求枚举类型需满足TargetType协议 1234567public protocol TargetType {var baseURL: NSURL { get }var path: String { get }var method: Moya.Method { get }var parameters: [String: AnyObject]? { get }var sampleData: NSData { get }} 实现一个枚举代码如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112import Foundationimport Moyaenum ApiManager {case getDantangList(String)case getNewsListcase getMoreNews(String)case getThemeListcase getThemeDesc(Int)case getNewsDesc(Int)case Create(title: String, body: String, userId: Int)case Login(phone:String,password:String)case Banner(String)}extension ApiManager: TargetType {/// The target's base `URL`.var baseURL: URL {switch self {case .Create(_,_,_):return URL.init(string: "http://jsonplaceholder.typicode.com/")!case .getDantangList,.Banner:return URL.init(string: "http://api.dantangapp.com/")!case .Login:return URL.init(string: "https://api.grtstar.cn")!default:return URL.init(string: "http://news-at.zhihu.com/api/")!}}/// The path to be appended to `baseURL` to form the full `URL`.var path: String {switch self {case .getDantangList(let page):return "v1/channels/\(page)/items"case .getNewsList:return "4/news/latest"case .getMoreNews(let date):return "4/news/before/" + datecase .getThemeList:return "4/themes"case .getThemeDesc(let id):return "4/theme/\(id)"case .getNewsDesc(let id):return "4/news/\(id)"case .Create(_, _, _):return "posts"case .Login:return "/rest/user/certificate"case .Banner:return "v1/banners"}}/// The HTTP method used in the request.var method: Moya.Method {switch self {case .Create(_, _, _):return .postcase .Login:return .postdefault:return .get}}/// The parameters to be incoded in the request.var parameters: [String: Any]? {switch self {case .Create(let title, let body, let userId):return ["title": title, "body": body, "userId": userId]case .Login(let number, let passwords):return ["mobile" : number, "password" : passwords,"deviceId": "12121312323"]case .Banner(let strin):return ["channel" :strin]default:return nil}}/// The method used for parameter encoding.var parameterEncoding: ParameterEncoding {return URLEncoding.default}/// Provides stub data for use in testing.var sampleData: Data {switch self {case .Create(_, _, _):return "Create post successfully".data(using: String.Encoding.utf8)!default:return "".data(using: String.Encoding.utf8)!}}var task: Task {return .request}/// Whether or not to perform Alamofire validation. Defaults to `false`.var validate: Bool {return false}} 现在就可以发送简单的网络请求了:1.定义一个全局变量MoyaProvider 1let ApiManagerProvider = MoyaProvider<ApiManager> 2.发送网络请求1234567ApiManagerProvider.request(.getNewsList) { (result) -> () incase let .success(response):breakcase let .failure(error):break } MoyaProvider的初始化我们观察下MoyaProvider的初始化方法. MoyaProvider初始化都是有默认值的 123456public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,stubClosure: @escaping StubClosure = MoyaProvider.neverStub,manager: Manager = MoyaProvider<Target>.defaultAlamofireManager(),plugins: [PluginType] = [],trackInflights: Bool = false) 这些可选参数就是Moya的强大之处了 ,文章主要也是介绍如何使用这些插件的。 参数说明: EndpointClosure可以对请求参数做进一步的修改,如可以修改endpointByAddingParameters endpointByAddingHTTPHeaderFields等 RequestClosure 你可以在发送请求前,做点手脚. 如修改超时时间,打印一些数据等等 StubClosure可以设置请求的延迟时间,可以当做模拟慢速网络 Manager 请求网络请求的方式。默认是Alamofire [PluginType]一些插件。回调的位置在发送请求后,接受服务器返回之前 稍后详细介绍这部分内容。 RxSwiftMoya也有自己的RxSwift的扩展,不懂RxSwift的童鞋可以看下我们博客中的关于RxSwift库介绍的文章。Moya使用RxSwift很简单,如下所示我们只需要对请求结果进行监听就行了 使用RxSwift可以这样来请求 123456789let provider = RxMoyaProvider<ApiManager>()//要使用RxMoyaProvider创建provider,暂时不携带任何参数provider.request(.getNewsList).subscribe { event inswitch event {case .next(let response):// do something with the datacase .error(let error):// handle the error}} 我们还可以对Observable进行扩展,自定义一些自己流水线操作,比如自动实现json转化Model,定义如下。 12345678910111213141516171819func mapObject<T: Mappable>(type: T.Type) -> Observable<T> {return self.map { response in//if response is a dictionary, then use ObjectMapper to map the dictionary//if not throw an errorguard let dict = response as? [String: Any] else {throw RxSwiftMoyaError.ParseJSONError}guard (dict["code"] as?Int) != nil else{throw RxSwiftMoyaError.ParseJSONError}if let error = self.parseError(response: dict) {throw error}return Mapper<T>().map(JSON: dict)!}} 下边的方法就需要根据服务器返回数据进行判断了,我常用的逻辑是数据请求成功了才返回再就行界面赋值刷新操作,如果是状态码不成功就直接拦截抛出错误(后台返回的message),比如是登录密码错误提示之类的 123456789101112131415161718192021fileprivate func parseError(response: [String: Any]?) -> NSError? {var error: NSError?if let value = response {var code:Int?//后台的数据每次会返回code只有是200才会表示逻辑正常执行if let codes = value["code"] as?Int{code = codes}if code != 200 {var msg = ""if let message = value["message"] as? String {msg = message}error = NSError(domain: "Network", code: code!, userInfo: [NSLocalizedDescriptionKey: msg])}}return error} 那么就可以定义一个请求方法了 1234567func login(phone: String, password:String) -> Observable<UserModel> {return provider.request(.Login(phone: phone, password: password)).mapJSON().debug() // 打印请求发送中的调试信息.mapObject(type: UserModel.self)} 如下代码就完成了一次请求 12345678let viewModel = ViewModel(self)viewModel.login(phone: "156178...." , password: "11111").subscribe(onNext: { (userModel: UserModel) in//do something with postsprint(userModel.user?.nickName ?? "")}).addDisposableTo(dispose) Moya也为我们提供了很多Observable的扩展,让我们能更轻松的处理MoyaResponse,常用的如下: filter(statusCodes:) 过滤response状态码 filterSuccessfulStatusCodes() 过滤状态码为请求成功的 mapJSON() 将请求response转化为JSON格式 mapString() 将请求response转化为String格式 具体可以参考官方文档 下边就说说RxMoyaProvider参数吧 EndpointClosure 没写什么就打印下参数,请求方法,路径..可以核对 12345678private func endpointMapping<Target: TargetType>(target: Target) -> Endpoint<Target> {print("请求连接:\(target.baseURL)\(target.path) \n方法:\(target.method)\n参数:\(String(describing: target.parameters)) ")return MoyaProvider.defaultEndpointMapping(for: target)} manager 用的是Alamofire请求,这里主要写了一个忽略SSL验证的方法,当然也可以在这里修改请求头等等123456789101112131415public func defaultAlamofireManager() -> Manager {let configuration = URLSessionConfiguration.defaultconfiguration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaderslet policies: [String: ServerTrustPolicy] = ["ap.dimain.cn": .disableEvaluation]let manager = Alamofire.SessionManager(configuration: configuration,serverTrustPolicyManager: ServerTrustPolicyManager(policies: policies))manager.startRequestsImmediately = falsereturn manager} 最有意思的还是插件了 ,可以自定义各种功能 pluginsplugins参数是一个数组的形式,遵循PluginType协议我们先看下PluginType的协议内容 12345678910111213141516public protocol PluginType {/// Called to modify a request before sending//请求前可以修改一些requestfunc prepare(_ request: URLRequest, target: TargetType) -> URLRequest/// Called immediately before a request is sent over the network (or stubbed).//开始请求func willSend(_ request: RequestType, target: TargetType)/// Called after a response has been received, but before the MoyaProvider has invoked its completion handler.//结束请求func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType)/// Called to modify a result before completionfunc process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response, MoyaError>} 状态条中的网络加载提示,俗称”菊花加载” networkActivityPlugin123456789101112131415let networkActivityPlugin = NetworkActivityPlugin { (change) -> () inswitch(change){case .ended:UIApplication.shared.isNetworkActivityIndicatorVisible = falsecase .began:UIApplication.shared.isNetworkActivityIndicatorVisible = true}} NetworkActivityPlugin是Moya提供的方法,还是根据PluginType的协议实现的 请求一般就需要loading了这里用MBProgressHUD实现自动显示隐藏 12345678910111213141516171819202122232425262728293031323334353637383940public final class RequestLoadingPlugin: PluginType {private let viewController: UIViewControllervar HUD:MBProgressHUDvar hide:Boolinit(_ vc: UIViewController,_ hideView:Bool) {self.viewController = vcself.hide = hideViewHUD = MBProgressHUD.init()guard self.hide else {return}HUD = MBProgressHUD.showAdded(to: self.viewController.view, animated: true)}public func willSend(_ request: RequestType, target: TargetType) {print("开始请求\(self.viewController)")if self.hide != false {HUD.mode = MBProgressHUDMode.indeterminateHUD.label.text = "加载中"HUD.bezelView.color = UIColor.lightGrayHUD.removeFromSuperViewOnHide = trueHUD.backgroundView.style = .solidColor //或SolidColor}}public func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {print("结束请求")HUD.hide(animated: true)}} 修改请求头想想不该放在插件了实现,应该是在manager里实现,先放出来代码吧 123456789101112struct AuthPlugin: PluginType {let token: Stringfunc prepare(_ request: URLRequest, target: TargetType) -> URLRequest {var request = requestrequest.timeoutInterval = 30request.addValue(token, forHTTPHeaderField: "token")request.addValue("ios", forHTTPHeaderField: "platform")request.addValue("version", forHTTPHeaderField: Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String)return request}} 请求时候遇到逻辑错误或者不满足条件,参数错误等要提示这里用的是Toast 123456789101112131415161718192021222324252627282930313233//检测token有效性final class AccessTokenPlugin: PluginType {private let viewController: UIViewControllerinit(_ vc: UIViewController) {self.viewController = vc}public func willSend(_ request: RequestType, target: TargetType) {}public func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {switch result {case .success(let response)://请求状态码guard response.statusCode == 200 else {return}var json:Dictionary? = try! JSONSerialization.jsonObject(with: response.data,options:.allowFragments) as! [String: Any]print("请求状态码\(json?["status"] ?? "")")guard (json?["message"]) != nil else {return}guard let codeString = json?["status"]else {return}//请求状态为1时候立即返回不弹出任何提示 否则提示后台返回的错误信息guard codeString as! Int != 1 else{return}self.viewController.view .makeToast( json?["message"] as! String)case .failure(let error):print("出错了\(error)")break}}} AccessTokenPlugin这个名字有点问题哈,起初是想在这里判断token不正确就退出登录用的由于没有合适的api就实现了请求结果的状态判断,这就自动实现了逻辑错误的提示了 不用一个请求一个请求的判断了,还是挺方便的 有了这些插件就可以这样初始化RxMoyaProvider 1234567let provider :RxMoyaProvider<ApiManager>provider = RxMoyaProvider<ApiManager>(endpointClosure: endpointMapping,manager:defaultAlamofireManager(),plugins:[RequestLoadingPlugin(self.viewController,true),AccessTokenPlugin( self.viewController), NetworkLoggerPlugin(verbose: true),networkActivityPlugin,AuthPlugin(token: "暂时为空")] 关于Moya的用法先介绍到这里后续我会继续探究更加灵活全面的用法。 下边介绍下Then的语法棉花糖吧,看例子吧12345678910111213141516171819202122_ = UILabel.init(frame: CGRect.init(x: 0, y: 0, width: kScreenW, height: 50)).then({ (make) inmake.text = "Then的简单用法超赞👍"make.font = .systemFont(ofSize: 20)make.textColor = .redmake.textAlignment = .centerself.view.addSubview(make)})UserDefaults.standard.do {$0.set("devxoul", forKey: "username")$0.set("[email protected]", forKey: "email")$0.synchronize()let tableView = UITableView().then {$0.backgroundColor = .clear$0.separatorStyle = .none$0.register(MyCell.self, forCellReuseIdentifier: "myCell")}} 如果布局这样还不简单那再看下边用Then和SnapKit一起使用的方式 12345678910111213_ = UILabel().then({ (make) inmake.text = "Then的简单用法超赞👍"make.font = .systemFont(ofSize: 20)make.textColor = .redmake.textAlignment = .centerself.view.addSubview(make)make.snp.makeConstraints({ (make) inmake.top.left.right.equalTo(0)make.height.equalTo(50)})}) 再不满意只能用Xib布局了…. 在Swift中用SDCycleScrollView轮播图SDCycleScrollView之前一直在OC中使用觉得很简单又熟悉了所以这次写的Demo依旧搬了过来,但是呢SDCycleScrollView里实现图片下载用的是SDWebImage,而Swift版本提供了Kingfisher那不可能都用了,因为也不想放弃SDCycleScrollView就不得已修改了里边图片下载的方法,在Swift项目里OC类直接调用Swift类是调用不到的,所以我就咨询了下找到一个合适办法,新建Swift里继承SDCycleScrollView然后用Kingfisher实现图片下载,方法比较简单就是给开发者提供一个参考方法 1234567891011121314151617181920212223242526import UIKitimport SDCycleScrollViewimport Kingfisherclass CustomSDCycleScrollView: SDCycleScrollView {//因为之前库里边用的是SDWebImageView 缓存的图片 现在 换了Swift版本的Kingfisher所以 无奈修改了原库的方法 重写了下open override func imageView(_ imageView: UIImageView!, url: URL!) -> UIImageView! {let imageView: UIImageView? = imageViewimageView?.kf.setImage(with: url,placeholder:UIImage.init(named: "tab_5th_h"))return imageView}//重写oc代码 删除缓存override class func clearImagesCache(){let cache = KingfisherManager.shared.cache// 获取硬盘缓存的大小cache.calculateDiskCacheSize { (size) -> () inprint("磁盘缓存大小: \(size) bytes ")cache.clearDiskCache()}}} 用的时候直接使用CustomSDCycleScrollView即可 项目使用MJRefresh实现刷新 给UIScrollView写了一个类别比较简单代码如下1234567891011121314151617181920212223242526272829303132import UIKitimport MJRefreshextension UIScrollView{func headerRefresh(block: @escaping () -> ()) -> (){self.mj_header = MJRefreshNormalHeader.init(refreshingBlock: {block()})}func footerRefresh(block: @escaping () -> ()) -> (){self.mj_footer = MJRefreshBackNormalFooter(refreshingBlock: {block()})}func endrefresh(){self.mj_footer.endRefreshing()self.mj_header.endRefreshing()}} 用的时候更简单了 1234567891011weak var weakself = self//上拉刷新tableView.headerRefresh {weakself?.loadData()}//下拉加载tableView.footerRefresh{weakself?.loadData()}//结束刷新self.tableView.endrefresh() 值得一提的是Swift项目如何用OC 下班了。。。。后续更新]]></content>
</entry>
<entry>
<title><![CDATA[AFNetworking和Alamofire设置跳过https(SSL)验证]]></title>
<url>%2Fposts%2F46398%2F</url>
<content type="text"><![CDATA[我们服务器用的是https但是没用证书,客户端不验证,在ios11之前是不用做处理的,在下载了ios11之后发现请求失败了,就是证书验证失败,想必是这次版本更新的一点小变化吧。 现在项目用的是AFNetworking 3.0.2版本报错 Error Domain=NSURLErrorDomain Code=-999 “已取消” 然后换了swift的Alamofire请求报错是 Domain=NSURLErrorDomain Code=-1202 “The certificate for this server is invalid. You might be connecting to a server that is pretending to be “api.domain.cn” which could put your confidential information at risk.” UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x1c010efa0>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9807…. 总之就是证书的问题了,我们服务器是没有验证证书的 所以需要前端这边声明跳过验证的 网上查了下找到了如下解决办法 OC的代码如下 123456AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];// allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO// 如果是需要验证自建证书,需要设置为YESsecurityPolicy.allowInvalidCertificates = YES;[securityPolicy setValidatesDomainName:NO];[manger setSecurityPolicy:securityPolicy]; Swift用Alamofire解决方案 12345678910111213public func defaultAlamofireManager() -> Manager { let configuration = URLSessionConfiguration.default configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders let policies: [String: ServerTrustPolicy] = [ "api.domian.cn": .disableEvaluation ] let manager = Alamofire.SessionManager(configuration: configuration,serverTrustPolicyManager: ServerTrustPolicyManager(policies: policies)) manager.startRequestsImmediately = false return manager}]]></content>
</entry>
<entry>
<title><![CDATA[使用DZNEmptyDataSet时DZNEmptyDataSetView自动上移]]></title>
<url>%2Fposts%2F25894%2F</url>
<content type="text"><![CDATA[使用DZNEmptyDataSet遇到的一个问题 今天在使用DZNEmptyDataSet遇到了一个问题,就是我用MJRefresh 后DZNEmptyDataSet View的整个背景尺寸不对。ps: DZNEmptyDataSet是一个相当不错设置空白页面的轮子非常 好使 。 无论是用自定义 1- (UIView *)customViewForEmptyDataSet:(UIScrollView *)scrollView 还是直接用 DZNEmptyDataSet 推荐的方式直接设置都是有问题的 123- (UIImage *)imageForEmptyDataSet:(UIScrollView *)scrollView;- (NSAttributedString *)descriptionForEmptyDataSet:(UIScrollView *)scrollView- (NSAttributedString *)buttonTitleForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state 看看问题的具体表现吧 下边这个就是 有问题了,没数据后自动上移了 简单的说问题是:刷新后DZNEmptyDataSetView 还会向上偏移一段距离!尝试各种刷新都没有用 12[self.tableView reloadEmptyDataSet]; [self.tableView reloadData]; 看了下视图的层次关系可以看出了DZNEmptyDataSetView上移了54,然后我一搜 54 ,发现54出现地方并不多,结合刷新猜测应该就是它啦 1const CGFloat MJRefreshHeaderHeight = 54.0; 所以推测,DZNEmptyDataSetView是根据正在刷新的过程中给其定布局的。然而我们需要阻止它或延后它。 此处我是想着在这之后直接处理它,改变它的 origin。 123456for (UIView *subView in self.tableView.subviews) {if ([NSStringFromClass([subView class]) isEqualToString:@"DZNEmptyDataSetView"]){if (subView.frame.origin.y == 0) return;subView.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height - 64);}} 在刷新后这样处理,方法有点死,但是相对来说比较直接的。 从 github 上看到的方法如下,貌似是目前使用最合理的吧,有其他方法欢迎告知。 123- (void)emptyDataSetWillAppear:(UIScrollView *)scrollView {scrollView.contentOffset = CGPointZero;}]]></content>
</entry>
<entry>
<title><![CDATA[自己动手实现KVO]]></title>
<url>%2Fposts%2F59559%2F</url>
<content type="text"><![CDATA[本篇会探究 KVO (Key-Value Observing) 实现机制,并去实践一番 - 利用 Runtime 自己动手去实现 KVO 。 KVO (Key-Value Observing) KVO 是 Objective-C 对观察者模式(Observer Pattern)的实现。也是 Cocoa Binding 的基础。当被观察对象的某个属性发生更改时,观察者对象会获得通知。 有意思的是,你不需要给被观察的对象添加任何额外代码,就能使用 KVO 。这是怎么做到的? KVO 实现机制 KVO 的实现也依赖于 Objective-C 强大的 Runtime 。Apple 的文档有简单提到过 KVO 的实现: Automatic key-value observing is implemented using a technique called isa-swizzling… When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class … Apple 的文档真是一笔带过,唯一有用的信息也就是:被观察对象的 isa 指针会指向一个中间类,而不是原来真正的类。看来,Apple 并不希望过多暴露 KVO 的实现细节。不过,要是你用 runtime 提供的方法去深入挖掘,所有被掩盖的细节都会原形毕露。Mike Ash 早在 2009 年就做了这么个探究。 简单概述下 KVO 的实现: 当你观察一个对象时,一个新的类会动态被创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。自然,重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象值的更改。最后把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。 原来,这个中间类,继承自原本的那个类。不仅如此,Apple 还重写了 -class 方法,企图欺骗我们这个类没有变,就是原本那个类。更具体的信息,去跑一下 Mike Ash 的那篇文章里的代码就能明白,这里就不再重复。 KVO 缺陷 KVO 很强大,没错。知道它内部实现,或许能帮助更好地使用它,或在它出错时更方便调试。但官方实现的 KVO 提供的 API 实在不怎么样。 比如,你只能通过重写 -observeValueForKeyPath:ofObject:change:context: 方法来获得通知。想要提供自定义的 selector ,不行;想要传一个 block ,门都没有。而且你还要处理父类的情况 - 父类同样监听同一个对象的同一个属性。但有时候,你不知道父类是不是对这个消息有兴趣。虽然 context 这个参数就是干这个的,也可以解决这个问题 - 在 -addObserver:forKeyPath:options:context: 传进去一个父类不知道的 context。但总觉得框在这个 API 的设计下,代码写的很别扭。至少至少,也应该支持 block 吧。 有不少人都觉得官方 KVO 不好使的。Mike Ash 的 Key-Value Observing Done Right,以及获得不少分享讨论的 KVO Considered Harmful 都把 KVO 拿出来吊打了一番。所以在实际开发中 KVO 使用的情景并不多,更多时候还是用 Delegate 或 NotificationCenter。 自己实现 KVO 如果没找到理想的,就自己动手做一个。既然我们对官方的 API 不太满意,又知道如何去实现一个 KVO,那就尝试自己动手写一个简易的 KVO 玩玩。 首先,我们创建 NSObject 的 Category,并在头文件中添加两个 API: typedef void(^PGObservingBlock)(id observedObject, NSString *observedKey, id oldValue, id newValue); @interface NSObject (KVO) - (void)PG_addObserver:(NSObject *)observer forKey:(NSString *)key withBlock:(PGObservingBlock)block; - (void)PG_removeObserver:(NSObject *)observer forKey:(NSString *)key; @end 接下来,实现 PG_addObserver:forKey:withBlock: 方法。逻辑并不复杂: 检查对象的类有没有相应的 setter 方法。如果没有抛出异常; 检查对象 isa 指向的类是不是一个 KVO 类。如果不是,新建一个继承原来类的子类,并把 isa 指向这个新建的子类; 检查对象的 KVO 类重写过没有这个 setter 方法。如果没有,添加重写的 setter 方法; 添加这个观察者 - (void)PG_addObserver:(NSObject *)observer forKey:(NSString *)key withBlock:(PGObservingBlock)block { // Step 1: Throw exception if its class or superclasses doesn't implement the setter SEL setterSelector = NSSelectorFromString(setterForGetter(key)); Method setterMethod = class_getInstanceMethod([self class], setterSelector); if (!setterMethod) { // throw invalid argument exception } Class clazz = object_getClass(self); NSString *clazzName = NSStringFromClass(clazz); // Step 2: Make KVO class if this is first time adding observer and // its class is not an KVO class yet if (![clazzName hasPrefix:kPGKVOClassPrefix]) { clazz = [self makeKvoClassWithOriginalClassName:clazzName]; object_setClass(self, clazz); } // Step 3: Add our kvo setter method if its class (not superclasses) // hasn't implemented the setter if (![self hasSelector:setterSelector]) { const char *types = method_getTypeEncoding(setterMethod); class_addMethod(clazz, setterSelector, (IMP)kvo_setter, types); } // Step 4: Add this observation info to saved observation objects PGObservationInfo *info = [[PGObservationInfo alloc] initWithObserver:observer Key:key block:block]; NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers)); if (!observers) { observers = [NSMutableArray array]; objc_setAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } [observers addObject:info]; } 再来一步一步细看。 第一步里,先通过 setterForGetter() 方法获得相应的 setter 的名字(SEL)。也就是把 key 的首字母大写,然后前面加上 set 后面加上 :,这样 key 就变成了 setKey:。然后再用 class_getInstanceMethod 去获得 setKey: 的实现(Method)。如果没有,自然要抛出异常。 第二步,我们先看类名有没有我们定义的前缀。如果没有,我们就去创建新的子类,并通过 object_setClass() 修改 isa 指针。 - (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName { NSString *kvoClazzName = [kPGKVOClassPrefix stringByAppendingString:originalClazzName]; Class clazz = NSClassFromString(kvoClazzName); if (clazz) { return clazz; } // class doesn't exist yet, make it Class originalClazz = object_getClass(self); Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0); // grab class method's signature so we can borrow it Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class)); const char *types = method_getTypeEncoding(clazzMethod); class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types); objc_registerClassPair(kvoClazz); return kvoClazz; } 动态创建新的类需要用 objc/runtime.h 中定义的 objc_allocateClassPair() 函数。传一个父类,类名,然后额外的空间(通常为 0),它返回给你一个类。然后就给这个类添加方法,也可以添加变量。这里,我们只重写了 class 方法。哈哈,跟 Apple 一样,这时候我们也企图隐藏这个子类的存在。最后 objc_registerClassPair() 告诉 Runtime 这个类的存在。 第三步,重写 setter 方法。新的 setter 在调用原 setter 方法后,通知每个观察者(调用之前传入的 block ): static void kvo_setter(id self, SEL _cmd, id newValue) { NSString *setterName = NSStringFromSelector(_cmd); NSString *getterName = getterForSetter(setterName); if (!getterName) { // throw invalid argument exception } id oldValue = [self valueForKey:getterName]; struct objc_super superclazz = { .receiver = self, .super_class = class_getSuperclass(object_getClass(self)) }; // cast our pointer so the compiler won't complain void (*objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper; // call super's setter, which is original class's setter method objc_msgSendSuperCasted(&superclazz, _cmd, newValue); // look up observers and call the blocks NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers)); for (PGObservationInfo *each in observers) { if ([each.key isEqualToString:getterName]) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ each.block(self, getterName, oldValue, newValue); }); } } } 细心的同学会发现我们对 objc_msgSendSuper 进行类型转换。在 Xcode 6 里,新的 LLVM 会对 objc_msgSendSuper 以及 objc_msgSend 做严格的类型检查,如果不做类型转换。Xcode 会抱怨有 too many arguments 的错误。(在 WWDC 2014 的视频 What new in LLVM 中有提到过这个问题。) 最后一步,把这个观察的相关信息存在 associatedObject 里。观察的相关信息(观察者,被观察的 key, 和传入的 block )封装在 PGObservationInfo 类里。 @interface PGObservationInfo : NSObject @property (nonatomic, weak) NSObject *observer; @property (nonatomic, copy) NSString *key; @property (nonatomic, copy) PGObservingBlock block; @end 就此,一个基本的 KVO 就可以 work 了。当然,这只是一个一天多做出来的小东西,会有 bug,也有很多可以优化完善的地方。但作为 demo 演示如何利用 Runtime 动态创建类、如何实现 KVO,足已。 完整的例子可以从这里下载:ImplementKVO]]></content>
</entry>
<entry>
<title><![CDATA[iOS线程安全篇]]></title>
<url>%2Fposts%2F47161%2F</url>
<content type="text"><![CDATA[一、前言前段时间看了几个开源项目,发现他们保持线程同步的方式各不相同,有@synchronized、NSLock、dispatch_semaphore、NSCondition、pthread_mutex、OSSpinLock。后来网上查了一下,发现他们的实现机制各不相同,性能也各不一样。不好意思,我们平常使用最多的@synchronized是性能最差的。下面我们先分别介绍每个加锁方式的使用,在使用一个案例来对他们进行性能对比。二、介绍与使用2.1、@synchronizedNSObject obj = [[NSObject alloc] init];dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @synchronized(obj) { NSLog(@”需要线程同步的操作1 开始”); sleep(3); NSLog(@”需要线程同步的操作1 结束”); }});dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); @synchronized(obj) { NSLog(@”需要线程同步的操作2”); }});@synchronized(obj)指令使用的obj为该锁的唯一标识,只有当标识相同时,才为满足互斥,如果线程2中的@synchronized(obj)改为@synchronized(self),刚线程2就不会被阻塞,@synchronized指令实现锁的优点就是我们不需要在代码中显式的创建锁对象,便可以实现锁的机制,但作为一种预防措施,@synchronized块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。上面结果的执行结果为:2016-06-29 20:48:35.747 SafeMultiThread[35945:580107] 需要线程同步的操作1 开始2016-06-29 20:48:38.748 SafeMultiThread[35945:580107] 需要线程同步的操作1 结束2016-06-29 20:48:38.749 SafeMultiThread[35945:580118] 需要线程同步的操作22.2、dispatch_semaphoredispatch_semaphore_t signal = dispatch_semaphore_create(1);dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3 NSEC_PER_SEC);dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_semaphore_wait(signal, overTime); NSLog(@”需要线程同步的操作1 开始”); sleep(2); NSLog(@”需要线程同步的操作1 结束”); dispatch_semaphore_signal(signal);});dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); dispatch_semaphore_wait(signal, overTime); NSLog(@”需要线程同步的操作2”); dispatch_semaphore_signal(signal);});dispatch_semaphore是GCD用来同步的一种方式,与他相关的共有三个函数,分别是dispatch_semaphore_create,dispatch_semaphore_signal,dispatch_semaphore_wait。(1)dispatch_semaphore_create的声明为:dispatch_semaphore_t dispatch_semaphore_create(long value);传入的参数为long,输出一个dispatch_semaphore_t类型且值为value的信号量。值得注意的是,这里的传入的参数value必须大于或等于0,否则dispatch_semaphore_create会返回NULL。(2)dispatch_semaphore_signal的声明为:long dispatch_semaphore_signal(dispatch_semaphore_t dsema);这个函数会使传入的信号量dsema的值加1;(3) dispatch_semaphore_wait的声明为:long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);这个函数会使传入的信号量dsema的值减1;这个函数的作用是这样的,如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,不能直接传入整形或float型数),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。dispatch_semaphore 是信号量,但当信号总量设为 1 时也可以当作锁来。在没有等待情况出现时,它的性能比 pthread_mutex 还要高,但一旦有等待情况出现时,性能就会下降许多。相对于 OSSpinLock 来说,它的优势在于等待时不会消耗 CPU 资源。如上的代码,如果超时时间overTime设置成>2,可完成同步操作。如果overTime<2的话,在线程1还没有执行完成的情况下,此时超时了,将自动执行下面的代码。上面代码的执行结果为:2016-06-29 20:47:52.324 SafeMultiThread[35945:579032] 需要线程同步的操作1 开始2016-06-29 20:47:55.325 SafeMultiThread[35945:579032] 需要线程同步的操作1 结束2016-06-29 20:47:55.326 SafeMultiThread[35945:579033] 需要线程同步的操作2如果把超时时间设置为<2s的时候,执行的结果就是:2016-06-30 18:53:24.049 SafeMultiThread[30834:434334] 需要线程同步的操作1 开始2016-06-30 18:53:25.554 SafeMultiThread[30834:434332] 需要线程同步的操作22016-06-30 18:53:26.054 SafeMultiThread[30834:434334] 需要线程同步的操作1 结束2.3、NSLockNSLock lock = [[NSLock alloc] init];dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //[lock lock]; [lock lockBeforeDate:[NSDate date]]; NSLog(@”需要线程同步的操作1 开始”); sleep(2); NSLog(@”需要线程同步的操作1 结束”); [lock unlock];});dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); if ([lock tryLock]) {//尝试获取锁,如果获取不到返回NO,不会阻塞该线程 NSLog(@”锁可用的操作”); [lock unlock]; }else{ NSLog(@”锁不可用的操作”); } NSDate date = [[NSDate alloc] initWithTimeIntervalSinceNow:3]; if ([lock lockBeforeDate:date]) {//尝试在未来的3s内获取锁,并阻塞该线程,如果3s内获取不到恢复线程, 返回NO,不会阻塞该线程 NSLog(@”没有超时,获得锁”); [lock unlock]; }else{ NSLog(@”超时,没有获得锁”); }});NSLock是Cocoa提供给我们最基本的锁对象,这也是我们经常所使用的,除lock和unlock方法外,NSLock还提供了tryLock和lockBeforeDate:两个方法,前一个方法会尝试加锁,如果锁不可用(已经被锁住),刚并不会阻塞线程,并返回NO。lockBeforeDate:方法会在所指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO。上面代码的执行结果为:2016-06-29 20:45:08.864 SafeMultiThread[35911:575795] 需要线程同步的操作1 开始2016-06-29 20:45:09.869 SafeMultiThread[35911:575781] 锁不可用的操作2016-06-29 20:45:10.869 SafeMultiThread[35911:575795] 需要线程同步的操作1 结束2016-06-29 20:45:10.870 SafeMultiThread[35911:575781] 没有超时,获得锁源码定义如下:@protocol NSLocking- (void)lock;- (void)unlock;@end@interface NSLock : NSObject {@private void _priv;}- (BOOL)tryLock;- (BOOL)lockBeforeDate:(NSDate )limit;@property (nullable, copy) NSString name NS_AVAILABLE(10_5, 2_0);@end2.4、NSRecursiveLock递归锁//NSLock lock = [[NSLock alloc] init];NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^RecursiveMethod)(int); RecursiveMethod = ^(int value) { [lock lock]; if (value > 0) { NSLog(@”value = %d”, value); sleep(1); RecursiveMethod(value - 1); } [lock unlock]; }; RecursiveMethod(5);}); NSRecursiveLock实际上定义的是一个递归锁,这个锁可以被同一线程多次请求,而不会引起死锁。这主要是用在循环或递归操作中。这段代码是一个典型的死锁情况。在我们的线程中,RecursiveMethod是递归调用的。所以每次进入这个block时,都会去加一次锁,而从第二次开始,由于锁已经被使用了且没有解锁,所以它需要等待锁被解除,这样就导致了死锁,线程被阻塞住了。调试器中会输出如下信息:2016-06-30 19:08:06.393 SafeMultiThread[30928:449008] value = 52016-06-30 19:08:07.399 SafeMultiThread[30928:449008] -[NSLock lock]: deadlock ( '(null)')2016-06-30 19:08:07.399 SafeMultiThread[30928:449008] Break on _NSLockError() to debug.在这种情况下,我们就可以使用NSRecursiveLock。它可以允许同一线程多次加锁,而不会造成死锁。递归锁会跟踪它被lock的次数。每次成功的lock都必须平衡调用unlock操作。只有所有达到这种平衡,锁最后才能被释放,以供其它线程使用。如果我们将NSLock代替为NSRecursiveLock,上面代码则会正确执行。2016-06-30 19:09:41.414 SafeMultiThread[30949:450684] value = 52016-06-30 19:09:42.418 SafeMultiThread[30949:450684] value = 42016-06-30 19:09:43.419 SafeMultiThread[30949:450684] value = 32016-06-30 19:09:44.424 SafeMultiThread[30949:450684] value = 22016-06-30 19:09:45.426 SafeMultiThread[30949:450684] value = 1如果需要其他功能,源码定义如下:@interface NSRecursiveLock : NSObject {@private void _priv;}- (BOOL)tryLock;- (BOOL)lockBeforeDate:(NSDate )limit;@property (nullable, copy) NSString name NS_AVAILABLE(10_5, 2_0);@end2.5、NSConditionLock条件锁NSMutableArray products = [NSMutableArray array];NSInteger HAS_DATA = 1;NSInteger NO_DATA = 0;dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ while (1) { [lock lockWhenCondition:NO_DATA]; [products addObject:[[NSObject alloc] init]]; NSLog(@”produce a product,总量:%zi”,products.count); [lock unlockWithCondition:HAS_DATA]; sleep(1); }});dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ while (1) { NSLog(@”wait for product”); [lock lockWhenCondition:HAS_DATA]; [products removeObjectAtIndex:0]; NSLog(@”custome a product”); [lock unlockWithCondition:NO_DATA]; }});当我们在使用多线程的时候,有时一把只会lock和unlock的锁未必就能完全满足我们的使用。因为普通的锁只能关心锁与不锁,而不在乎用什么钥匙才能开锁,而我们在处理资源共享的时候,多数情况是只有满足一定条件的情况下才能打开这把锁:在线程1中的加锁使用了lock,所以是不需要条件的,所以顺利的就锁住了,但在unlock的使用了一个整型的条件,它可以开启其它线程中正在等待这把钥匙的临界地,而线程2则需要一把被标识为2的钥匙,所以当线程1循环到最后一次的时候,才最终打开了线程2中的阻塞。但即便如此,NSConditionLock也跟其它的锁一样,是需要lock与unlock对应的,只是lock,lockWhenCondition:与unlock,unlockWithCondition:是可以随意组合的,当然这是与你的需求相关的。上面代码执行结果如下:2016-06-30 20:31:58.699 SafeMultiThread[31282:521698] wait for product2016-06-30 20:31:58.699 SafeMultiThread[31282:521708] produce a product,总量:12016-06-30 20:31:58.700 SafeMultiThread[31282:521698] custome a product2016-06-30 20:31:58.700 SafeMultiThread[31282:521698] wait for product2016-06-30 20:31:59.705 SafeMultiThread[31282:521708] produce a product,总量:12016-06-30 20:31:59.706 SafeMultiThread[31282:521698] custome a product2016-06-30 20:31:59.706 SafeMultiThread[31282:521698] wait for product2016-06-30 20:32:00.707 SafeMultiThread[31282:521708] produce a product,总量:12016-06-30 20:32:00.708 SafeMultiThread[31282:521698] custome a product如果你需要其他功能,源码定义如下:@interface NSConditionLock : NSObject {@private void _priv;}- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;@property (readonly) NSInteger condition;- (void)lockWhenCondition:(NSInteger)condition;- (BOOL)tryLock;- (BOOL)tryLockWhenCondition:(NSInteger)condition;- (void)unlockWithCondition:(NSInteger)condition;- (BOOL)lockBeforeDate:(NSDate )limit;- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate )limit;@property (nullable, copy) NSString name NS_AVAILABLE(10_5, 2_0);@end2.6、NSConditionNSCondition condition = [[NSCondition alloc] init];NSMutableArray products = [NSMutableArray array];dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ while (1) { [condition lock]; if ([products count] == 0) { NSLog(@”wait for product”); [condition wait]; } [products removeObjectAtIndex:0]; NSLog(@”custome a product”); [condition unlock]; } });dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ while (1) { [condition lock]; [products addObject:[[NSObject alloc] init]]; NSLog(@”produce a product,总量:%zi”,products.count); [condition signal]; &nnbsp; [condition unlock]; sleep(1); }});一种最基本的条件锁。手动控制线程wait和signal。[condition lock];一般用于多线程同时访问、修改同一个数据源,保证在同一时间内数据源只被访问、修改一次,其他线程的命令需要在lock 外等待,只到unlock ,才可访问[condition unlock];与lock 同时使用[condition wait];让当前线程处于等待状态[condition signal];CPU发信号告诉线程不用在等待,可以继续执行上面代码执行结果如下:2016-06-30 20:21:25.295 SafeMultiThread[31256:513991] wait for product2016-06-30 20:21:25.296 SafeMultiThread[31256:513994] produce a product,总量:12016-06-30 20:21:25.296 SafeMultiThread[31256:513991] custome a product2016-06-30 20:21:25.297 SafeMultiThread[31256:513991] wait for product2016-06-30 20:21:26.302 SafeMultiThread[31256:513994] produce a product,总量:12016-06-30 20:21:26.302 SafeMultiThread[31256:513991] custome a product2016-06-30 20:21:26.302 SafeMultiThread[31256:513991] wait for product2016-06-30 20:21:27.307 SafeMultiThread[31256:513994] produce a product,总量:12016-06-30 20:21:27.308 SafeMultiThread[31256:513991] custome a product2.7、pthread_mutexblock pthread_mutex_t theLock;pthread_mutex_init(&theLock, NULL);dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ pthread_mutex_lock(&theLock); NSLog(@”需要线程同步的操作1 开始”); sleep(3); NSLog(@”需要线程同步的操作1 结束”); pthread_mutex_unlock(&theLock);});dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); pthread_mutex_lock(&theLock); NSLog(@”需要线程同步的操作2”); pthread_mutex_unlock(&theLock);});c语言定义下多线程加锁方式。1:pthread_mutex_init(pthread_mutex_t mutex,const pthread_mutexattr_t attr);初始化锁变量mutex。attr为锁属性,NULL值为默认属性。2:pthread_mutex_lock(pthread_mutex_t mutex);加锁3:pthread_mutex_tylock(pthread_mutex_t mutex);加锁,但是与2不一样的是当锁已经在使用的时候,返回为EBUSY,而不是挂起等待。4:pthread_mutex_unlock(pthread_mutex_t mutex);释放锁5:pthread_mutex_destroy(pthread_mutex_t mutex);使用完后释放代码执行操作结果如下:2016-06-30 21:13:32.440 SafeMultiThread[31429:548869] 需要线程同步的操作1 开始2016-06-30 21:13:35.445 SafeMultiThread[31429:548869] 需要线程同步的操作1 结束2016-06-30 21:13:35.446 SafeMultiThread[31429:548866] 需要线程同步的操作22.8、pthread_mutex(recursive) block pthread_mutex_t theLock;//pthread_mutex_init(&theLock, NULL);pthread_mutexattr_t attr;pthread_mutexattr_init(&attr);pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);pthread_mutex_init(&lock, &attr);pthread_mutexattr_destroy(&attr);dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^RecursiveMethod)(int); RecursiveMethod = ^(int value) { pthread_mutex_lock(&theLock); if (value > 0) { NSLog(@”value = %d”, value); sleep(1); RecursiveMethod(value - 1); } pthread_mutex_unlock(&theLock); }; RecursiveMethod(5);});这是pthread_mutex为了防止在递归的情况下出现死锁而出现的递归锁。作用和NSRecursiveLock递归锁类似。如果使用pthread_mutex_init(&theLock, NULL);初始化锁的话,上面的代码会出现死锁现象。如果使用递归锁的形式,则没有问题。2.9、OSSpinLock__block OSSpinLock theLock = OS_SPINLOCK_INIT;dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ OSSpinLockLock(&theLock); NSLog(@”需要线程同步的操作1 开始”); sleep(3); NSLog(@”需要线程同步的操作1 结束”); OSSpinLockUnlock(&theLock);});dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ OSSpinLockLock(&theLock); sleep(1); NSLog(@”需要线程同步的操作2”); OSSpinLockUnlock(&theLock);});OSSpinLock 自旋锁,性能最高的锁。原理很简单,就是一直 do while 忙等。它的缺点是当等待时会消耗大量 CPU 资源,所以它不适用于较长时间的任务。 不过最近YY大神在自己的博客不再安全的 OSSpinLock中说明了OSSpinLock已经不再安全,请大家谨慎使用。三、性能对比对以上各个锁进行1000000此的加锁解锁的空操作时间如下:OSSpinLock: 46.15 msdispatch_semaphore: 56.50 mspthread_mutex: 178.28 msNSCondition: 193.38 msNSLock: 175.02 mspthread_mutex(recursive): 172.56 msNSRecursiveLock: 157.44 msNSConditionLock: 490.04 ms@synchronized: 371.17 ms总的来说:OSSpinLock和dispatch_semaphore的效率远远高于其他。@synchronized和NSConditionLock效率较差。鉴于OSSpinLock的不安全,所以我们在开发中如果考虑性能的话,建议使用dispatch_semaphore。如果不考虑性能,只是图个方便的话,那就使用@synchronized。 设计代码可在我的git下载]]></content>
</entry>
<entry>
<title><![CDATA[Swift使用OC框架]]></title>
<url>%2Fposts%2F28457%2F</url>
<content type="text"><![CDATA[Swift使用OC框架 场景一:使用Cocopods导入框架看以前的教程用Cocopods导入框架也要手动设置桥接文件,我最近创建的项目用Cocopods安装的SDWebImage和SVProgressHUD发现xcode会自动生成一个桥接的.h文件名字是Targets-Bridging-Header.h,并且会自动设置路径如图,这个稍后在场景2再继续介绍 用Cocopods导入的OC框架Xcode会自动生成一个文件但是我并不能在本地找到他,看SDWebImage的这个文件如下在使用的过程中直接用import SDWebImage就可以使用 SDWebImage的各种方法了这种由Cocopods导入的框架都会生成这个类似的文件不用在桥接文件有引入头文件类似#import “UIImageView+WebCache.h”. 场景二:没有使用Cocopods或者使用Cocopods手动导入框架没有使用Cocopods就需要自己新建一个桥接文件了当然如果用了Cocopods可以使用Xcode自动生成的就是场景一的那种情况了,下图新建一个桥接文件命名随意这里的图是用别人的真是随意了啊,我建议命名规则可以按照Targets-Bridging-Header.h毕竟这样看起来正规点嘛填好文件名字,选中语言为Objective-C,点击Next 这个时候回出现一个弹框,意思就是 你是否想要配置一个OC桥接文件,选择创建,路径可以按照场景一的图找到就不再上图了。这里强调一个重要设置注意、注意、注意重要的事情说三遍嘛1.选择target(就是左边你的工程target)—— BuildSettings —— search Paths 下的 User Header Search Paths 2.双击后面的空白区域,并且点击“+”号添加一项:并且输入:“$(PODS_ROOT)”(没有引号),选择:recursive(会在相应的目录递归搜索文件)。 这个设置完才可以保证在桥接文件里能够正常引用OC库 一切设置妥当就可以手动拖入OC库了,举个例子我用的Toast 愉快使用OC和Swift的混编吧]]></content>
</entry>
<entry>
<title><![CDATA[OC中load方法和initialize方法的区别]]></title>
<url>%2Fposts%2F56109%2F</url>
<content type="text"><![CDATA[OC中有两个特殊的类方法,分别是load和initialize。本文总结一下这两个方法的区别于联系、使用场景和注意事项。Demo可以在我的Github上找到——load和initialize,如果觉得有帮助还望点个star以示支持,总结在文章末尾。load顾名思义,load方法在这个文件被程序装载时调用。只要是在Compile Sources中出现的文件总是会被装载,这与这个类是否被用到无关,因此load方法总是在main函数之前调用。调用规则如果一个类实现了load方法,在调用这个方法前会首先调用父类的load方法。而且这个过程是自动完成的,并不需要我们手动实现:// In Parent.m+ (void)load { NSLog(@”Load Class Parent”);}// In Child.m,继承自Parent+ (void)load { NSLog(@”Load Class Child”);}// In Child+load.m,Child类的分类+ (void)load { NSLog(@”Load Class Child+load”);}// 运行结果:/ 2016-02-01 21:28:14.379 load[11789:1435378] Load Class Parent 2016-02-01 21:28:14.380 load[11789:1435378] Load Class Child 2016-02-01 22:28:14.381 load[11789:1435378] Load Class Child+load /如果一个类没有实现load方法,那么就不会调用它父类的load方法,这一点与正常的类继承和方法调用不一样,需要额外注意一下。执行顺序load方法调用时,系统处于脆弱状态,如果调用别的类的方法,且该方法依赖于那个类的load方法进行初始化设置,那么必须确保那个类的load方法已经调用了,比如demo中的这段代码,打印出的字符串就为null: 12345678910// In Child.m+ (void)load { NSLog(@"Load Class Child"); Other *other = [Other new]; [other originalFunc]; // 如果不先调用other的load,下面这行代码就无效,打印出null [Other printName];} load方法的调用顺序其实有迹可循,我们看到demo的项目设置如下:执行顺序在Compile Sources中,文件的排放顺序就是其装载顺序,自然也就是load方法调用的顺序。这一点也证明了load方法中会自动调用父类的方法,因为在demo的输出结果中,Parent的load方法先于Child调用,而它的装载顺序其实在Child之后。虽然在这种简单情况下我们可以辨别出各个类的load方法调用的顺序,但永远不要依赖这个顺序完成你的代码逻辑。一方面,这在后期的开发中极容易导致错误,另一方面,你实际上并不需要这么做。使用场景由于调用load方法时的环境很不安全,我们应该尽量减少load方法的逻辑。另一个原因是load方法是线程安全的,它内部使用了锁,所以我们应该避免线程阻塞在load方法中。一个常见的使用场景是在load方法中实现Method Swizzle: 1234567// In Other.m+ (void)load { Method originalFunc = class_getInstanceMethod([self class], @selector(originalFunc)); Method swizzledFunc = class_getInstanceMethod([self class], @selector(swizzledFunc)); method_exchangeImplementations(originalFunc, swizzledFunc);} 在Child类的load方法中,由于还没调用Other的load方法,所以输出结果是”Original Output”,而在main函数中,输出结果自然就变成了”Swizzled Output”。一般来说,除了Method Swizzle,别的逻辑都不应该放在load方法中实现。initialize这个方法在第一次给某个类发送消息时调用(比如实例化一个对象),并且只会调用一次。initialize方法实际上是一种惰性调用,也就是说如果一个类一直没被用到,那它的initialize方法也不会被调用,这一点有利于节约资源。调用规则与load方法类似的是,在initialize方法内部也会调用父类的方法,而且不需要我们显示的写出来。与load方法不同之处在于,即使子类没有实现initialize方法,也会调用父类的方法,这会导致一个很严重的问题:// In Parent.m+ (void)initialize { NSLog(@”Initialize Parent, caller Class %@”, [self class]);}// In Child.m// 注释掉initialize方法// In main.mChild *child = [Child new];运行后发现父类的initialize方法竟然调用了两次:2016-02-01 22:57:02.985 load[12772:1509345] Initialize Parent, caller Class Parent2016-02-01 22:57:02.985 load[12772:1509345] Initialize Parent, caller Class Child这是因为在创建子类对象时,首先要创建父类对象,所以会调用一次父类的initialize方法,然后创建子类时,尽管自己没有实现initialize方法,但还是会调用到父类的方法。虽然initialize方法对一个类而言只会调用一次,但这里由于出现了两个类,所以调用两次符合规则,但不符合我们的需求。正确使用initialize方法的姿势如下:// In Parent.m+ (void)initialize { if (self == [Parent class]) { NSLog(@”Initialize Parent, caller Class %@”, [self class]); }}加上判断后,就不会因为子类而调用到自己的initialize方法了。使用场景initialize方法主要用来对一些不方便在编译期初始化的对象进行赋值。比如NSMutableArray这种类型的实例化依赖于runtime的消息发送,所以显然无法在编译器初始化: 12345678910// In Parent.mstatic int someNumber = 0; // int类型可以在编译期赋值static NSMutableArray *someObjects;+ (void)initialize { if (self == [Parent class]) { // 不方便编译期复制的对象在这里赋值 someObjects = [[NSMutableArray alloc] init]; }} 总结1 load和initialize方法都会在实例化对象之前调用,以main函数为分水岭,前者在main函数之前调用,后者在main函数之后并且给对象发消息(实例化或者调用+方法)时候调用。这两个方法会被自动调用,不能手动调用它们。。 load和initialize方法都不用显示的调用父类的方法而是自动调用,即使子类没有initialize方法也会调用父类的方法,而load方法则不会调用父类。 load方法通常用来进行Method Swizzle,initialize方法一般用于初始化全局变量或静态变量。 load和initialize方法内部使用了锁,因此它们是线程安全的。实现时要尽可能保持简单,避免阻塞线程,不要再使用锁。 总结2+load 首先,load方法是一定会在runtime中被调用的,只要类被添加到runtime中了,就会调用load方法,所以我们可以自己实现laod方法来在这个时候执行一些行为。 而且有意思的一点是,load方法不会覆盖。也就是说,如果子类实现了load方法,那么会先调用父类的load方法,然后又去执行子类的load方法。同样的,如果分类实现了load方法,也会先执行主类的load方法,然后又会去执行分类的load方法。所以父类的load会执行很多次,这一点需要注意。而且执行顺序是 类 -> 子类 ->分类。而不同类之间的顺序不一定。 +initialize 与load不同的是,initialize方法不一定会执行。只有当一个类第一次被发送消息的时候会执行,注意是第一次。什么叫发送消息呢,就是执行类的一些方法的时候。也就是说这个方法是懒加载,没有用到这个类就不会调用,可以节省系统资源。 还有一点截然相反,却更符合我们预期的就是,initialize方法会覆盖。也就是说如果子类实现了initialize方法,就不会执行父类的了,直接执行子类本身的。如果分类实现了initialize方法,也不会再执行主类的。所以initialize方法的执行覆盖顺序是 分类 -> 子类 ->类。且只会有一个initialize方法被执行。 原文1,原文2]]></content>
</entry>
<entry>
<title><![CDATA[swift报错UICollectionView must be initialized with a non-nil layout parameter]]></title>
<url>%2Fposts%2F25752%2F</url>
<content type="text"><![CDATA[今天学习swift的UICollectionView时候项目运行就报错,控制台显示“UICollectionView must be initialized with a non-nil layout parameter”,意思很明显就是UICollectionView初始化时候没有给个有效的layout,就是说UICollectionViewFlowLayout没有初始化,我就纳闷了明明是初始化了啊 123全局变量 private var layout = UICollectionViewFlowLayout() private var collectionView :UICollectionView() 然后在viewDidLoad里 1collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout) 看起来没什么问题,才初学swift不知道 1private var collectionView :UICollectionView() 这样写已经是初始化了,所以就报错了,应该在声明变量的时候改为如下写法 1private var collectionView :UICollectionView! 这样就就只是声明变量了再在viewDidLoad里初始化使用就没问题了 12 layout = UICollectionViewFlowLayout()collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)]]></content>
</entry>
<entry>
<title><![CDATA[ios9设置通用链接]]></title>
<url>%2Fposts%2F28111%2F</url>
<content type="text"><![CDATA[什么是通用链接?通过唯一的网址, 就可以链接一个特定的视图到你的 APP 里面, 不需要特别的 schema通用链接是一种能够方便的通过传统 HTTP 链接来启动 APP, 使 用相同的网址打开网站和 APP。当你的应用支持通用链接,iOS9 之后 , 用户可以点击一个链接跳转到你的网站,并获得无缝重定向到您安装的应用程序,而无需通过 Safari 浏览器。如果你的应用不支持的话,点击链接将会用 Safari 来打开。。试想一下 Twitter 使用了通用链接, 那么你每在 twitter.com 点击一个链接, 你的 iOS 设备都会在 Twitter 里面自动打开这个页面, 而不是当你没有安装时转到普通的网页。这个用户体验是顺畅的, 最重要的是用户不会失去上下文(跳到 APP 后 Safari 上不再留下空标签)。 通用链接的优点通用链接比自定义的URL链接有几处尤为突出的好处,具体来说,通用链接独特性:与自定义的URL链接相比,通用链接不能被其他的应用程序所访问,因为 1:它们使用的是标准的HTTP 或 HTTPS 链接到你的网站。安全性:当用户安装应用程序,iOS会检查您已经上传到web服务器文件,以确 2:保您的网站允许你的应用程序能打开代表它的URL 文件,只要你创建并且上传该文件,那么你的应用和服务器之间的关联是安全的。灵活性:当你的应用程序没有被安装的时候,通用链接照样能够工作。 3:当用户没有安装你的应用程序,点击该链接,将会以用户所期望的以Safari的形式访问。简单性:通用链接既能支持你的网站,又能支持你的应用私有性:其他的应用程序能和你的应用程序通信,不管你的应用是否被安装。 准备使用通用链接实现通用链接不难, 但首先必须遵守一些先决条件。如下: 有一个注册的域名 通过 SSL 访问域名 支持上传一个 JSON 文件到你的域名 至少 iOS 9 beta 2 版本 [下载],这很重要,因为如果是之前的测试版本你需要做额外的操作。 至少 Xcode 7 beta 2如果你都有了, 那就来按照下面3个步骤来做吧。 1.开启 Associated Domains 域: 进入苹果Apple Developer — Member Center - Certificates, Identifiers & Profiles – Identifiers - App IDs –Edit 然后开启打钩 Associated Domains 后保存。 2.添加域名到 Capabilities首先, 你必须在 Xcode 的 capabilities 里 添加你的 APP 域名, 必须用 applinks: 前置它:还添加一些你可能拥有的子域和扩展(www.domain.com, news.domain.com 等等)。 用applinks: 前缀添加所有域名, 同时不要忘了包含所有可能需要的子域名 这将使你的 APP 从你的域名请求一个特殊的 JSON 文件 apple-app-site-association。当你第一次启动 APP,它会从 https://domain.com/apple-app-site-association 下载这个文件。跳到下个步骤来了解如何构建这个文件。 3.上传 apple-app-site-association 文件该文件必须存在且为了安全原因可使用 SSL 通过 GET 请求访问到。你可以打开一个文本编辑器然后写一个这样的简单 JSON 格式: 123456789101112131415{ "applinks": { "apps": [], "details": [ { "appID": "L2LYHXGC7E.com.domain.Appr", "paths": [ "*" ,"/","/archives/","/archives/*"] }, { "appID": "ABCD1234.com.apple.wwdc", "paths": [ "*" ] } ] }} 最后, 上传这个文件到你的域名根目录。如果你打开 https://domain.com/apple-app-site-association 可以看到你的文件, 我看到网上一部分人说可以打开苹果的这个网址检测apple-app-site-association是否是有效,在我部署完以上步骤后测试下最后一项Link to Application也是无效的但在我做完所有步骤后发现已经实现了本文要实现的功能,也就是说具体效果还是要以自己测试为准。那么你就可以继续下一步了。 注意事项(1) 1, paths 路径是大小写敏感的 2, paths 内容可明确哪些通用链接需要被处理,哪些不需要 3, NOT 使用:为了明确指出不被处理的链接,可增加 “NOT”在链接前面例 如 “paths”: [ “/wwdc/news/“, “NOT /videos/wwdc/2010/“, “/videos/wwdc/201?/“] 4, 你可以使用 * 明确所有的网页 5, 也可以使用一个明确的的 URL,例如 /wwdc/news/ 6, 也可以追加 到你的 URL ,例如 /videos/wwdc/2015/ 也可以使用 来匹配任何字符,使用 ? 来匹配一个字符,可以在路径 中使用这种混搭的形式, 例如 /foo//bar/201?/mypage 注意事项(2) 1, “appID”组成部分:TeamID + BundleId TeamID 2, BundleId 一定要和 APP 的 BundleId 一致。 3, apple-app-site-association 文件不能带后缀 4, apple-app-site-association 文件需要上传到网站根目录 5, 每一个代表着应用的 字典,必须包含一个 appID 和 paths, appID 是teamID 和 bundleID,paths 是一个字符串的数组明确着你的应用支持的通用链接和你的应用程序不支持的通用连接内容。 下图就是 “appID”组成部分:TeamID + BundleId TeamID 最后为了在 APP 里支持通用链接, 你需要在 AppDelegate 里实现相对应的逻辑 附上我那个验证apple-app-site-association的截图吧B6B1BAB7-FBDD-4CFD-8862-9B78738787E4 posted by (C)sky521 效果图 在“全聚星”打开 IMG_0413 posted by (C)sky521]]></content>
</entry>
<entry>
<title><![CDATA[解决xcode8.2控制台数据打印不全]]></title>
<url>%2Fposts%2F16299%2F</url>
<content type="text"><![CDATA[最近升级Xcode8,开发工具出现了,控制台log数据不完整,经常打印一半,就没有了,经过查询一番终于找到了解决办法! 解决办法:宏定义去log日志数据,把NSlog换成print 123456789#ifdef DEBUG#define DLog( s, ... ) printf("class: <%p %s:(%d) > method: %s \n%s\n", self, [[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __PRETTY_FUNCTION__, [[NSString stringWithFormat:(s), ##__VA_ARGS__] UTF8String] )#else#define DLog( s, ... )#endif 日了狗了 让我纠结半天 ,心想xcode不会出现这么低级的问题吧,没想查了一下还真有这个问题,事实证明‘实践是证明真理的唯一标准’啊。]]></content>
</entry>
<entry>
<title><![CDATA[解决TableViewCell和CollectionViewCell复用布局错乱问题]]></title>
<url>%2Fposts%2F14803%2F</url>
<content type="text"><![CDATA[解决TableViewCell和UICollectionViewCell使用过程中因为重用机制问题造成数据重复显示或者布局错乱问题。 常规配置如下 当超过tableView显示的范围的时候 后面显示的内容将会和前面重复 123456789101112131415161718192021// 这样配置的话超过页面显示的内容会重复出现- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ // 定义唯一标识 static NSString *CellIdentifier = @"Cell"; // 通过唯一标识创建cell实例 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; // 判断为空进行初始化 --(当拉动页面显示超过主页面内容的时候就会重用之前的cell,而不会再次初始化) if (!cell) { cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier]; } // 对cell 进行简单地数据配置 cell.textLabel.text = @"text"; cell.detailTextLabel.text = @"text"; cell.imageView.image = [UIImage imageNamed:@"4.png"]; return cell;}//通过以下3方案可以解决 方案一: 让每个cell都拥有一个对应的标识 这样做也会让cell无法重用 所以也就不会是重复显示了 显示内容比较多时内存占用也是比较多的 1234567891011121314151617181920/ /方案一 通过不让他重用cell 来解决重复显示 不同的是每个cell对应一个标识- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 定义cell标识 每个cell对应一个自己的标识 NSString *CellIdentifier = [NSString stringWithFormat:@"cell%ld%ld",indexPath.section,indexPath.row]; // 通过不同标识创建cell实例 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; // 判断为空进行初始化 --(当拉动页面显示超过主页面内容的时候就会重用之前的cell,而不会再次初始化) if (!cell) { cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier]; } // 对cell 进行简单地数据配置 cell.textLabel.text = @"text"; cell.detailTextLabel.text = @"text"; cell.imageView.image = [UIImage imageNamed:@"4.png"]; return cell; } <!-- more --> 方案二: 同样取消cell的重用机制,通过indexPath来创建cell 将可以解决重复显示问题 不过这样做相对于大数据来说内存就比较吃紧了 123456789101112131415161718// 方案二 通过不让他重用cell 来解决重复显示- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 定义唯一标识 static NSString *CellIdentifier = @"Cell"; // 通过indexPath创建cell实例 每一个cell都是单独的 UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; // 判断为空进行初始化 --(当拉动页面显示超过主页面内容的时候就会重用之前的cell,而不会再次初始化) if (!cell) { cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier]; } // 对cell 进行简单地数据配置 cell.textLabel.text = @"text"; cell.detailTextLabel.text = @"text"; cell.imageView.image = [UIImage imageNamed:@"4.png"]; return cell; } ###方案三 只要最后一个显示的cell内容不为空,然后把它的子视图全部删除,等同于把这个cell单独出来了 然后跟新数据就可以解决重复显示 12345678910111213141516171819202122232425// 方案三 当页面拉动需要显示新数据的时候,把最后一个cell进行删除 就有可以自定义cell 此方案即可避免重复显示,又重用了cell相对内存管理来说是最好的方案 前两者相对比较消耗内存- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ // 定义唯一标识 static NSString *CellIdentifier = @"Cell"; // 通过唯一标识创建cell实例 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; // 判断为空进行初始化 --(当拉动页面显示超过主页面内容的时候就会重用之前的cell,而不会再次初始化) if (!cell) { cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier]; } else//当页面拉动的时候 当cell存在并且最后一个存在 把它进行删除就出来一个独特的cell我们在进行数据配置即可避免 { while ([cell.contentView.subviews lastObject] != nil) { [(UIView *)[cell.contentView.subviews lastObject] removeFromSuperview]; } } // 对cell 进行简单地数据配置 cell.textLabel.text = @"text"; cell.detailTextLabel.text = @"text"; cell.imageView.image = [UIImage imageNamed:@"4.png"]; return cell;} 经过测试UIcollectionViewCell布局错乱用第一种方法同样可以解决问题,实际解决情况还要自己测试才可以。 最后贴一个我的UIcollectionViewCell解决复用的代码 123456789101112- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ NSString *CellIdentifier = [NSString stringWithFormat:@"cell%ld%ld",indexPath.section,indexPath.row]; [self.collectionView registerNib:[UINib nibWithNibName:@"AudienceViewCollectionViewCell" bundle:nil] forCellWithReuseIdentifier:CellIdentifier]; // 通过不同标识创建cell实例 AudienceViewCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath]; }]]></content>
</entry>
<entry>
<title><![CDATA[Swift函数与闭包的应用实例]]></title>
<url>%2Fposts%2F9416%2F</url>
<content type="text"><![CDATA[今天的博客算是比较基础的,还是那句话,基础这东西在什么时候都是最重要的。说到函数,只要是写过程序就肯定知道函数是怎么回事,今天就来讨论一下Swift中的函数的特性以及Swift中的闭包。今天的一些小实例中回类比一下Objective-C中的函数的写法等等。Swift中的函数还是有许多好用的特性的,比如输入参数,使用元组返回多个值, 定义形参名,设定默认参数以及可变参数等等一些好用的特性。而在Swift中的闭包就是Objective-C中的Block, 除了语法不通外,两者的用法是一样的。废话少说,开始今天的主题,先搞一搞Swift中的函数,然后在搞一搞Swift中的闭包。 一.Swift中的函数 1. 函数的定义与使用 在介绍Swift中的函数之前,我想用Objective-C中的一个简单的加法函数来作为引子,然后类比着实现一下Swift中相同功能的函数。关于函数定义就比较简单了,就是一些语法的东西,下面的代码片段是Objc中求两个整数之和的函数,并返回两个数的和。- (NSInteger)sumNumber1:(NSInteger) number1 number2:(NSInteger) number2 { return number1 + number2;} 函数的功能比较简单了,就是把两个整数传进来,然后返回两个整数的和。接下来将用Swift语言实现,也好通过这个实例来熟悉一下Swift语言中定义函数的语法。下方是Swift语言中求两个整数之和的函数。语法比较简单了,在Swift中定义函数,我们会使用到关键字func来声明函数。1 //函数定义2 func sum (number1:Int, number2:Int) -> Int{3 return number1 + number2;4 } 用文字来描述Swift定义基本函数的语法就是: func 函数名 (形参列表) -> 返回值类型 { 函数体},这样你就可以定义一个函数了。当然,函数定义时还有好多其他的用法,下面会详细介绍。上面函数的调用方法如下:1 let sumTwoNubmer = sum(2, number2: 3); 2. 函数中的形参列表 关于函数中的形参列表还是有必要提上一嘴的,因为形参列表作为函数数据源之一,所以把参数列表好好的搞一搞还是很有必要的。参数列表也有很多好用的使用方式,接下来详细的介绍一下函数的形参列表。 (1) 默认的形参是常量(let) 在函数的形参列表中,默认的形参是常量。也就是相当于用let关键字对形参进行修饰了。我们可以做个试验,把上面加法函数做一个修改,在加法函数中对number1进行加1操作,你会得到一个错误,这个错误的大体意思就是“number1是不可被修改的,因为它是let类型的常量”。并且编译器还给人出了Fix-it(修复)的方案,就是在number1前面使用var关键字进行修饰,使其成为变量,这样才可以修改其值。 上面说这么多,一句话:形参默认是常量,如果你想让其是变量,那么你可以使用var关键字进行修饰,这样被关键字var修饰的变量在函数中就可以被修改。下方就是报的这个错误,和编译器提供的解决方案。(在Objc中默认可以在函数中改变形参的值) (2)给形参命名 为了代码的可读性和可维护性,我们在定义函数时,需要为每个参数名一个名字,这样调用者见名知意,很容易就知道这个参数代表什么意思了。接下来还是在上述加法函数中进行修改,为每个参数名一个名字,并看一下调用方式。修改上面的函数,给第一个形参命名成numberOne, 第二个形参为numberTwo, 下方是修改后的函数。 紧接着sum()函数的调用方式也会有所改变,在调用函数时编译器会给出参数的名称,这样调用者一目了然。1 //函数定义2 func sum (numberOne number1:Int, numberTwo number2:Int) -> Int{3 return number1 + number2;4 }56 let sumTwoNubmer = sum(numberOne: 10, numberTwo: 20); 调用上述函数时,下方是编译器给出的提示,一目了然呢。 关于Swift中参数名的内容,要说明的是在Swift1.0的时候,你可以在参数前面添加上#号,然后参数名就与变量(或者常量)的名字相同,而Swift2.0后这个东西去掉了,因为默认就相当于Swift1.0中添加#号。 (3) 函数的传参与传引用 先暂且这么说着,在C语言的函数中可以给函数传入参数,或者传入实参的内存地址就是所谓的传引用。如果传入的是引用的话,在函数中对值进行修改的话,那么出了函数,这个被修改的值是可以被保留的。在Swift中也是可以的,不过你需要使用inout关键字修饰形参,并且在使用该函数时,用&来修饰。这一点和C语言中类似,&就是取地址符。下方是inout使用的一个小实例。1 func incrementStepTwo (inout myNumber:Int) {2 myNumber += 23 }4 var myTestNumber = 65 incrementStepTow(&myTestNumber) //myTestNumber = 8 myTestNumber变量经过incrementStepTwo()函数后,其值就会增加2。当然前提是myTestNumber是变量,如果myTestNumber是常量的话,那么对不起,调用该函数就会报错,下面是把var改成let后IDE给的错误提示。错误原因很显然是你动了一个不该动的值,也就是常量不可再次被修改的。 (4) 不定参数函数 不定参数函数也就是形参的个数是不定的,但是形参的类型必须是相同的。不定形参在使用时怎么取呢?不定个数的形参实际上是一个数组,我们可以通过for循环的形式来遍历出每个形参的值,然后使用就可以了。下方incrementMultableAdd()函数的形参的个数是不定的,其功能是求多个整数的和。在函数中我们只需遍历每个参数,然后把每个参数进行相加,最后返回所求的和即可。函数比较简单,再此就不在啰嗦了。 (5) 默认形参值 在Swift语言中是支持给形参赋初始值的,这一点在其他一些编程语言中也是支持的。但是Objective-C这么看似古老的语言中就不支持给形参指定初始值,在Swift这门现代编程语言中是支持这一特性的。默认参数要从参数列表后开始为参数指定默认值,不然就会报错。下方就是为函数的形参指定默认参数的示例。一个表白的方法sayLove(), 形参youName默认是“山伯”, 形参loverName默认是“英台”。 紧接着是sayLove函数的三种不同的调用方式,在调用函数时你可以不传参数,可以传一个参数,当然传两个也是没问题的。 因为函数的每个参数都是有名字的,在含有默认参数的函数调用时,可以给任意一个参数进行传值,其他参数取默认值,这也是Swift的一大特色之一,具体请看如下简单的代码示例: 3.函数类型 每个函数都有自己的所属类型,函数类型说白了就是如果两个函数参数列表相同以及返回值类型相同,那么这两个函数就有着相同的函数类型。在Swift中可以定义一个变量或者常量来存储一个函数的类型。接下来将用过一个实例还介绍一下函数类型是个什么东西。 (1) 首先创建两个函数类型相同的函数,一个函数返回两个整数的差值,另一个函数返回两个整数的乘积。当然这两个函数比较简单,直接上代码:1 //现定义两个函数类型相同的函数2 func diff (number1:Int, number2:Int) -> Int {3 return number1 - number2;4 }56 func mul (number1:Int, number2:Int) -> Int {7 return number1 number2;8 } (2) 函数定义好后,接着要定义个一个枚举来枚举每种函数的类型,下面定义这个枚举在选择函数时会用到,枚举定义如下:1 //定义两种计算的枚举类型2 enum CountType:Int {3 case DiffCount = 04 case MulCount5 } (3) 接下来就是把(1)和(2)中定义的东西通过一个函数来组合起来。说白了,就是定义个函数来通过枚举值返回这个枚举值所对应的函数类型。有时候说多了容易犯迷糊,就直接上代码得了。下方函数的功能就是根据传进来的枚举值来返回相应的函数类型。 1 //选择类型的函数,并返回相应的函数类型 2 func choiseCountType(countType:CountType) -> ((Int, Int) -> Int) { 3 //函数类型变量 4 var myFuncType:(Int, Int) -> Int 5 6 switch countType { 7 case .DiffCount: 8 myFuncType = diff 9 case .MulCount:10 myFuncType = mul11 }12 return myFuncType;13 } (4) 接下来就是使用(3)中定义的函数了,首先我们需要定义一个相应函数类型((Int, Int) -> Int)的变量来接收choiseCountType()函数中返回的函数类型,然后调用该函数类型变量,在Playground中执行的结果如下: 4.函数嵌套 我们可以把 3 中的代码使用函数嵌套进行重写,在Swift中是支持函数嵌套的。 所以可以吧3.1和3.2中的函数放到3.3函数中的,所以我们可以对上述代码使用函数嵌套进行重写。使用函数嵌套重写后的代码如下所示,当然,choiseCountType()函数的调用方式没用发生改变,重写后的调用方式和3.4中的调用方式是一样一样的。 1 //选择类型的函数,并返回相应的函数类型 2 func choiseCountType(countType:CountType) -> ((Int, Int) -> Int) { 3 4 //现定义两个函数类型相同的函数 5 func diff (number1:Int, number2:Int) -> Int { 6 return number1 - number2; 7 } 8 9 func mul (number1:Int, number2:Int) -> Int {10 return number1 number2;11 }121314 //函数类型变量15 var myFuncType:(Int, Int) -> Int1617 switch countType {18 case .DiffCount:19 myFuncType = diff20 case .MulCount:21 myFuncType = mul22 }23 return myFuncType;24 } 二. 闭包 说道Swift中的闭包呢,不得不提的就是Objective-C中的Block, 其实两者是一个东西,使用方式以及使用场景都是相同的。我们完全可以类比着Objective-C中的Block来介绍一下Swift中的Closure(闭包)。其实就是匿名函数。接下来的这段内容,先介绍一下Swift中Closure的基本语法,然后在类比着ObjC中的Block窥探一下Closure的使用场景。 1.Closure变量的声明 Closure就是匿名函数,我们可以定义一个闭包变量,而这个闭包变量的类型就是我们上面介绍的“函数类型”。定义一个闭包变量其实就是定义一个特定函数类型的变量,方式如下。因为Closure变量没有赋初始值,所以我们把其声明为可选类型的变量。在使用时,用!强制打开即可。1 var myCloure0:((Int, Int) -> Int)? 除了上面的方式外,我们还用另一种常用的声明闭包变量的方式。那就是使用关键字typealias定义一个特定函数类型,我们就可以拿着这个类型去声明一个Closure变量了,如下所示1 //定义闭包类型 (就是一个函数类型)2 typealias MyClosureType = (Int, Int) -> Int3 var myCloure:MyClosureType? 2. 给Closure变量赋值 给Closure变量赋值,其实就是把一个函数体赋值给一个函数类型的变量,和函数的定义区别不大。但是给闭包变量赋值的函数体中含有参数列表,并且参数列表和真正的函数体之间使用关键字in来分割。 闭包可选变量的调用方式与普通函数没什么两样,唯一不同的是这个函数需要用!来强制打开才可以使用。赋值和调用方式如下。 3. 闭包回调的应用实例 暂且先称作闭包回调吧,其实就是Objc中的Block回调。在Swift中的闭包回调和Objc中的Block回调用法一致,下方将会通过一个实例来介绍一下闭包的应用之一。下方会创建两个视图控制器,我们暂且称为FirstViewController和SecondViewController。在FirstViewController上有一个Label和一个Button, 这个Button用来跳转到SecondViewController, 而这个Label用来显示从SecondViewController中回调过来的值。 而SecondViewController也有一个TextField和一个Button, 点击Button就会把输入框中的值通过闭包回调回传到FirstViewController然后在FirstViewController上的Label显示。 (1) 构建这个实例的第一步要做的就是使用Stroyboard把我们所需的控件布局好,并且管理相应的类。当然我们这个Demo的重点不在于如何去布局控件,如何去关联控件,以及如何去使用控件,所以上述的这些就不做赘述了。这个实例的重点在于如何使用Closure实现值的回调。下方是我们的控件布局和目录结构的截图,从Storyboard上的控件来看,功能也就一目了然了。点击“FirstViewController” 上的“Go SecondViewController”按钮,就会跳转到 “SecondViewController” 。 在SecondViewController视图上的输入框输入数值,点击Back按钮返回到FirstViewController, 同时把输入框中的文本通过闭包回调的形式回传过来在FristViewController的label上显示。大致就这个简单的功能。 (2)FirstViewController.swift中的内容 FirstViewController.swift中的内容比较简单,就关联一个Label控件和一个按钮点击的事件,点击按钮就会跳转到SecondViewController,具体代码如下,在此就不啰嗦了,请看代码中的注释。下方代码重要的一点是在跳转到SecondViewController时要实现其提供的闭包回调,以便接受回传过来的值。 1 // 2 // FirstViewController.swift 3 // SwiftDemo 4 // 5 // Created by Mr.LuDashi on 15/11/18. 6 // Copyright © 2015年 ZeluLi. All rights reserved. 7 // 8 9 import UIKit1011 class FirstViewController: UIViewController {1213 @IBOutlet var showTextLabel: UILabel! //展示回调过来的文字信息1415 override func viewDidLoad() {16 super.viewDidLoad()17 }1819 override func didReceiveMemoryWarning() {20 super.didReceiveMemoryWarning()21 }2223 //点击按钮跳转到SecondViewController24 @IBAction func tapGoSecondViewControllerButton(sender: UIButton) {25 //从Storyboard上加载SecondViewController26 let secondVC = UIStoryboard(name: “Main”, bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier(“SecondViewController”)as! SecondViewController2728 //实现回调,接收回调过来的值29 secondVC.setBackMyClosure { (inputText:String) -> Void in30 self.showTextLabel.text = inputText31 }3233 //push到SecondViewController34 self.navigationController?.pushViewController(secondVC, animated: true)35 }36 } (3) SecondViewController.swift中的内容 SecondViewController.swift中的内容也不麻烦,就是除了关联控件和事件外,还定义了一个闭包类型(函数类型),然后使用这个特定的函数类型声明了一个此函数类型对应的变量。我们可以通过这个变量来接受上个页面传过来的闭包体,从而把用户输入的值,通过这个闭包体回传到上个页面。具体代码实现如下: 1 // 2 // SecondViewController.swift 3 // SwiftDemo 4 // 5 // Created by Mr.LuDashi on 15/11/18. 6 // Copyright © 2015年 ZeluLi. All rights reserved. 7 // 8 9 import UIKit1011 typealias InputClosureType = (String) -> Void //定义闭包类型(特定的函数类型函数类型)1213 class SecondViewController: UIViewController {1415 @IBOutlet var inputTextField: UITextField! //输入框,让用户输入值,然后通过闭包回调到上一个页面1617 var backClosure:InputClosureType? //接收上个页面穿过来的闭包块1819 override func viewDidLoad() {20 super.viewDidLoad()21 }2223 override func didReceiveMemoryWarning() {24 super.didReceiveMemoryWarning()25 }2627 //闭包变量的Seter方法28 func setBackMyClosure(tempClosure:InputClosureType) {29 self.backClosure = tempClosure30 }3132 @IBAction func tapBackButton(sender: UIButton) {33 if self.backClosure != nil {34 let tempString:String? = self.inputTextField.text35 if tempString != nil {36 self.backClosure!(tempString!)37 }38 }39 self.navigationController!.popViewControllerAnimated(true)40 }41 } (4) 经过上面的步骤这个实例已经完成,接下来就是看一下运行效果的时间了。本来想做成Git动态图的,感觉实例功能简单,而且UI上也比较简单,就没做,还是看截图吧。运行效果的截图如下: 4.数组中常用的闭包函数 在Swift的数组中自带了一些比较好用的闭包函数,例如Map, Filter, Reduce。接下来就好好的看一下这些闭包,用起来还是比较爽的。 (1) Map(映射) 说到Map的用法和功能,不能不说的是如果你使用过ReactiveCocoa框架,那么对里边的Sequence中的Map的使用方式并不陌生。其实两者的使用方法和功能是极为相似的。如果你没使用过RAC中的Map,那也无关紧要,接下来我们先上段代码开看一下数组中的Map闭包函数。 通过上面的代码段以及运行结果,我们不难看出,map闭包函数的功能就是对数组中的每一项进行遍历,然后通过映射规则对数组中的每一项进行处理,最终的返回结果是处理后的数组(以一个新的数组形式出现)。当然,原来数组中的元素值是保持不变的,这就是map闭包函数的用法与功能。 (2) Filter (过滤器) Filter的用法还是比较好理解的,Filter就是一个漏勺,就是用来过滤符合条件的数据的。在ReactiveCocoa中的Sequence也是有Filter的,用法还是来过滤Sequence中的数据的。而在数组中的Filter用来过滤数组中的数据,并且返回新的数组,新的数组中存放的就是符合条件的数据。Filter的用法如下实例,下方的实例就是一个身高的过滤,过滤掉身高小于173的人,返回大于等于173的身高数据。 (3)Reduce 在ReactiveCocoa中也是有Reduce这个概念的,ReactiveCocoa中使用Reduce来合并消减信号量。在swift的数组中使用Reduce闭包函数来合并items, 并且合并后的Value。下方的实例是一个Salary的数组,其中存放的是每个月的薪水。我们要使用Reduce闭包函数来计算总的薪水。下方是DEMO的截图:]]></content>
</entry>
<entry>
<title><![CDATA[Swift字符串常用操作]]></title>
<url>%2Fposts%2F3254%2F</url>
<content type="text"><![CDATA[swift 中关于取子串有4 个方法 str.index(after: String.Index) str.index(before: String.Index) str.index(String.Index, offsetBy: String.IndexDistance) str.index(String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index) 分别是什么, 该如何使用?下面来看看本例中, 我们创建一个字符串"Hello, playground" , 如下 var str = "Hello, playground" 字符索引 startIndex 和 endIndex startIndex 是第一个字符的索引, 也就是 endIndex 是最后一个字符索引 +1 // character str[str.startIndex] // H str[str.endIndex] // error: after last character // range let range = str.startIndex ..< str.endIndex str[range] // "Hello, playground" after index(after: String.Index)after 指向给定索引后面的一个索引(类似与 + 1) // character let index = str.index(after: str.startIndex) str[index] // "e" // range let range = str.index(after: str.startIndex)..<str.endIndex str[range] // "ello, playground" before index(before: String.Index)before 指向给定索引之前的一个索引(类似与 - 1) // character let index = str.index(before: str.endIndex) str[index] // d // range let range = str.startIndex ..< str.index(before: str.endIndex) str[range] // Hello, playgroun offsetBy index(String.Index, offsetBy: String.IndexDistance)offsetBy 的值可以为正或是负, 正则表示向后, 负则相反.别被offsetBy 的 String.IndexDistance 类型吓到, 本身其实是一个 Int.(类似于+n 和 -n) // characterlet index = str.index(str.startIndex, offsetBy: 7)str[index] // p // rangelet start = str.index(str.startIndex, offsetBy: 7)let end = str.index(str.endIndex, offsetBy: -6)let range = start ..< endstr[range] // play limitedBy index(String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index) limitedBy 在 offset 过大导致溢出错误的时候很有用.这个返回值是一个可选值, 如果 offsetBy 超过了 limitBy, 则返回 nil. // characterif let index = str.index(str.startIndex, offsetBy: 7, limitedBy: str.endIndex) {str[index] // p} 上面这段代码中如果offset 设置为 77, 返回值就是 nil, if 中的代码就会跳过 所以如果你要取子串的正确姿势是什么? 取某个位置之后的所有字符str.substring(from: str.index(str.startIndex, offsetBy: 7)) // playground取倒数某个位置之后的所有字符str.substring(from: str.index(str.endIndex, offsetBy: -10)) //playground取某个位置之前的所有字符str.substring(to: str.index(str.startIndex, offsetBy: 5)) //Hello取倒数某个位置之前的所有字符str.substring(to: str.index(str.endIndex, offsetBy: -12)) //Hello取中间的某个字符串str.substring(with: str.index(str.startIndex, offsetBy: 7) ..< str.index(str.endIndex, offsetBy: -6)) // play]]></content>
</entry>
<entry>
<title><![CDATA[myResume]]></title>
<url>%2Fposts%2F10633%2F</url>
<content type="text"><![CDATA[简历预览王森男|未婚|1992年2月生| 户口:河南-南阳| 现居住于:北京-朝阳区2.5年工作经验 |团员Tel:15617877692 E-mail:[email protected]求职意向工作性质:全职期望职业:iOS开发工程师期望行业:互联网技术开发工作地区:北京期望月薪:面议目前状况:我目前在职,正考虑换个新环境(如有合适的工作机会,到岗时间2个星期左右)专业技能1.熟练掌握Object-C、Swift语言开发,熟悉C语言、html、css,以及JavaScprit的简单使用2.熟悉 runtime 以及 runloop3. 熟悉并掌握多线程技术,如 NSThread、GCD、NSOpeartion;4.熟悉MRC ARC 内存管理机制5.熟练使用Core Animation、UIView开发动画6.熟练使用WebStorm并掌握H5与iOS交互7.熟练掌握百度地图、高德地图的定位、导航、自定义标注、地理编码等功能。 8.熟练使用支付宝支付、微信支付、银联支付等多种支付方式。9.熟练使用推送通知,自定义消息的处理。10.掌握Instruments leaks内存检测以及单元测试提升程序的稳定性。11.熟练使用各种设计模式,如MVC 模式(MVVM模式)、代理模式、单例模式、观察者模式等设计模式 12.熟练使用AFNetworking、SDWebImage,Masonry第三方框架13. 熟悉 SourceTree、Github 代码管理工具,jenkins可持续开发自我评价对于iOS的技术水平可以单独做完整个项目,负责各个模块的开发调试。有较强的学习能力和探索精神。工作经历2016/06 – 至今 北京易聚创意科技有限公司|iOS开发工程师 行业类别: 互联网/电子商务|规模:100-499人|职位月薪:15000-25000元/月工作描述:带领团队共同开发‘全聚星’直播App,搭建gitlab代码管理工具配置环境,搭建禅道项目管理Bug反馈处理工具,搭建Jenkins持续集成配置环境,设计项目框架;实现直播模块,商城模块,私信聊天;辅助其他开发成员解决疑难问题,修复Bug,根据开发日期分配任务2015/09 – 2016/05北京青籁科技有限公司|iOS工程师 行业类别: 互联网/电子商务|规模:100-499人|职位月薪:10001-15000元/月工作描述: 独立开发“理大师”app的用户端,负责整个项目的框架、逻辑处理以及各个模块的开发,地图定位 poi 搜索 导航,微信支付和支付宝支付,推送通知,配合后台和web前端交互处理h5界面,维护升级。 实现一个可以在线预约下单上门推拿的健康医疗服务。(包含用户端与医生端)2014/10 – 2015/09北京溥联信息科技有限公司|iOS开发实习生 行业类别: 互联网/电子商务|企业性质:事业单位|规模:20-99人|职位月薪:6001-8000元/月工作描述:快停App手机端的开发,根据需求进行iOS平台客户端软件的开发和维护,设计项目框架,实现界面展示与后台调试api完善项目功能,维护升级。 实现了一个可以快速停车为车主提供便捷精准可靠的停车服务(精准定位,预约停车,停车导航,智能计时,在线支付) 业绩描述:负责项目所有板块的开发, 独自解决快停App首页加载地图自定义上千个标注点的卡顿现象,实现区域标注点与全局标注点的切换,提高程序的流畅度,提升用户体验。离职原因:项目经验2016/06 – 至今 全聚星直播责任描述:带领iOS开发组共同开发‘全聚星’直播App,搭建gitlab代码管理工具配置环境,搭建禅道项目管理Bug反馈处理工具,搭建Jenkins持续集成配置环境,设计项目框架;实现直播模块,商城模块,私信聊天;辅助其他开发成员解决疑难问题,修复Bug,根据开发日期分配任务。项目简介:全聚星是由香港上市企业-中国创意集团斥巨资打造的国内首家艺人养成互动平台。我们以赵薇、佟大为等明星股东为支点、专业化的艺人培养机制为保障, 高品质的互动视频内容为跳板、为才艺新人提供全方位的培养、包装体系。全聚星积极探索符合国内业态发展需要的新型娱乐经济产业模式,汇聚互动综艺直播、明星直播、才艺直播等高品质互动视频内容,率先开启全民互动娱乐2.0浪潮。2015/09 – 2016/05理大师用户端和管理端责任描述:独立开发“理大师”app的用户端,负责整个项目的框架、逻辑处理以及各个模块的开发,地图定位 poi 搜索 导航,微信支付和支付宝支付,推送通知,配合后台和web前端交互处理h5界面,维护升级。项目简介:理大师以中医理疗推拿为核心的O2O健康理疗上门服务的平台。专注健康,贴心服务,以百年传承的中医养生医疗体系为核心,融合西医及美式整脊疗法先进的检测与健康管理方法,为用户提供定制化的名医大师健康理疗服务。2014/10 – 2015/09快停责任描述:快停App手机端的开发,根据需求进行iOS平台客户端软件的开发和维护,设计项目框架,实现界面展示与后台调试api完善项目功能,维护升级。项目简介: 快停司机端:为车主提供便捷精准可靠的停车服务(精准定位,停车导航,智能计时,在线支付),为车主彻底解决城市出行中停车难的问题,同时为停车场提供智能引导,平台支付,高效管理,实现停车场效率与收益最优化. 教育经历2016/07 – 至今 郑州大学远程教育|计算机科学与技术|本科|非统招2013/09 – 2015/06河南广播电视大学|机械设计制造及其自动化|大专|统招语言能力英语 :读写能力熟练| 听说能力良好]]></content>
</entry>
<entry>
<title><![CDATA[Hexo配合github搭建免费博客,绑定域名]]></title>
<url>%2Fposts%2F10450%2F</url>
<content type="text"><![CDATA[出发点:有那么大的博客平台不用为什么自己搭建博客,说白了就是玩玩坚持写几篇博客然后就是装逼了呗。本篇文章在我的简书地址今天要讲的搭建博客教程很简单不需要什么钱当然有钱可以买虚拟机配个数据库可以玩wordpress搭建博客更简单,记得万网有活动几块钱就可以买个一年的虚拟机活动长期有可以看看,这里就不详细介绍了。言归正传,本篇博客说的是用github和 Hexo搭建一个免费的博客网站,搭建完看起来像这样 http://ws1227.github.io 如果有个人域名就可以类似这样http://wangsen.website/ 后边再介绍。 先说下需要的基本条件:安装Node(必须) 作用:用来生成静态页面的 到Node.js官网下载相应平台的最新版本,一路安装即可。提供一个mac版本的https://pan.baidu.com/s/1gf5NSyf 不用翻墙下载了安装Git(必须)作用:把本地的hexo内容提交到github上去. 安装Xcode就自带有Git,我就不多说了。没有的话可以去https://git-scm.com/downloads 下载申请GitHub(必须)作用:是用来做博客的远程创库、域名、服务器之类的,怎么与本地hexo建立连接等下讲。 github账号我也不再啰嗦了,没有的话直接申请就行了,跟一般的注册账号差不多,SSH Keys,看你自己了,可以不配制,不配置的话以后每次对自己的博客有改动提交的时候就要手动输入账号密码,配置了就不需要了,怎么配置我就不多说了,网上有很多教程。安装HexoNode和Git都安装好后,可执行如下命令安装hexo:1$ sudo npm install -g hexo创建一个文件夹,如:Blog,cd到Blog里执行hexo init的。命令:1hexo init好啦,至此,全部安装工作已经完成!因为Hexo编写的文章都是用 markdown语法写的 所以需要生成静态界面也就是Html网页。 生成静态页面继续在刚才的Blog目录下执行如下命令,生成静态页面 1hexo generate (hexo g 缩写也可以) 本地启动启动本地服务,进行文章预览调试,命令: 1hexo server (hexo s 缩写也可以) 开启本地预览服务,打开浏览器访问 http://localhost:4000 即可看到内容,很多人会碰到浏览器一直在转圈但是就是加载不出来的问题,一般情况下是因为端口占用的缘故,因为4000这个端口太常见了 ,可以更换个端口号 然后再执行 hexo s12hexo server -p 5000 #更改端口随意写hexo server -i 192.168.1.1 #自定义 IP运行成功后按Control+L可以停止 如果直接用Hexo s启动会提示错误 就是已经被占用了 所以正确的命令应该是12hexo clean #清除缓存 网页正常情况下可以忽略此条命令hexo g #生成静态网页上边算是搭建了一个本地的博客系统了 然后就是配置Git了用git就可以把博客移动到远程服务器访问类似我的博客地址http://ws1227.github.io配置Github建立Repository建立与你用户名对应的仓库,仓库名必须为【useame.github.io】如下图我的,固定写法 建立关联 打开Blog在本地/Users/wangsen/Blog,Blog是之前建Hexo的文件,有:12345678910Blog | |-- _config.yml |-- node_modules |-- public |-- source |-- db.json |-- package.json |-- scaffolds |-- themes 现在我们需要打开_config.yml文件,来建立关联,命令: 如果终端不是在Blog目录就cd到该目录执行命令 1vim _config.yml 我建议用sublime翻到最下面,改成我这样子的,注意 : 后面要有个空格repository地址换成你自己的git地址1234deploy: type: git repository: https://github.com/ws1227/ws1227.github.io.git branch: master执行如下命令才能使用git部署1npm install hexo-deployer-git –save配置命令:作用就是把blog目录下的Public目录下的文件上传至git仓库1hexo deploy 然后再浏览器中输入就行了http://ws1227.github.io/,我的 github 的账户叫 ws1227 ,把这个改成你 github 的账户名就行了每次修改博客文件后都需要从新部署123hexo cleanhexo generatehexo deploy一些常用命令:http://www.jianshu.com/p/eb02029f7a81HEXO主题 如果你到了这里没有任何问题,那么恭喜你已经成功了,不过这才刚刚开始。当你成功的看到自己博客搭建好的那一刻又是激动又是失望,激动的是博客总算折腾出来了,失望的是,为何如此的丑。。。说实话Hexo默认的主题我不喜欢,如果你想换风格,Hexo的主题网上随便一搜也有很多。在此笔者使用的博客主题是Next.Pisces(国人写的)。https://github.com/iissnan/hexo-theme-next 作者很详细的使用文档域名绑定(第三步,可选)笔者是在万网买的域名(http://wangsen.website/)。 域名买好之后提交实名认证等,这些操作就不在赘述。域名购买地址。价格表:https://wanwang.aliyun.com/help/price.html?spm=5176.8076989.339865.8.yRl9gU选择自己喜欢又能接受价格的域名吧 域名是需要每年都付钱的,看清楚续费价格再下手。买完域名后进入域名管理后台添加解析,下图我添加解析后的。说下这个记录值就是ip地址得获取方式,打开终端ping下自己的git仓库 命令如下格式1ping ws1227.github.io看到如下数据,就看到这个ip地址了123456PING github.map.fastly.net (151.101.100.133): 56 data bytes64 bytes from 151.101.100.133: icmp_seq=0 ttl=50 time=105.119 msRequest timeout for icmp_seq 164 bytes from 151.101.100.133: icmp_seq=2 ttl=50 time=105.448 ms64 bytes from 151.101.100.133: icmp_seq=3 ttl=50 time=104.788 ms64 bytes from 151.101.100.133: icmp_seq=4 ttl=50 time=103.319 ms然后在你的本地站点目录里的source目录下添加一个CNAME文件,不带后缀,可以用终端切换到source目录下,执行命令如下1vim CNAME内容是域名地址不带www和http 就类似我的是 wangsen.website填写完了之后再重新部署到github pages上(部署简写命令hexo d -g)打开自己的博客尽情打开各种蹂躏吧]]></content>
</entry>
<entry>
<title><![CDATA[Hello World]]></title>
<url>%2Fposts%2F16107%2F</url>
<content type="text"><![CDATA[新的一片天]]></content>
</entry>
</search>