budding项目小结

项目描述

目的

目前档口市场处于传统的做单形式,手写单子、手动记账,该软件有助于档口市场的高效开单以及对账,用软件代替手工处理单据,并事实给出报表数据。

功能

分为以下几个领域:

  1. 创建商家入驻,或者店员身份申请审核;
  2. 商品列表以及单据信息;
  3. 客户列表以及单据信息;
  4. 销售单的数据信息;
  5. 管理员身份,具有店铺采购的功能,也可以在云市场发起采购;
  6. 可以发展线下用户,会按照发展下线的方式,给出一定比的红包;
  7. 商家的老板可以购买月套餐以及年套餐,支持多用户级;
  8. 线上的账户可以对账户余额进行提现;
  9. 报表数据查看;

iOS 工程中的逆向传值的方法

在项目内,我们通常需要处理界面之间的数据传递,主要的处理方式有一下几种:

1. 属性传值;
2. 方法参数传值;
3. delegate传值;
4. block回调方法传值;
5. notification,发送通知传递数据信息;
6. 单例修改数据传值;
7. 全局属性传值;
8. NSUserDeaults 传值,属于单例传值的一种;

以上几种方法处理逆向传值,都是OK的。

在修改一个项目中,H领导说想要实现不确定的方法来传值,而不是像代理和block那样,必须实现既定的方法。他写了一套方法,给后面的VC添加target和selector,后面的方法去按照给到的selector去响应。正好,API内包括一个NSInvocation,可以设置target,selector,argument,我们可以按照这个来拿。

在响应的事件后,可以获取NSInvocation的参数,利用参数,然后响应消息。

处理方法有一下几种:

  1. performSelector,注意此方法最多可以传递两个参数;
  2. 可以objc运行时的方法objc_msgSend来转发消息;
  3. 利用NSInvocation的信息去invoke这个方法。

第一种方法,在主线程运行,只能传递一个参数;
第二种方法,在运行时会提示,Too many arguments to function call,expected 0,have 3,需做修改:

Build Setting–> Apple LLVM 7.0 - Preprocessing–> Enable Strict Checking of objc_msgSend Calls 改为 NO.

但是不晓得为啥,我用了第二种第三种方法传递带了两个参数的selector,没执行好。

iOS开发之CocoaPods的使用说明

前言

在开发的过程中,我们经常使用到第三方的库,很多人会采用直接拖入代码的形式,但是这种处理方法不够便捷,伟大的牛牛们做出来可以用来方便的统一管理这些第三方库。

cocoapods: https://cocoapods.org/

github地址:https://github.com/CocoaPods/CocoaPods

安装

1.获取镜像

终端输入如下命令(把Ruby镜像指向taobao,避免被墙,你懂得)

1
2
3
gem sources --remove https://rubygems.org/ 
gem sources -a https://ruby.taobao.org/
gem sources -l (用来检查使用替换镜像位置成功)

2.下载安装CocoaPods

终端输入:

sudo gem install cocoapods

3.使用CocoaPods

新建一个项目,终端切入到项目的根目录(cd 目录);

建立Podfile文件,创建配置文件,终端输入

touch Podfile

编辑Podfile文件,终端输入

vim Podfile

键盘输入 i,进入编辑模式,输入

1
2
3

platform :ios,
pod 'MBProgressHUD', '~> 0.9.1'

文件内,说明了使用的平台、pod的对象以及pod对象的版本号,可以同时引入多个三方库,新起一行pod即可。

具体输入的版本号是多少,可以采用如下命令查看:

pod search MBProgressHUD

比如我现在查看一下我自己的,结果如下:

MisheraldeMacBook-Pro:~ Misheral$ pod search MBProgressHUD


-> MBProgressHUD (0.9.1)
   An iOS activity indicator view.
   pod 'MBProgressHUD', '~> 0.9.1'
   - Homepage: http://www.bukovinski.com
   - Source:   https://github.com/matej/MBProgressHUD.git
   - Versions: 0.9.1, 0.9, 0.8, 0.7, 0.6, 0.5 [master repo]


