在之前也写过正反向传值的文章,不过语言是Swift的(http://www.jianshu.com/p/be608f587e78 ),其实方法都差不多,现在用Objective-C实现,其中穿插正则表达式,原生json解析,category,原生联网请求(在XML解析一文中也使用过,这里会提到更高级的联网语法)等。联网记得修改plist文件
很久没写过有意思的文章了,一直想把一个程序解释清楚,但是只要代码量一大就不知道从何说起,所以之前的好多文章都是直接放代码,代码中写清楚注释就OK了,现在再看一下之前的文章,如果不费点脑筋还是不易读懂的,好吧,一句话:懂点技术,但是表达一点不会。
本例子界面是通过StoryBoard搭建的,具体如下图所示:
两个界面中的TextField都是自定义的,定制后的效果就是没有边框,然后在文本框下方有一条线,效果图如下:
自定义文本框代码:
.h文件中什么也不用写,这里放.m文件
#import "CDmMyTextField.h"
@implementation CDmMyTextField
- (void)drawRect:(CGRect)rect {
//加下划线
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:CGPointMake(0, rect.size.height)];
[bezierPath addLineToPoint:CGPointMake(rect.size.width, rect.size.height)];
[bezierPath stroke];
}
@end
界面搭建好了,接下来就用原来生成的ViewController写代码
.h文件也是空的,这里放.m文件
#import "ViewController.h"
#import "UIViewController+CDTool.h"
#import "CDRegViewController.h"
#import "CDSnsContext.h"
@interface ViewController () //
@property (weak, nonatomic) IBOutlet UITextField *userNameFiel;
@property (weak, nonatomic) IBOutlet UITextField *passWordField;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//消息中心传值
//注册观察者
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(foo:) name:@"名字" object:nil];
}
//接收到消息进行处理
- (void)foo:(NSNotification *) sender{
NSArray *array = sender.object;
_userNameFiel.text = array[0];
_passWordField.text = array[1];
}
//实现协议方法
//-(void)fillUsername:(NSString *)username password:(NSString *)password{
// _userNameFiel.text = username;
// _passWordField.text = password;
//}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)loginButtonClicked:(UIButton *)sender {
NSString *uid = _userNameFiel.text;
NSString *pwd = _passWordField.text;
//通过字符串拼接的方式构造带查询参数的URL字符串
NSString *urlStr = [NSString stringWithFormat:@"http://10.0.8.8/sns/my/login.php?username=%@&password=%@",uid,pwd];
NSURL *url = [NSURL URLWithString:urlStr];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error) {
//原生json解析
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSString *msg = dict[@"message"];
//弹框提示
[self cd_showAlertWithMessage:msg delay:1.5];
}
}];
//resume非阻塞,异步
[dataTask resume];
}
//该方法是在通过UISegue切换视图控制器的时候自动调用
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
//CDRegViewController *regVC = segue.destinationViewController;
//协议方式
// regVC.delegate = self;
//block方式
// regVC.handler = ^(NSString *username, NSString *password){
// _userNameFiel.text = username;
// _passWordField.text = password;
// };
//单利方式
//在viewWillAppear里面写
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
// _userNameFiel.text = [CDSnsContext sharedSnsContext].username;
// _passWordField.text = [CDSnsContext sharedSnsContext].password;
}
//dealloc方法中需要处理的事情
-(void)dealloc{
//1.如果使用的NSTimer 那么要调用invalidate方法使其失效
//2.如果使用了观察者 那么要移除观察者
//3.如果使用系统底层的C函数申请了内存 那么在此处需要手动释放
// - malloc() ---> free() --->nil
// - CFXxxCreate() ---> CFRelease()
//移除观察者
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end
接着先把注册相关代码放上来,等下再解释
注册.h文件
#import
//@protocol CDRegisterSuccessDelegate
//- (void) fillUsername: (NSString *) username
// password: (NSString *) password;
//@end
//此处将block类型重新命名成CDRegHandler
//以下代码如果出现CDRegHandler类型就代表这个block类型
typedef void (^CDRegHandler)(NSString *, NSString *);
@interface CDRegViewController : UIViewController
//@property (nonatomic , weak) id delegate;
//修饰符用法:
//assign - 基本数据类型、枚举、结构体/联合体都写assign,如果属性是一个指针,写assign相当于写weak
//strong - 对象指针通常都写strong,表示引用计数+1
//weak - 协议指针、插座变量、可能导致循环引用的地方都应该写weak
//copy - 确保指针指向不可变对象、获得对象的副本、Block类型写copy
//unsafe_unretained - 八辈子都不会用一次,不会自动将指针赋为nil,导致结果:可能产生野指针
//handler属性是block类型的属性,那么他可以保存一段代码
//在后面写的程序中可以使用handler属性来调用它保存的代码
@property (nonatomic , copy) CDRegHandler handler;
@end
注册.m文件
#import "CDRegViewController.h"
#import "UIViewController+CDTool.h"
#import "NSString+CDUtility.h"
#import "CDSnsContext.h"
@interface CDRegViewController () /**/
@property (weak, nonatomic) IBOutlet UITextField *userField;
@property (weak, nonatomic) IBOutlet UITextField *passField;
@property (weak, nonatomic) IBOutlet UITextField *passField2;
@property (weak, nonatomic) IBOutlet UITextField *emailField;
@end
@implementation CDRegViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)cancelButtonClicked:(UIButton *)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (IBAction)OKAction:(UIButton *)sender {
/*
放到补丁包中
//正则表达式运用
//创建谓词对象,限定字符串只能6-20位
NSPredicate *pridicate = [NSPredicate predicateWithFormat:@"self matches %@",@"^\\w{6,20}$"];
*/
NSString *uid = _userField.text;
//开始验证
if (![uid cd_matches:@"^\\w{6,20}$"]) {
[self cd_showAlertWithMessage:@"无效的用户名" delay:1.5];
return;
}
NSString *pwd = _passField.text;
NSString *email = _emailField.text;
//验证邮箱
// if (![email cd_matches:@"^\\w+([-+.]\\w+)@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"]) {
// [self cd_showAlertWithMessage:@"无效的邮箱" delay:1.5];
// return;
// }
NSString *urlStr = [NSString stringWithFormat:@"http:10.0.8.8/sns/my/register.php?username=%@&password=%@&email=%@",uid,pwd,email];
//URL中不能出现非ASCII码字符(中文,日语,希腊字母等Unicode字符都不能出现在URL中)
//所以要先将含有非ASCII码的URL字符串中的那些字符转成百分号编码才是有效的URL字符串
urlStr = [urlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSURL *url= [NSURL URLWithString:urlStr];
//联网Session高逼格方法(三种:默认,HTTPS,需要验证方式)
//创建联网配置对象(三种配置:默认,无痕,后台)
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
//不用wifi或者电量过低就断网
//config.discretionary = YES;
//超时时间
//config.timeoutIntervalForRequest = 5;
//配置请求头
//config setHTTPAdditionalHeaders:<#(NSDictionary * _Nullable)#>
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error) {
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSString *msg = dict[@"message"];
NSString *code = dict[@"code"];
//登陆成功会返回相应字符串
if ([code isEqualToString:@"registered"]) {
//反向传值,将用户名和密码传入登录界面的文本框
//协议方式
// [_delegate fillUsername:uid password:pwd];
//block方式
//传入对应的参数调用handler保存的代码
//_handler(uid,pwd);
//单利方式
//[CDSnsContext sharedSnsContext].username = _userField.text;
//[CDSnsContext sharedSnsContext].password = _passField.text;
NSArray *array = @[_userField.text,_passField.text];
//消息中心
[[NSNotificationCenter defaultCenter] postNotificationName:@"名字" object:array userInfo:nil];
[self dismissViewControllerAnimated:YES completion:nil];
}
else{
//弹框提示
[self cd_showAlertWithMessage:msg delay:1.5];
}
}
}];
[dataTask resume];
}
@end
在上面的代码中使用了协议方式、Block方式、单例方式、消息中心方式进行了传值,(协议方式、Block方式、单例方式都已经注释掉了,最后留下来的是消息中心的方式)注册成功后,注册后的账号和密码会传给登录界面,回到登录界面时就自动补全刚才注册的账号密码,至于什么是协议方式、Block方式、单例方式、消息中心方式可以看原来的文章“正反向传值”,只是Block在Swift中叫做闭包,实现方法差不多。
其中单例方式需要创建单例对象,方法在代码注释中很详细,代码如下:
.h文件
#import
@interface CDSnsContext : NSObject
//需要保存的属性
@property (nonatomic , copy) NSString *username;
@property (nonatomic , copy) NSString *password;
+ (instancetype) sharedSnsContext;
@end
.m文件
#import "CDSnsContext.h"
//实现单利的两个要点
//1.拒绝使用初始化方法创建对象
//2.通过类方法向外界提供唯一的实例
@implementation CDSnsContext
//1.1拒绝使用初始化方法创建对象
-(instancetype)init {
//敢调用就敢抛异常
@throw [NSException exceptionWithName:@"CDSnsContextInitNSException" reason:@"不能调用init方法创建CDSnsContext对象" userInfo:nil];
}
//1.2创建一个私有的初始化方法
-(instancetype)initPrivate{
if (self = [super init]){
}
return self;
}
//2.通过类方法向外界提供唯一的实例
+ (instancetype)sharedSnsContext{
//加了static的都是放到全局数据段的,拥有全局生命周期,独一无二(程序中永远只有一个本对象)
static CDSnsContext *instance = nil;
//为了解决多线程可以创建多个本对象问题,需要加上同步锁,保证多线程也只能创建一个本对象
//同步锁
// @synchronized (self) {
// if (!instance) {
// instance = [[self alloc] initPrivate];
// }
// }
//此处使用一次派发效率会更高
//一次派发
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (!instance) {
instance = [[self alloc] initPrivate];
}
});
return instance;
}
@end
在代码中还是用到了一个方法“cd_showAlertWithMessage: delay:”和一个正则表达式的判断方法“cd_matches:”(邮箱判断的正则表达式来自谷歌,好像有点问题),第一个是弹出提示框并自动消失,为了让每个试图控制器都有这个功能,所以给ViewController写了一个category,同样的,第二个方法为了让每个字符串都具有正则校验功能,所以给NSString写了category
ViewController的category
.h文件
#import
@interface UIViewController (CDTool)
-(void)cd_showAlertWithMessage: (NSString *) message
delay: (CGFloat) interval;
@end
.m文件
#import "UIViewController+CDTool.h"
@implementation UIViewController (CDTool)
-(void)cd_showAlertWithMessage: (NSString *) message
delay: (CGFloat) interval{
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"温馨提示" message:message preferredStyle:UIAlertControllerStyleAlert];
[self presentViewController:alert animated:YES completion:nil];
//延迟操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//提示框消失
[self dismissViewControllerAnimated:YES completion:nil];
});
});
}
@end
NSString的category
.h文件
#import
@interface NSString (CDUtility)
- (BOOL) cd_matches: (NSString *) regex;
@end
.m文件
#import "NSString+CDUtility.h"
@implementation NSString (CDUtility)
- (BOOL) cd_matches: (NSString *) regex{
//正则表达式运用
//创建谓词对象,限定字符串只能6-20位
NSPredicate *pridicate = [NSPredicate predicateWithFormat:@"self matches %@",regex];
return [pridicate evaluateWithObject:self];
}
@end
如何创建category?
首先创建一个Objective-C的文件
再选中category,需要category的类,起个名字就OK了