IOS工作笔记(九)

时间:2022-12-28 21:16:09

关于IOS与服务器交互json数据
①从服务器接受json数据:
这个可以用AFN,接受方式一般为get。如:

 1 +(NSArray *)getContactsFromServer:(ZMMeetingAddViewController *)meetingAddController{
 2     NSMutableArray *contactsArr = [NSMutableArray array];
 3     
 4     NSString *urlGetContactsFromServer = [NSString stringWithFormat:@"http://xxx.com"];
 5     
 6     urlGetContactsFromServer = [urlGetContactsFromServer stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
 7     AFHTTPRequestOperationManager *requestManager = [[AFHTTPRequestOperationManager alloc]init];
 8     requestManager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html",@"application/json", @"text/plain", nil];
 9     requestManager.requestSerializer.HTTPShouldHandleCookies = YES;
10     
11     [requestManager GET:urlGetContactsFromServer
12              parameters:nil
13                 success:^(AFHTTPRequestOperation *operation, id responseObject) {
14                     NSDictionary *dict = responseObject;
15                     NSString *resultStr = [dict objectForKey:@"result"];
16                     NSArray *contactsList = [dict objectForKey:@"list"];
17                     NSString *errorStr = [dict objectForKey:@"error_reason"];
18                     if (resultStr && [resultStr isEqualToString:@"success"]) {
19                         for (NSDictionary *dic in contactsList) {
20                             NSString *telNum = [dic objectForKey:@"tel"];
21                             NSString *userName = [dic objectForKey:@"name"];
22                             ZMContactsFromServer *contact = [[ZMContactsFromServer alloc]init];
23                             contact.userName = userName;
24                             contact.telNum = telNum;
25                             [contactsArr addObject:contact];
26                         }
27                     } else {
28                         NSLog(@"%@",errorStr);
29                     }
30                     meetingAddController.allContacts = contactsArr;
31                     
32                     [meetingAddController.tableView reloadData];
33                     
34                 } failure:^(AFHTTPRequestOperation *operation, NSError *error){
35                     NSLog(@"数据请求失败");
36                     
37                 }];
38     
39     return contactsArr;
40 }

②往服务器发送json数据,需要注意以下几点
⑴一定要用post方式
⑵设置请求头
⑶设置请求体

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // 1.创建请求
    NSURL *url = [NSURL URLWithString:@"http://xxx.com"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    
    // 2.设置请求头
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    
    // 3.设置请求体
    NSDictionary *json = @{@"order_id" : @"123",@"user_id" : @"789",@"shop" : @"Toll"};
    
    // NSData --> NSDictionary
    // NSDictionary --> NSData
    NSData *data = [NSJSONSerialization dataWithJSONObject:json options:NSJSONWritingPrettyPrinted error:nil];
    request.HTTPBody = data;
    
    // 4.发送请求
    [NSURLConnection sendAsynchronousRequest:request 
                                       queue:[NSOperationQueue mainQueue] 
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError){
                                NSLog(@"%d", data.length);
    }];
}

 

2.关于如何在项目中如何建立分类文件夹的问题,即Classes下分Controller、Model、View、Others等,像这样

IOS工作笔记(九)

但下午在建文件夹时,是这样

IOS工作笔记(九)

不知道是什么原因,按照以前那种方法分类后再编译会提示“Appdelegate.h file not found”错误。
由于新建项目时用的Xcode版本是6.3.2,用5.2版本按照这种方法则完全正常,所以想当然的以为是xcode版本问题,还在网上查了半天资料。后来问了人,才恍然大悟。
xcode中用new group方法添加的文件夹不是真实存在的,若想真实存在,则需要点击项目名称——show in finder,新建文件夹Classes,然后往里边拖或Add Files To "项目名称"时,这时就要特别注意选择类型

IOS工作笔记(九)

应选择“Create groups”,而不是“Create folder reference”。
切记。

 

3. 关于多线程处理问题
子线程是不能更新UI的,它只负责数据的处理。只有主线程才能更新UI,若在子线程更新UI,则会报

Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now

比如,点击按钮产生一个新线程

-(void)clickTheBtn{
    NSThread *buttonThread = [[NSThread alloc] initWithTarget:self selector:@selector(readSim) object:nil];
    [buttonThread setName:@"thread-1"];
    [buttonThread start];
}

错误做法

-(void)readSim{
    //其它操作
    //更新UI
    [self.imgBtn setImage:[UIImage imageNamed:@"newimg"] forState:UIControlStateNormal];//会报错  
}