-> MBProgressHUD+BWMExtension (1.0.0)
   Nihility-Ming to MBProgressHUD extension, easy to use.
   pod 'MBProgressHUD+BWMExtension', '~> 1.0.0'
   - Homepage: https://github.com/Nihility-Ming/MBProgressHUD-BWMEXtension
   - Source:   https://github.com/Nihility-Ming/MBProgressHUD-BWMEXtension.git
   - Versions: 1.0.0 [master repo]


-> MBProgressHUDExtensions (0.0.1)
   UIViewController extensions for displaying an MBProgressHUD
   pod 'MBProgressHUDExtensions', '~> 0.0.1'
   - Homepage: https://github.com/SymmetricInfinity/MBProgressHUDExtensions
   - Source:   https://github.com/SymmetricInfinity/MBProgressHUDExtensions.git
   - Versions: 0.0.1 [master repo]


-> MBProgressHUDExtensions@donly (0.3)
   UIViewController extensions for displaying an MBProgressHUD
   pod 'MBProgressHUDExtensions@donly', '~> 0.3'
   - Homepage: https://github.com/donly/MBProgressHUDExtensions
   - Source:   https://github.com/donly/MBProgressHUDExtensions.git
   - Versions: 0.3, 0.2, 0.1 [master repo]

项目安装使用

pod install

安装成功,你会在根目录看到很多文件,并且看到一个后缀为xcworkspace的文件。

那么,请注意,以后打开项目,不在是prj文件,而是点击xcworkspace文件了。(这个错误我是经常犯的)

总结

过程还是蛮简单的,只不过我貌似不常用,大部分的三方库在现在的项目中都已经封装好了,直接使用。

想要查询可以使用哪些第三方的库,可以在CocoaPods的官网,search一下。

注意,我使用的是chrome,有时查看不到pod语句,可以用safari,鼠标移动到小图标上面,实时显示pod语句。

可能在使用的过程中有时候会比较慢,耐心的等待一下,就OK,像我昨天,在公司弄的时候,网络不好,我就趁着安装的过程,看了微博,微信空间啥的,合理利用碎片时间,奥,影响貌似不大好。

VVDocumenter-Xcode 的使用

这是什么呢?

通常我们在写注释的时候,要打很多烦躁的代码,这个工具便于我们快速的在一个类或者方法前面生成一个注释代码。

如何使用和安装

github地址如下:

https://github.com/onevcat/VVDocumenter-Xcode

  • 下载应用,在mac上运行;

  • 本机进入/Users/Name/Library/Application\ Support/Developer/Shared/Xcode/Plug-ins/,能看到多出一个插件VVDocumenter-Xcode.xcplugin;

  • 重启Xcode;

在类或者方法的前面,打 ///即可快速添加注释。

若想修改快捷方式,可在Xcode-Window下点击VVDocumenter,弹出的面板上可以选择性设置。

好吧,这就是它伟大的配置方法,该插件出自我们伟大的喵神。

然而,它也有不尽人意之处,每次升级Xcode以后,都不能再使用该快捷键,需重新再运行一次。

喵神的原创

有好多很厉害的开发呢,没事儿的时候可以多和高手学习一下。

onevcat blog address : http://onev.cat

基于Xcode 7.1的UI Testing初探

写在前面

iOS 里面大致有两种测试的方法,系统的UNIT测试,以及UIAutomation插件的方法。以前只是有听说,笔者并未过多关注这些方面的东西,13年开始接触这个行业的时候就有看到单元测试,只是不了解,主要在学习基础部分。上班两年多了,随着项目的日积月累,测试已经是一件必须提上日程的事情。但是介于不动脑子以及项目的紧迫性,大部分功能都是实现即可,不考虑性能问题,以至于现在想要做测试也是分外的困难。

目前看到如下几个问题:

  • 项目负载过大;
  • 代码的冗余度过高;
  • 代码的耦合度过高;

经分析:

  • 项目负载大,就意味着出现错误的可能性更多,也就意味着Assert的代码更多,我面对这个项目的时候,不知道该从何着手。

  • 冗余度高,前期在做这个项目的时候,只想着实现功能,别让领导以为自己真的不行,就忽略了已有框架里面封装的方法,直接采用网上的方法或者处理手段,比如,获取view的图片的方法,自己费了好大的劲儿写完之后才发现,H领导已经封装了imageForView的方法,尴尬😅。

  • 代码耦合度高,一个类里面定义公开的property,在其他方法里面并未直接以参数的形式传入,而是直接调用,造成测试时无法mock假的数据,类与类之间过渡依赖。

所以,晓得吗?我感觉自己啥都不会了,不晓得怎么去做这个测试,根本没有入手点,H领导们和别的同事一起封闭开发去了,剩下我独自啃着啃不动的干粮,哭毙了。。。

UI Testing 是Xcode 7开始,Apple 对外公布的用于界面测试的方法,貌似这个一直都存在的,之前一直在内部使用,现在公开出来。该技术主要根据通过界面元素,审查app的运行是否正确。

UI Testing的结构

  • XCUIApplication, 有点儿类似于Obecjtive 里面的UIApplication,继承于XCUIElement;
  • XCUIElementQuery,查询试图之间的关联;
  • XCUIElement,界面的可视化的元素,可以通过设置AccesibilityIdentifier访问,若是没有设置,可通过设置的值label,value,title唯一标示访问;

    界面部分,可以通过某个元素是否存在来检测,利用XCUIElement的exists属性。

期望判断

XCUITests提供了用于判断是否达到预期效果的方法;

1
2
3
4
5
#if XCT_NULLABLE_AVAILABLE
- (XCTestExpectation *)expectationForNotification:(NSString *)notificationName object:(nullable id)objectToObserve handler:(nullable XCNotificationExpectationHandler)handler;
#else
- (XCTestExpectation *)expectationForNotification:(NSString *)notificationName object:(id)objectToObserve handler:(XCNotificationExpectationHandler)handler;
#endif

可以设计预订的时间等待某个通知是否能接受,接受到notify,则继续执行,等待不到,则直接崩溃,抛出异常信息。

比如,我在2秒钟以后发送通知,主线程直接进入等待状态:

1
2
3
4
5
6
7
8
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter]postNotificationName:stateName object:nil];
};

do {
[self expectationForNotification:stateName object:nil handler:nil];
[self waitForExpectationsWithTimeout:second handler:nil];
} while (0);

以上这段代码,会在2秒钟以后正常执行,若是碰不到本地的通知,则底下超出second时间,将会崩溃。

  • 这里有一个细节的东西要特别注意,waitForExpectationsWithTimeout:这个方法必须是在测试的主线程执行的,若是在其他线程,则程序会崩溃,提示这个方法必须在主线程执行,这句代码以后的代码,若等待到notify,则会在sencond以后正常执行。

话外之语

以前都不知道为啥H领导要这样那样的分成零散的方法去处理一段逻辑,那时的我看代码都是不停的跳转,觉得这样单独的功能分裂多块去处理,代码不严谨,但是现在我觉得,H领导很有前瞻性,这种构架对于后期开发一场有效。

其他相关文章如下:

其他测试

其他测试方案如下:

JSValue计算数学公式

由JSValue 计算引起的bug

公司在做订货方面的东西,在下单界面需要根据公式计算数据结果。我们公司H老大做了一套处理方法,利用API种JSContext自带的evaluateScript方法。之前没多少项目有小数的处理,但是最近有一家客户,单款商品单价包含两位小数,出现如下情况:

  • 加法计算: (76.32) + (2.11) = 78.429999999

  • 乘法计算: (76.82) * 10 = 768.200000001

后面的小数位数 可能有一定的出入,但是大致可以看出波动是在0.00000001,或者-0.00000001的出入。

方法使用错误?或者是精度缺失?

我查看了代码,发现我们返回数据是用的方法竟然是JSValue的toString方法,这个方法会把真正的数据类型再转为String,强转数据有可能会造成数据丢失。

  • 我看API有toObject方法,就使用这个方法,但是返回的类型不是NSString了,我想按照类型再一个一个转,H领导说,为啥要那样做,不是有description吗?突然脑子反应过来,是呀,怎么把这个方法给遗忘了。