正确的做法

-(void)readSim{
    //其它操作
    //更新UI
    dispatch_sync(dispatch_get_main_queue(), ^(){
        // 这里的代码会在主线程执行
        [self.imgBtn setImage:[UIImage imageNamed:@"newimg"] forState:UIControlStateNormal]; 
    });
}

利用block块的方法比普通方法简单,如

//在主线程上执行操作
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];

 

4.json格式问题,在处理json时,从后台返回来的格式如下

@"{\"name\":\"wewins bluetooth data\",\"uuid\":\"26750052\"}"

但打印后却显示正常

{"name":"wewins bluetooth data","uuid":"26750052"}

这可能是Xcode进行了自动转译。对于这种形式的json,用传统替代字符串的方法处理会出现问题,由于“\”属于转义字符,所以处理“\”时得用“\\”

1 NSString *uuidDev = self.locationArray[1];//self.locationArray[1]即为上述反斜杠形式字符串
2 NSString *character = nil;
3 for (int i=0; i<strWithLine.length; i++) {
4     character = [strWithLine substringWithRange:NSMakeRange(i, 1)];
5     if ([character isEqualToString:@"\""]) {
6         [strWithLine deleteCharactersInRange:NSMakeRange(i, 1)];
7     }
8 }

上面的方法在处理单个或无规则位置的反斜杠“\”有用。对于上述形式则需要用SBJson来处理。

1 NSMutableString *strWithLine = self.locationArray[1];
2 SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
3 NSDictionary *dictTemp = [jsonParser objectWithString:strWithLine];

SBJson的功能很强大,对于数组或是字典,都可以用objectWithString来解析

1 NSString *jsonStr  = @"{\"name\":\"jia\",\"age\":\"24\"}";
2 NSString *jsonStr2 = @"[\"1\",\"2\"]";
3 SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
4 NSMutableDictionary *dict = [jsonParser objectWithString:jsonStr];
5 NSLog(@"%@",dict); 
6 NSMutableArray *arr = [jsonParser objectWithString:jsonStr2];
7 NSLog(@"%@",arr);

 

5.关于代理取值后数组数据消失的问题
经过是这样的,之前要把一个功能整合到项目中,这个功能的作用是在程序启动时用代理获取一个数组,先做的demo,测试正常。但加到项目中后,刚开始数组是有值的,但跳转几次页面后数据消失(之前的demo也有页面跳转),然后开始分析。
之前一直以为是自己的程序有问题,debug测试了老半天,对该数组进行追踪,发现没有其它操作,但数据就是莫名其妙地消失了。
经过debug,还是有了点眉目,因为项目是混合应用,所以页面有的是单纯的html跳转,有的是controller之间跳。当时完全没有想到这里,认为demo中经过几次页面跳转后数据还在,现在跳转后不在了是自己的写法有问题。
然后呢,最后问题确认了,项目中多加了一个跳转,然后再返回原页面。这步操作是原生写法,不是HTML的。在返回原页面时用的是

1 MainPageViewController *second = [[MainPageViewController alloc] init];
2 AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
3 appDelegate.window.rootViewController = second;

由上边可以看出返回原页面时对controller新建了一个实例。跟原来的controller不是同一个,所以数组数据就消失了。
知道问题在哪,解决方法就有了。
①页面跳转时都不要新建实例,可以把controller作为参数传递。这样不管跳转几次,都只有一个实例
②若新建了实例,那么需要把数据存到单例中,最明显的例子就是Appdelegate和NSUserDefaults。

1 //存储时,在AppDelegate.h中
2 @property(nonatomic,strong) NSMutableArray *tempArr;
3 //在需要储存数据的controller中
4 AppDelegate *mainDelegate = [[UIApplication sharedApplication] delegate];
5 mainDelegate.tempArr = self.dataArr;
6 //读取数据时
7 AppDelegate *mainDelegate = [[UIApplication sharedApplication] delegate];
8 NSArray *arrList = mainDelegate.tempArr;

 

6.沙盒的应用,往plist文件存储数据
一些基本的数据类型,如NSString、NSDictionary、NSArray、NSData、NSNumber等可以存储到plist文件中。如:

 1 //要存储的数组
 2 NSString *str = @"abc";  
 3 NSString *astr = @"efg";  
 4 NSArray *Array = [NSArray arrayWithObjects:str, astr, nil];
 5 
 6 //存储数据 
 7 NSString *Path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; 
 8 NSString *filename = [Path stringByAppendingPathComponent:@"test.plist"];  
 9 [NSKeyedArchiver archiveRootObject:Array toFile:filename];