最后使用如下代码:

1
2
3
JSContext *context = [[JSContext alloc] init];
JSValue *value = [context evaluateScript:script];
return [[value toObject] description];

但是依然有一部分数据计算结果是错误的。

👶很不开森,很郁闷,这是为什么呢?

我朋友问了他们的技术总监,对方说,有可能在计算的过程中精确缺失掉了。

  • 突然想起,这个运算的处理是将字符串转为了数字,然后再计算的结果返回的,这么说倒是对的,然后就去查找JavaScript的计算处理,google js的精度丢失,发现还真有这么一个bug。
世界瞬间美好的不行不行滴,然后我就开始出各种馊主意

两种方法处理:

  • 固定精度法;
    计算数据的时候,直接调用toFixed(2),这样的形式保留两位小数。

  • 乘除位数法;
    计算数据的时候,直接把数据转为整数,再除以整数倍数,如(数据 * 100)/100。

结果呢?!!

😒心情好的不行不行的。然而下午的时候java方面同事说不行。

  • 第一种,固定精度,会造成精度后面的数据直接被丢弃,不进行四舍五入😓。

  • 第二种,乘除位数吧,加法的时候 还是有多位,他说搞不定搞不定,不行呀不行呀,怎么办呢?

那怎么办,凉拌!!

四舍五入呀,我就去摸索四舍五入了。

断点调试,在lldb时输入

1
po [[[JSContext alloc] init] evaluateScript:@"Math.round(76.8399999*100)/100"]

Bing🐶,出现了我想要的结果,然后就让他们修改传入公式,然后就在演示前十三分钟搞定了。

总结

精度这种东西还是要注意的,尤其是在强制转换数据的时候,有时候不小心,或者稍微大意就会造成这种错误,虽然swift里对于强转对象会有warning显示,但是还是自己严谨处理代码好些,避免类似的bug重现。

附上JAvaScriptCore.h 的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef JavaScriptCore_h
#define JavaScriptCore_h

#include <JavaScriptCore/JavaScript.h>
#include <JavaScriptCore/JSStringRefCF.h>

#if defined(__OBJC__) && JSC_OBJC_API_ENABLED

#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"

#endif

#endif

匹配电话号码

最近做一个店务的东西,在提交订单时客户要求输入用户的手机号码,又摸回去找以前的东西,看了一个简单的,贴出来共享一下。

以下思路:

  • 创建匹配语句;
  • 利用iOS内置的NSPredicate创建规则表达式;
  • 匹配目标字符串。

简单的代码如下:

1
2
3
4
5
6
7
+ (BOOL)validatePhoneNumber:(NSString *)number{
BOOL success = NO;
NSString *phoneRegex = @"1[3|5|7|8|][0-9]{9}";
NSPredicate *phoneTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", phoneRegex];
success = [phoneTest evaluateWithObject:number];
return success;
}

但是仔细说起来的话,这样的写法还是不正确的,精确的写法如下:(来自网络)

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
//正则判断手机号码格式
+ (BOOL)validatePhone:(NSString *)phone
{
/**
* 手机号码
* 移动:134[0-8],135,136,137,138,139,150,151,157,158,159,182,187,188
* 联通:130,131,132,152,155,156,185,186
* 电信:133,1349,153,180,189
*/

NSString * MOBILE = @"^1(3[0-9]|5[0-35-9]|8[025-9])\\d{8}$";
/**
10 * 中国移动:China Mobile
11 * 134[0-8],135,136,137,138,139,150,151,157,158,159,182,187,188
12 */

NSString * CM = @"^1(34[0-8]|(3[5-9]|5[017-9]|8[278])\\d)\\d{7}$";
/**
15 * 中国联通:China Unicom
16 * 130,131,132,152,155,156,185,186
17 */

NSString * CU = @"^1(3[0-2]|5[256]|8[56])\\d{8}$";
/**
20 * 中国电信:China Telecom
21 * 133,1349,153,180,189
22 */

NSString * CT = @"^1((33|53|8[09])[0-9]|349)\\d{7}$";
/**
25 * 大陆地区固话及小灵通
26 * 区号:010,020,021,022,023,024,025,027,028,029
27 * 号码:七位或八位
28 */

// NSString * PHS = @"^0(10|2[0-5789]|\\d{3})\\d{7,8}$";

NSPredicate *regextestmobile = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", MOBILE];
NSPredicate *regextestcm = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CM];
NSPredicate *regextestcu = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CU];
NSPredicate *regextestct = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CT];

if (([regextestmobile evaluateWithObject:phone] == YES)
|| ([regextestcm evaluateWithObject:phone] == YES)
|| ([regextestct evaluateWithObject:phone] == YES)
|| ([regextestcu evaluateWithObject:phone] == YES))
{
if([regextestcm evaluateWithObject:phone] == YES) {
NSLog(@"China Mobile");
} else if([regextestct evaluateWithObject:phone] == YES) {
NSLog(@"China Telecom");
} else if ([regextestcu evaluateWithObject:phone] == YES) {
NSLog(@"China Unicom");
} else {
NSLog(@"Unknow");
}

return YES;
}
else
{
return NO;
}
}

当然还会遇到匹配身份证号码,车牌号,区号这样的东西,后续会继续补充出来。

其实,总的来说。只要会写表达式,都能做出来的,只是写出来的容易理解与否不大确定而已。

UIAlertController中block使用出现的问题

自iOS 8.0 以及以后的版本,API推荐使用UIAlertCtroller.

一段代码

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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
id sendAlert(id object){
NSString *text=[NSString stringWithFormat:@"%@",object];
NSString *button=[NSBundle localizedString:@"ZHIDAOLE"];
if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")){
UIAlertDialog *alert=[[UIAlertDialog alloc] init];
alert.title=text;
alert.message=nil;
[alert addButton:button withBlock:nil];
[alert show];
return alert;
}else{
UIAlertController *alert=[UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleAlert];
alert.title=text;
alert.message=nil;
UIAlertAction *action = [UIAlertAction actionWithTitle:button style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[alert dismissViewControllerAnimated:YES completion:nil];
}];
[alert addAction:action];
UIViewController *root = [UIApplication sharedApplication]. keyWindow.rootViewController;
[root presentViewController:alert animated:YES completion:^{}];
return alert;
}
return nil;
}
id sendAlertBlock(id object,dispatch_block_t block){
NSString *text=[NSString stringWithFormat:@"%@",object];
NSString *button=[NSBundle localizedString:@"ZHIDAOLE"];
if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")){
UIAlertDialog *alert=[[UIAlertDialog alloc] init];
alert.title=text;
alert.message=nil;
[alert addButton:button withBlock:block];
[alert show];
return alert;
}else{
UIAlertController *alert=[UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleAlert];
alert.title=text;
alert.message=nil;

UIAlertAction *action=[UIAlertAction actionWithTitle:button style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[alert dismissViewControllerAnimated:YES completion:^{}];
if (block) {
block();
}
}];
[alert addAction:action];
UIViewController *root=[UIApplication sharedApplication]. keyWindow.rootViewController;
[root presentViewController:alert animated:YES completion:^{}];
return alert;
}
return nil;
}

id sendToast(id object){
return sendToastWithTimeout(object, 2);
}
id sendToastWithTimeout(id object,NSTimeInterval timeout){
NSString *text=[NSString stringWithFormat:@"%@",object];
if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")){
UIAlertDialog *alert=[[UIAlertDialog alloc] init];
alert.title=text;
alert.message=nil;
[alert show];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[alert dismissWithClickedButtonIndex:-1 animated:YES];
});
return alert;
}else{
UIAlertController *alert=[UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleAlert];
alert.title=text;
alert.message=nil;
UIViewController *root=[UIApplication sharedApplication]. keyWindow.rootViewController;
[root presentViewController:alert animated:YES completion:^{}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[alert dismissViewControllerAnimated:YES completion:^{}];
});
return alert;
}
return nil;
}