10 
11 //获取数据
12 NSArray *arr = [NSKeyedUnarchiver unarchiveObjectWithFile: filename];  
13 NSString *strRead = [arr objectAtIndex:0];  
14 NSString *astrRead =  [arr objectAtIndex:1];

此类操作时需要用NSKeyedArchiver的archiveRootObject和unarchiveObjectWithFile操作。这在SSKeyChain中也有用到。
若要存储model类,则需要重写encodeWithCoder和initWithCoder方法。如在一个对象User中
在.h中

1 @interface ZMUserModel : NSObject<NSCoding>
2 @property(nonatomic,copy) NSString *userId;
3 @property(nonatomic,copy) NSString *userIpAdd;
4 @property(nonatomic,copy) NSString *userTel;
5 @end

在.m中

@implementation ZMUserModel

-(void)encodeWithCoder:(NSCoder *)aCoder{
    [aCoder encodeObject:self.userId forKey:@"userId"];
    [aCoder encodeObject:self.userIpAdd forKey:@"userIpAdd"];
    [aCoder encodeObject:self.userTel forKey:@"userTel"];
}

-(id)initWithCoder:(NSCoder *)aDecoder{
    if (self == [super init]) {
        self.userId = [aDecoder decodeObjectForKey:@"userId"];
        self.userIpAdd = [aDecoder decodeObjectForKey:@"userIpAdd"];
        self.userTel = [aDecoder decodeObjectForKey:@"userTel"];
    }
    return self;
}

@end

然后在获取时可以用类似于下边的

for (NSData *userData in defaultsArr) {
    ZMUserModel *userModel = [NSKeyedUnarchiver unarchiveObjectWithData:userData];
    if ([userId isEqualToString:userModel.userId]) {
        ipStr = userModel.userIpAdd;
        userTel = userModel.userTel;
    }
}

 

7.Category是IOS的一种设计模式,用来对已存在的类添加方法,新添加的方法也会被扩展的类的子类继承。假如某个类的方法有bug而不方便修改时,可以用Category解决。但要注意的是,此时要实现那个bug方法的所有功能,否则会出现新bug。
Category只能添加类的方法,不能添加新属性。
添加方法为:新建File——>Source—>Objective-C File,选FileType和Class,命名。如对NSString添加TurnStn方法(反转字符),则

IOS工作笔记(九)

系统会生成固定格式的方法名,NSString+TurnStn。

IOS工作笔记(九)

在NSString+TurnStn的.h中

@interface NSString (TurnStn)  
+ (NSString *) reverseString:(NSString *)strSrc;  
@end

在.m中实现

 1 @implementation NSString (TurnStn)  
 2 + (NSString *)reverseString:(NSString *)strSrc;  
 3 {  
 4     NSMutableString *reversedString =[[NSMutableString alloc]init];  
 5     NSInteger charIndex = [strSrc length];  
 6     while (charIndex > 0) {  
 7         charIndex--;  
 8         NSRange subStrRange =NSMakeRange(charIndex, 1);  
 9         [reversedString appendString:[strSrcsubstringWithRange:subStrRange]];  
10     }  
11     return reversedString;  
12 }  
13 @end

使用时直接调用reverseString即可,非常方便。

还有一种特殊的Category使用方法。如在view中想获取宽度,需要用

CGFloat widthTemp = self.frame.size.width;

若不想这么繁琐,则可以扩展

在UIView+ShortStr的.h中

1 @interface UIView (ShortStr)
2 // 这样只会生成方法的声明, 不会生成方法的实现和成员变量
3 @property(nonatomic,assign) CGFloat width;
4 @end

在.m中

 1 @implementation UIView (ShortStr)
 2 - (void)setWidth:(CGFloat)width
 3 {
 4     CGRect frame = self.frame;
 5     frame.size.width = width;
 6     self.frame = frame;
 7 }
 8 
 9 - (CGFloat)width
10 {
11     return self.frame.size.width;
12 }
13 @end

 

8.NSString转换大小写
有两套方法,一种是lowercaseString,uppercaseString;另一套是uppercaseStringWithLocale,lowercaseStringWithLocale
推荐用第二套,因为考虑到本地化问题