id sendConfirm(id object,dispatch_block_t block){
dispatch_block_t non=^{};
return sendConfirm2(object, block, non);
}
id sendConfirm2(id object,dispatch_block_t yesblock,dispatch_block_t noblock){
NSString *text=[NSString stringWithFormat:@"%@",object];
NSString *confirm=[NSBundle localizedString:@"QUEDING"];
NSString *cancel=[NSBundle localizedString:@"QUXIAO"];
if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")){
UIAlertDialog *alert=[[UIAlertDialog alloc] init];
alert.title=text;
alert.message=nil;
[alert addButton:confirm withBlock:yesblock];
[alert addButton:cancel withBlock:noblock];
[alert show];
return alert;
}else{
UIAlertController *alert=[UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleAlert];
alert.title=text;
alert.message=nil;

UIAlertAction *actionConfirm=[UIAlertAction actionWithTitle:confirm style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
if (yesblock) {
yesblock();
}
dispatch_async(dispatch_get_main_queue(), ^{
[alert dismissViewControllerAnimated:YES completion:^{}];
});
}];
[alert addAction:actionConfirm];

UIAlertAction *actionConcel=[UIAlertAction actionWithTitle:cancel style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
if (noblock) {
noblock();
}
dispatch_async(dispatch_get_main_queue(), ^{
[alert dismissViewControllerAnimated:YES completion:^{}];
});
}];
[alert addAction:actionConcel];
UIViewController *root=[UIApplication sharedApplication]. keyWindow.rootViewController;
[root presentViewController:alert animated:YES completion:^{}];
return alert;
}
return nil;
}

id sendTask(id object,dispatch_block_t block){
NSString *text=[NSBundle localizedString:@"QINGSHAOHOU"];
if (object) {
text=[NSString stringWithFormat:@"%@",object];
}
if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")){
UIAlertDialog *alert=[[UIAlertDialog alloc] init];
alert.title=text;
alert.message=nil;
[alert show];
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue=dispatch_queue_pool("serial.alert", NULL);
});
[[UIApplication sharedApplication] setNetworkActivity:YES];
dispatch_async(queue, ^{
if (block) {
block();
}
dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] setNetworkActivity:NO];
[alert dismissWithClickedButtonIndex:-1 animated:YES];
});
});
return alert;
}else{
UIAlertController *alert=[UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleAlert];
alert.title=text;
alert.message=nil;
UIViewController *root=[UIApplication sharedApplication]. keyWindow.rootViewController;
[root presentViewController:alert animated:YES completion:^{}];
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue=dispatch_queue_pool("serial.alert", NULL);
});
[[UIApplication sharedApplication] setNetworkActivity:YES];
dispatch_async(queue, ^{
if (block) {
block();
}
dispatch_async(dispatch_get_main_queue(), ^{
[alert dismissViewControllerAnimated:YES completion:^{}];
[[UIApplication sharedApplication] setNetworkActivity:NO];
});
});
return alert;
}
return nil;
}
id sendTaskWithCallback(id object,UIAlertViewBlock block){
NSString *text=[NSBundle localizedString:@"QINGSHAOHOU"];
if (object) {
text=[NSString stringWithFormat:@"%@",object];
}
if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")){
if (!block) {
block=^(UIAlertView *alertView){};
}

UIAlertDialog *alert=[[UIAlertDialog alloc] init];
alert.title=text;
alert.message=@"-";
[alert show];
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue=dispatch_queue_pool("serial.alert", NULL);
});
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] setNetworkActivity:YES];
});
block(alert);
dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] setNetworkActivity:NO];
[alert dismissWithClickedButtonIndex:-1 animated:YES];
});
});
return alert;
}else{
if (!block) {
block=^(UIAlertController *alertView){};
}
UIAlertController *alert=[UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleAlert];
alert.title=text;
alert.message=@"-";
UIViewController *root=[UIApplication sharedApplication]. keyWindow.rootViewController;
[root presentViewController:alert animated:YES completion:^{}];
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue=dispatch_queue_pool("serial.alert", NULL);
});
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] setNetworkActivity:YES];
});
block(alert);
dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] setNetworkActivity:NO];
[alert dismissViewControllerAnimated:YES completion:nil];
});
});
return alert;
}
return nil;
}
id sendLongTaskWithCallback(id object,UIAlertViewBlock block){
if (!block) {
if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")){
block=^(UIAlertView *alertView){};
}else{
block=^(UIAlertController *alertView){};
}
}
UIAlertViewBlock newblock=^(id alertView) {
dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] setIdleTimerDisabled:YES];
});
block(alertView);
dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] setIdleTimerDisabled:NO];
});
};
return sendTaskWithCallback(object, newblock);
}

id sendInputAlert(id object,UIAlertViewStyle style,UIAlertViewBlock block){
NSString *text=[NSString stringWithFormat:@"%@",object];
NSString *confirm=[NSBundle localizedString:@"QUEDING"];
NSString *cancel=[NSBundle localizedString:@"QUXIAO"];
if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")){
UIAlertDialog *alert=[[UIAlertDialog alloc] init];
alert.title=text;
alert.alertViewStyle=style;
[alert addButton:confirm withAlertBlock:block];
[alert addButton:cancel withBlock:nil];
[alert show];
return alert;
}else{
UIAlertController *alert=[UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleAlert];
alert.title=text;
alert.message=nil;

switch (style) {
case UIAlertViewStyleSecureTextInput:
{
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.secureTextEntry = YES;
textField.borderStyle = UITextBorderStyleLine;
}];
}
break;

case UIAlertViewStylePlainTextInput:
{
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.borderStyle = UITextBorderStyleLine;
}];
}
break;

case UIAlertViewStyleLoginAndPasswordInput:
{
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.secureTextEntry = YES;
textField.borderStyle = UITextBorderStyleLine;
}];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.borderStyle = UITextBorderStyleLine;
}];
}
break;

default:
break;
}

UIAlertAction *actionConfirm=[UIAlertAction actionWithTitle:confirm style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
if (block) {
block(alert);
}
dispatch_async(dispatch_get_main_queue(), ^{
[alert dismissViewControllerAnimated:YES completion:^{}];
});
}];
[alert addAction:actionConfirm];

UIAlertAction *actionConcel=[UIAlertAction actionWithTitle:cancel style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[alert dismissViewControllerAnimated:YES completion:^{}];

}];
[alert addAction:actionConcel];
UIViewController *root=[UIApplication sharedApplication]. keyWindow.rootViewController;
[root presentViewController:alert animated:YES completion:^{}];
return alert;
}
return nil;
}

Analysis

分析如下:

  1. iOS 8.0以及以后的版本,API内推荐使用UIalertController;
  2. 但是8.0以前的版本,会经常出现键盘跳来跳去的问题;
  3. 为了兼容两个版本,写出来上述代码。

但是:

1.设置弹出消息时,按照上面的代码来写的,有一个问题,当在一个sendAlert里面弹出一个新的alert时,原来的alert是被present出来的,没有消失掉,又要弹出新的alert,app终端就会提示已经有一个模态推出的视图;为了让它可以被推出;

2.我又在present的时候做了判断,判断当前的window的rootWindow,是否有presentedViewController并且这个Controller的状态是否是isBeDismissed,有值且没有被dismiss,那有就先dismiss掉,之后再调用presented方法;但问题还在,有时会出现还没有dismiss掉,present的动作就又发起了;

3.又重新找了一个方法,依然判断是否存在presentedViewController并且这个Controller的状态是否是isBeDismissed,有值且没有被dismiss,那有就先dismiss掉,在dismiss的结束block里面去present新的alert,没有的就直接present出来,这样,这个问题就避免了。

然并卵

领导说,这样做其实很费力的,感觉会遗留很多问题出来(我是有点儿说不清楚,至少window这个角色我就觉得有些问题),所以没有做。

我觉得,后续可以看三方的比较好的alert的处理方案,借鉴代码也是可以的,只要逻辑相通,代码的修改量不大,都是可以接受的。

总结

突然就对UIWindow这个东西感兴趣,想要做出一个类似于UIAlertView的东西,来兼容两个版本,可我发现,UIWindow在makeKeysAndVisible这个方法上,必须要有rootViewController使我非常不明白,后续弄清楚了,再继续补充说明吧。