1 NSString *testString = @"Hello World";
2 NSString *lowerCaseString1 = [testString lowercaseString];
3 NSLog(@"lowerCaseString1: %@",lowerCaseString1);
4 //结果为 hello world
5 
6 NSString *upperStr = [testString uppercaseStringWithLocale:[NSLocale currentLocale]];
7 NSLog(@"upperStr: %@",upperStr);
8 //结果为 HELLO WORLD

 

9.获取folder reference(即蓝色文件夹)内文件

IOS工作笔记(九)

常规的图片获取方法

UIImage *img = [UIImage imageNamed:@"a"];

是获取不到的,这只适用于group类型的(黄色文件夹)。
正确的应该是

1 NSString *path = [[NSBundle mainBundle] pathForResource:@"a.png" ofType:nil inDirectory:@"images/ttt"];
2 UIImage *img = [UIImage imageWithContentsOfFile:path];

或者是

UIImage *img = [UIImage imageNamed:@"images/ttt/a.png"];

这里牵涉到ios的相对路径和绝对路径。下边的就是相对路径

UIImage *img=[UIImage imageNamed:@"cellicon.png"];

folder里的由于不能编译,所以得是绝对路径。获取程序根目录的方法为:

NSString *basePath = [NSString stringWithFormat:@"%@%@",[[NSBundle mainBundle]resourcePath],@"/"];
NSLog("根路径为%@",basePath);

打印结果为

根路径为/Users/apologize/Library/Developer/CoreSimulator/Devices/9175C57E-399F-41DA-8405-D153A5FBEC9F/data/Containers/Bundle/Application/9852AB51-3E69-4CB6-ADF3-AAE3AF563429/Demo-获取文件内图片.app/

 

10.关于block变量的使用
在使用MBProgressHud时,出现

Variable is not assignable (missing__block type specifier)
出错代码如下

IOS工作笔记(九)

经查找,是因为在block内部使用block外部定义的局部变量时,该变量没有被__block修饰(注意是双下划线)。没有被__block修饰的变量在block内部是readonly的。若想修改,只能在前边加__block修饰
但是,若在block内修改全局变量,则不需要__block修饰。
那么修改方案如下

 1 @property(nonatomic,strong) MBProgressHUD *hhh;
 2 
 3 -(void)showTextDialog{
 4     __block MBProgressHUD *mbHud = [[MBProgressHUD alloc]initWithView:self.view];
 5     [self.view addSubview:mbHud];
 6     mbHud.dimBackground = YES;
 7     mbHud.labelText = @"请稍等";
 8     [mbHud showAnimated:YES whileExecutingBlock:^{
 9         //对话框显示时进行的操作
10         sleep(3);
11     }completionBlock:^{
12         //操作完后取消对话框
13         [mbHud removeFromSuperview];
14         mbHud = nil;
15         self.hhh = nil;
16     }];
17 }

 

11. 关于UIAlertView的自动消失
网上的有两种方法,一种用NSTimer,一种用dismissWithClickedButtonIndex。这里说下第二种。
之前用

 1 -(void)clickBtn{
 2     NSThread *newThread = [[NSThread alloc]initWithTarget:self selector:@selector(showAlert) object:nil];
 3     [newThread setName:@"newThread"];
 4     [newThread start];
 5     
 6 }
 7 
 8 -(void)showAlert{
 9     __block UIAlertView *alert = [[UIAlertView alloc]init];
10     dispatch_sync(dispatch_get_main_queue(), ^(){
11         alert = [[UIAlertView alloc]initWithTitle:nil message:@"正在写卡" delegate:self cancelButtonTitle:nil otherButtonTitles:nil, nil];
12         [alert show];
13         
14     });
15 
16     [alert dismissWithClickedButtonIndex:0 animated:YES ];
17 }

但有时会出现alert窗口一直不消失的情况。这时只需做个判断即可,alertView有个visible属性

if (alert.visible) {
        [alert dismissWithClickedButtonIndex:0 animated:YES ];
}

还有一种是delay再执行消失,但这种情况下alert的展示是固定时长的,酌情使用

 1 - (void)showAlert{            
 2     UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"title" message:@"message" delegate:nil 
 3 cancelButtonTitle:nil otherButtonTitles:nil];
 4     
 5     [alert show];
 6     [self performSelector:@selector(dimissAlert) withObject:alert afterDelay:2.0];
 7 }
 8 
 9 -(void)dimissAlert{
10     if (alert.visible) {
11         [alert dismissWithClickedButtonIndex:0 animated:YES ];
12     }
13 }