iOS测试和Android测试的区别

iOS测试和Android测试的区别
IOS 和安卓系统比较
1、系统与框架结构
2、渲染机制
2.1 iOS*先响应屏幕
2.2 iOS系统优先处理Touch层级
2.3 iOS图形特效基于GPU加速渲染
iOS测试和Android测试
Android以及iOS是当下手机的两大主流操作系统,APP测试中,iOS测试和Android测试需要分开测试。

IOS 和安卓系统比较
从以下几个方面,先简单了解一下这两大操作系统的不同之处

1、系统与框架结构
Android系统的底层建立在Linux系统之上;而ios基于UNIX系统。
这一点就造成了Android与iOS的生态不同了,Android完全开源,任何软件开发商或者个人都能开发安卓的软件;ios完全封源开发。

Android的编程语言是Java和KotLin;而ios的则为ObjectC和Swift。
Android的Java,面向对象,性能比C语言和OC低;ios的OC,基于对象,完全兼容C语言的语法,可以直接操作内存。
Android生成class文件,需要虚拟机来进行解释;ios直接执行程序的二进制代码。
这也在根本上造成了iOS与Android性能不同:Android和Window一样,目的是打造一款通用性非常好的系统,在任何机器上面都可以运行;ios目的是让软件和硬件完美的结合到一块,该操作系统只能在*少数机器上面才能运行。

iOS与Android的运行机制:ios采用的是沙盒运行机制;安卓采用的是虚拟机运行机制。
iOS采用伪后台,当用户HOME键退出应用时,IOS其实关闭了程序,只保留应用的图像入口,只会默认将*后的运行数据记录在RAM中。之所以IOS也能收到推送,是因为应用程序开启推送后,系统会增加一些进程,这些进程会从苹果服务器接收信息,然后再通过服务器发给用户,苹果服务器在这里是起到了中转的作用。
安卓手机的后台是真后台,将应用保留在RAM中,之所以能够收到推送,也因为它常驻内存。
所以Android在软件关闭的情况下,无法接收推送信息;ios在软件关闭的情况下,依然可以接收推送信息
iOS系统在系统内存不足时会自动释放内存。

2、渲染机制
2.1 iOS*先响应屏幕
IOS的UI渲染采用实时优先级,Android的UI渲染遵循传统电脑模式的主线程普通优先级

IOS的响应顺序依次为Touch–Media–Service–Core架构
Android系统的优先级响应层级是Application–Framework–Library–Kernal架构

当我们使用iOS或者是Android手机时,*步就是滑屏解锁找到相应程序点击进入。而这个时候往往是所有操控开始的*步骤,iOS系统产品就表现出来了流畅的一面,但Android产品却给人一种卡顿的现象,更别说后续深入玩游戏或者进行其它操控了。

这也就是为什么我们常说iOS比Android流畅了,因为iOS*先响应屏幕,iOS对屏幕反应的优先级是*高的,它的响应顺序依次为Touch–Media–Service–Core架构,换句话说当用户只要触摸接触了屏幕之后,系统就会*优先去处理屏幕显示也就是Touch这个层级,然后才是媒体(Media),服务(Service)以及Core架构。

而Android系统的优先级响应层级则是Application–Framework–Library–Kernal架构,和显示相关的图形图像处理这一部分属于Library,你可以看到到第三位才是它,当你触摸屏幕之后Android系统首先会激活应用,框架然后才是屏幕*后是核心架构。

2.2 iOS系统优先处理Touch层级
优先级的不同导致了iOS产品以及Android手机在操控过程中的表现差异,当你滑动屏幕进行操控的时候,iOS系统会优先处理Touch层级,而Android系统则是第三个才响应Library层级,这是造成它们流畅度不同的因素之一。

不过优先级对系统流畅性有有影响不假,但并不是**对的,造成两系统之间流畅性不一的现象还有其它因素。 目前智能手机硬件装备竞赛当中,其实处理器等配置已经达到了一个瓶颈期,各大旗舰产品在硬件比拼当中基本上没有太大的区别,而这时候GPU就成为了一个凸显差异的重要因素。一些大型软件像是3d游戏对GPU性能要求都会比较高,苹果iPhone产品采用的Power VR SGX系列GPU在当下来说非常的主流,跑分测试数据证明了它并不会比一些旗舰级别的Android产品差劲。

2.3 iOS图形特效基于GPU加速渲染
iOS系统对图形的各种特效处理基本上正好都是基于GPU硬件进行加速的,它可以不用完全借助CPU或者程序本身,而是通过GPU进行渲染以达到更流畅的操控表现。

但是Android系统产品则并非如此,因为Android需要适应不同的手机硬件,需要满足各种差异配置,所以很多图形特效大多都要靠程序本身进行加速和渲染,并严重依赖CPU运算的操作自然会加大处理器的负荷,从而出现卡顿的问题。

iOS系统对图形的各种特效处理基本上正好都是基于GPU硬件进行加速的,它可以不用完全借助CPU或者程序本身,而是通过GPU进行渲染以达到更流畅的操控表现。
Android系统产品则并非如此,因为Android需要适应不同的手机硬件,需要满足各种差异配置,所以很多图形特效大多都要靠程序本身进行加速和渲染。
(虽然Android 4.0以及4.1等更高版本中进行了改进将硬件加速设为默认开启,但依旧无法做到所有特效全部都靠GPU进行加速。在很多Android手机里面都自带有“是否开启GPU渲染”这个功能选项,不过开启之后的改善也是微乎其微。)

屏幕*先响应的优先级关系,再加上iSO本身GPU加速程序的特性,使得在操控过程中感觉iOS手机拥有着良好的流畅性。因为它本身的整个流程都是在为*大化的流畅做服务,不管是*印象的滑动接触屏幕,还是你进一步使用程序之后的更深层操作都是如此。而GPU加速这点特性,是它优于Android系统流畅性的又一个因素。

iOS测试和Android测试
APP测试中,iOS测试和Android测试主要会针对以下几个点进行测试:

分辨率的测试:Android端有20多种,iOS相对少一点。
操作系统版本:Android的操作系统版本比较多,现在常见的是Android9和Android10,还有不同手机厂商的版本,比如小米的MIUI,魅族的Flyme;
iOS的比较少,而且它只支持单向升级,不能支持降级。
操作习惯的不同:像Android,习惯的去点击back键,虽然现在很多都是全面屏,都是通过手势滑动返回,但还是属于back键的功能,所以Android需要测试back键是否被重写了,点击了back键系统的反馈是不是正常的。
推送消息的测试:Android点击home键后,程序运行到后台,那么这个时候,推送消息是否可以正常被推送,以及点击应用程序,唤醒到前台运行的时候,然后点击消息,是否可以正常的跳转;
iOS点击Home键或是锁屏,或者是关闭程序的时候,消息推送是否是正常的。
安装和卸载测试:Android的安装的平台和渠道相对会比较多,而iOS的话一般只支持官方的渠道比如说 APP store 、 iTunes 工具以及 testflight 的下载

iOS iOS NSDate 时间计算

打印效果:

%title插图%num
#import “ViewController.h”

@interface ViewController ()

@end

@implementation ViewController

– (void)viewDidLoad

{

[super viewDidLoad];

//当前时间

[self currentTime];

//当年共多少天

[self yearTotalDays];

//相差几天

[self quiteFewDays];

}

/**

当前时间

*/

– (void)currentTime

{

//得到当前的时间

NSDate * date = [NSDate date];

//时间管理

NSDateFormatter * dateFormatter = [[NSDateFormatter alloc] init];

//时间格式

[dateFormatter setDateFormat:@”yyyy-MM-dd HH:mm:ss”];

//设置时间间隔(秒)(这个我是计算出来的,不知道有没有简便的方法 )

NSTimeInterval time = 365 * 24 * 60 * 60;//一年的秒数

//得到一年之前的当前时间(-:表示向前的时间间隔(即去年),如果没有,则表示向后的时间间隔(即明年))

 

NSDate * lastYear = [date dateByAddingTimeInterval:-time];

//转化为字符串

NSString * startDate = [dateFormatter stringFromDate:lastYear];

NSString *dayYY = [NSString stringWithFormat:@”%@”,startDate];

NSLog(@”dayYY—> %@”,dayYY);

}

/**

 

当年共多少天

*/

– (void)yearTotalDays

{

//2.当年共多少天

NSCalendar *calender = [NSCalendar currentCalendar];

//计算时间

NSDateComponents *comps =[calender components:(NSYearCalendarUnit | NSMonthCalendarUnit |NSDayCalendarUnit | NSWeekCalendarUnit)fromDate:[NSDate date]];

 

int count = 0;

for (int i=1; i<=12; i++) {

 

[comps setMonth:i];

 

NSRange range = [calender rangeOfUnit:NSDayCalendarUnit inUnit:NSMonthCalendarUnit forDate: [calender dateFromComponents:comps]];

 

count += range.length;

}

NSLog(@”count—>%d”, count);

}

/**

相差几天

*/

– (void)quiteFewDays

{

//3.当时时间距离未来(过去)的天数

NSString * time = @”2017-06-07″;  //可以写过去时间差

NSDateFormatter * dateFormatter = [[NSDateFormatter alloc] init];

[dateFormatter setDateFormat:@”yyyy-MM-dd”];

 

//根据需求 选择date的方法

NSDate * date = [dateFormatter dateFromString:time];//x现在到未来的时间差

//NSDate * date = [time dateFromString:dateFormatter];//现在和以前的时间差天数

NSDate * currentDate = [NSDate date];

 

NSUInteger sec = [date timeIntervalSinceDate:currentDate];

NSLog(@”sec—->%zd”,sec/(3600*24));

}

 

iOS Mac下SVN环境的配置方法

友情提示:特别轻松的配置 svn

一、创建代码仓库:

1.打开终端,输入创建一个仓库的指令,如:svnadmin create /Users/svn/mycode

%title插图%num

二、配置svn的用户权限:

2.权限配置文件在 /svn/mycode/conf目录下;

打开svnserve.conf,主要配置项前面的“#”和空格都去掉;

如图:

%title插图%num

如图:正确的效果

%title插图%num

三、第二个权限配置文件passwd,在[users]下面添加帐号和密码

如图:正确的效果

%title插图%num

四、第三个权限配置文件authz,可以配置用户组和权限:

在[groups]下面添加组名和用户名,多个用户之间用逗号隔开,

如图:正确的效果

%title插图%num

注意点:使用[/]代表svn服务器中的所有资源库,如图,group这个组中的所有用户对所有资源库都有读写(rw)权限,组名前面要用@

五、启动svn服务器:

终端输入启动服务器的指令:svnserve -d -r /Users/svn

没有任何提示才是说明启动成功,

六、启动失败:svnserve: Can’t bind server socket: Address already in use 错误

解决svnserve: Can’t bind server socket: Address already in use

怎么解决呢?

killall  svnserve

svnserve -d -r /opt/svn/repos   启动成功。

图:

%title插图%num

七、关闭svn服务器:

关闭svn服务器要在实用工具里面打开“活动监视器”操作

如下图:

%title插图%num

八、导入代码

svn import  代码的路径     就可以下载服务器的代码

谢谢!!!

iOS – 底层知识学习之路-Mach-o 、lldb、 dyld

MACHO与重定位符号表认识。
命令回顾

1. 将源代码编译成可执行文件命令

Clang 文件名 -o 输出的可执行文件名称

如:
Clang test.m – o test
2. 查看可执行文件的代码段命令

objdump –macho -d 可执行文件名称

如:
objdump –macho -d test
3. 将文件编译生.o文件命令

clang -c 文件名 -o .o文件名
如:
clang -c test.m -o test.o
4. 分析.o文件的代码段命令

源码:

void test() {

}
void test_1() {

}
int global = 10;
int main(){
global = 21;
global = 20;
test();
test_1();
return 0;
}
执行:

objdump –macho -d test.o
结果:

(__TEXT,__text) section
_test:
0: 55 pushq %rbp
1: 48 89 e5 movq %rsp, %rbp
4: 5d popq %rbp
5: c3 retq
6: 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:_test(%rax,%rax)
_test_1:
10: 55 pushq %rbp
11: 48 89 e5 movq %rsp, %rbp
14: 5d popq %rbp
15: c3 retq
16: 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:_test(%rax,%rax)
_main:
20: 55 pushq %rbp
21: 48 89 e5 movq %rsp, %rbp
24: 48 83 ec 10 subq $16, %rsp
28: c7 45 fc 00 00 00 00 movl $_test, -4(%rbp)
2f: c7 05 fc ff ff ff 15 00 00 00 movl $21, _global-4(%rip)
39: c7 05 fc ff ff ff 14 00 00 00 movl $20, _global-4(%rip)
43: e8 00 00 00 00 callq _test
48: e8 00 00 00 00 callq _test_1
4d: 31 c0 xorl %eax, %eax
4f: 48 83 c4 10 addq $16, %rsp
53: 5d popq %rbp
54: c3 retq
得出结论:

1. 看汇编可以得出编译顺序与代码的书写顺序是一致的。

2. 看 43 与 48 行地址为 00 ,这是虚拟地址, 起占位作用, 通过重定位符号表来确定*终的内存地址。

3. 查重定位符号表

objdump –macho –reloc .o文件名
如:
objdump –macho –reloc test.o
结果:如下面结果 (43 , 48 的下一位)49 与 44 分别就是上面占位地址的*终内存地址 。

address pcrel length extern type scattered symbolnum/value
00000049 True long True BRANCH False _test_1
00000044 True long True BRANCH False _test
0000003b True long True SIGNED4 False _global
00000031 True long True SIGNED4 False _global
Relocation information (__LD,__compact_unwind) 3 entries
address pcrel length extern type scattered symbolnum/value
00000040 False quad False UNSIGND False 1 (__TEXT,__text)
00000020 False quad False UNSIGND False 1 (__TEXT,__text)
00000000 False quad False UNSIGND False 1 (__TEXT,__text)

%title插图%num

分析可执行文件的代码段命令

objdump –macho -d test
输出:

(__TEXT,__text) section
_test:
100003f60: 55 pushq %rbp
100003f61: 48 89 e5 movq %rsp, %rbp
100003f64: 5d popq %rbp
100003f65: c3 retq
100003f66: 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:(%rax,%rax)
_test_1:
100003f70: 55 pushq %rbp
100003f71: 48 89 e5 movq %rsp, %rbp
100003f74: 5d popq %rbp
100003f75: c3 retq
100003f76: 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:(%rax,%rax)
_main:
100003f80: 55 pushq %rbp
100003f81: 48 89 e5 movq %rsp, %rbp
100003f84: 48 83 ec 10 subq $16, %rsp
100003f88: c7 45 fc 00 00 00 00 movl $0, -4(%rbp)
100003f8f: c7 05 67 40 00 00 15 00 00 00 movl $21, 16487(%rip)
100003f99: c7 05 5d 40 00 00 14 00 00 00 movl $20, 16477(%rip)
100003fa3: e8 b8 ff ff ff callq _test
100003fa8: e8 c3 ff ff ff callq _test_1
100003fad: 31 c0 xorl %eax, %eax
100003faf: 48 83 c4 10 addq $16, %rsp
100003fb3: 5d popq %rbp
100003fb4: c3 retq
mac OS 是小端。  地址从右到左查看 。  右是高位

 

DWARF与DSYM
dsym 文件就是保存按照DWARF格式保存的调试信息的文件 。

dwarf 是一种被众多编译器和调试器使用的用于支持源码代码级别调试的调试文件格式。

dsym文件的生成过程:

1. 读取debug map

2. 从.o文件中加载 dwarf

3. 重新定位所有的地址

4. *后将全部的 dwarf 打包成 dsym bundle

生成调试信息命令

clang -g -c test.m -o test.o
理解第2步的过程: 终端命令 查看 test.o

otool -l test.o
结果:  下面就有dwarf 字段 。

Load command 0
cmd LC_SEGMENT_64
cmdsize 1112
segname
vmaddr 0x0000000000000000
vmsize 0x00000000000004c9
fileoff 1272
filesize 1225
maxprot 0x00000007
initprot 0x00000007
nsects 13
flags 0x0
Section
sectname __text
segname __TEXT
addr 0x0000000000000000
size 0x0000000000000055
offset 1272
align 2^4 (16)
reloff 2500
nreloc 4
flags 0x80000400
reserved1 0
reserved2 0
Section
sectname __data
segname __DATA
addr 0x0000000000000058
size 0x0000000000000004
offset 1360
align 2^2 (4)
reloff 0
nreloc 0
flags 0x00000000
reserved1 0
reserved2 0
Section
sectname __objc_imageinfo
segname __DATA
addr 0x000000000000005c
size 0x0000000000000008
offset 1364
align 2^0 (1)
reloff 0
nreloc 0
flags 0x10000000
reserved1 0
reserved2 0
Section
sectname __debug_str
segname __DWARF
addr 0x0000000000000064
size 0x0000000000000111
offset 1372
align 2^0 (1)
reloff 0
nreloc 0
flags 0x02000000
reserved1 0
reserved2 0
Section
sectname __debug_abbrev
segname __DWARF
addr 0x0000000000000175
size 0x0000000000000061
offset 1645
align 2^0 (1)
reloff 0
nreloc 0
flags 0x02000000
reserved1 0
reserved2 0
Section
sectname __debug_info
segname __DWARF
addr 0x00000000000001d6
size 0x0000000000000093
offset 1742
align 2^0 (1)
reloff 2532
nreloc 5
flags 0x02000000
reserved1 0
reserved2 0
Section
sectname __apple_names
segname __DWARF
addr 0x0000000000000269
size 0x0000000000000090
offset 1889
align 2^0 (1)
reloff 0
nreloc 0
flags 0x02000000
reserved1 0
reserved2 0
Section
sectname __apple_objc
segname __DWARF
addr 0x00000000000002f9
size 0x0000000000000024
offset 2033
align 2^0 (1)
reloff 0
nreloc 0
flags 0x02000000
reserved1 0
reserved2 0
Section
sectname __apple_namespac
segname __DWARF
addr 0x000000000000031d
size 0x0000000000000024
offset 2069
align 2^0 (1)
reloff 0
nreloc 0
flags 0x02000000
reserved1 0
reserved2 0
Section
sectname __apple_types
segname __DWARF
addr 0x0000000000000341
size 0x0000000000000047
offset 2105
align 2^0 (1)
reloff 0
nreloc 0
flags 0x02000000
reserved1 0
reserved2 0
如果是查看可执行文件,是找不到drarf字段的, 他会单独放在一个地方 ,通过命令查看:

nm -pa 可执行命令文件名
如:
nm -pa test
生成dsym文件命令:

clang -g1 test.m -o test
查看 dsym 命令:

dwarfdump test.dSYM
dsym 内文件中保存的是没有偏移的虚拟地址,所以能够进行bug的现场恢复。

 

计算虚拟地址
ViewController.m文件如下:

1. 获取 ASLR

2. 计算虚拟地址 。

#import “ViewController.h”
#import <mach-o/dyld.h>
#import <mach-o/getsect.h>
#import <objc/runtime.h>

@interface ViewController ()

@end

@implementation ViewController

// 获取ASLR
uintptr_t get_slide_address(void) {
uintptr_t vmaddr_slide = 0;
// 使用的所有的二进制文件 = ipa + 动态库
// ASLR Macho 二进制文件 image 偏移
for (uint32_t i = 0; i < _dyld_image_count(); i++) {
// 遍历的是那个image名称
const char *image_name = (char *)_dyld_get_image_name(i);
const struct mach_header *header = _dyld_get_image_header(i);
if (header->filetype == MH_EXECUTE) {
vmaddr_slide = _dyld_get_image_vmaddr_slide(i);
}
NSString *str = [NSString stringWithUTF8String:image_name];

if ([str containsString:@”TestInject”]) {

NSLog(@”Image name %s at address 0x%llx and ASLR slide 0x%lx.\n”, image_name, (mach_vm_address_t)header, vmaddr_slide);
break;
}
}

// ASLR返回出去
return (uintptr_t)vmaddr_slide;
}

– (void)viewDidLoad {
[super viewDidLoad];
[self getMethodVMA];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self test_dwarf];
});
NSLog(@”123″);
}

– (void)test_dwarf {
NSArray *array = @[];
array[1];
}

– (void)getMethodVMA {
// 运行中的地址(偏移)
IMP imp = (IMP)class_getMethodImplementation(self.class, @selector(test_dwarf));
unsigned long imppos = (unsigned long)imp;
unsigned long slide = get_slide_address();
// 运行中的地址(偏移) – ASLR = 真正的虚拟内存地址
unsigned long addr = imppos – slide;
}
@end
注意: 需要在building setting里面打开生成dysm文件的配置 。  debug默认不生成这个文件 。

xcode 终端格式输入十六进制   lldb调试输入

e -f x — 计算出的虚拟内存地址

结论:

使用的所有的二进制文件 = ipa + 动态库

ASLR Macho 二进制文件 image 偏移

 

 

dyld学习 – 后期通过源码深入学习
dyld 调试:

方式一: 如果想调试dyld的源代码,需要准备带调试信息dyld/libdyld.dylib/libclosured.dylib,与系统做替换,风险比较大。

方式二 : 通过在dyld文件上设置断点。  设置断点的方法有两种。

lldb保留了一个库列表(白名单), 避免在按名称设置短点时出现问题, 而 dyld 正好在这个名单上,所以需要强制设置短点, 方式如下两种:

-s 含义: 指定在哪个文件里面设置断点。

1. br set -n dyldbootstrap::start -s dyld

2. set set target.breakpoints-use-platform-avoid-list 0   (通过lldb)

%title插图%num

终端输入: 打印dyld所有的调用过程 、 其他配置自行查询实践。

DYLD_PRINT_APIS=1 ./ 可执行文件
如:
DYLD_PRINT_APIS=1 ./test
dyld 到底做了什么?

dyld: 动态链接程序

libdyld.dylib :给我们的程序提供在runtime期间能使用动态链接功能。

具体执行步骤如下:

%title插图%num

插入动态库与插入函数: hook

插入函数 :

__attribute__((used))  : 去除未使用的代码的警告,加在*前面 。

__attribute__((used)) static struct {
const void* replacement;
const void* replacee;

%title插图%num

iOS开发之判断用户是否打开APP通知开关,看我就够了

1.*近在做app内部的推送开关功能。 这样顾客可以自己定义推送给他的内容,屏蔽不想要的一些烦人推送。

在开发过程中, 如果顾客打开推送开关的时候,也已经向服务器发送指令,进行推送开关同步,给它说这个用户已经打开了A推送,现在服务器推送A推送给客户端, 这时候照说,客服端是可以收到通知的,但是客服端却没有收到。 这是为什么呢? 很简单的一个问题,原来是顾客没有在系统通知处打开app的通知开关,所以收不到推送是正常现象。

那现在就产生了一个需求:

用户在进行设置推送开关的时候,当用户打开推送开关为开的时候,需要去判断 系统通知处的 推送开关用户有没有进行设置?

网上这样的代码都是一大把,关于怎么去检测系统通知有没有打开,但是,发现运用到程序中,没什么鸟用, 会一直提示说,用户没有打开推送。

下面就到了贴代码的环节了,首先先看下这段代码:

if ([[UIApplication sharedApplication] enabledRemoteNotificationTypes] == UIRemoteNotificationTypeNone) { //判断用户是否打开通知开关
}
typedef NS_OPTIONS(NSUInteger, UIRemoteNotificationType) {//这个是用户当前通知的几种状态,*种就是用户没有开大通知开关
UIRemoteNotificationTypeNone = 0,
UIRemoteNotificationTypeBadge = 1 << 0,
UIRemoteNotificationTypeSound = 1 << 1,
UIRemoteNotificationTypeAlert = 1 << 2,
UIRemoteNotificationTypeNewsstandContentAvailability = 1 << 3,
} NS_ENUM_DEPRECATED_IOS(3_0, 8_0, “Use UIUserNotificationType for user notifications and registerForRemoteNotifications for receiving remote notifications instead.”) __TVOS_PROHIBITED;
我在程序中就是用到了这个方法,去进行检测,用户有没有打开推送开关, 却忽视了

NS_ENUM_DEPRECATED_IOS(3_0, 8_0, “Use UIUserNotificationType for user notifications and registerForRemoteNotifications for receiving remote notifications instead.”) __TVOS_PROHIBITED

通过上面的图,可以看到此方法在iOS8.0就废弃了,虽然说并没有被移除,但是也要用他*新建议的方法去进行版本的兼容,现在加上在iOS9.0上用这个方法进行判断没有任何作用,现在就用新版的判断方法。下面是新版的判断方法。

if ([[UIApplication sharedApplication] currentUserNotificationSettings].types == UIRemoteNotificationTypeNone) {
}
注意:
currentUserNotificationSettings 是一个对象,属于UIUserNotificationSettings类
所以.types切莫忘记。

总结:

*完善的做法就是,进行两者兼容 iOS7下的也要兼容, iOS7以上的我更要兼容啦,*完善的做法

 

#define IOS8 ([[[UIDevice currentDevice] systemVersion] doubleValue] >=8.0 ? YES : NO)

if (IOS8) { //iOS8以上包含iOS8
if ([[UIApplication sharedApplication] currentUserNotificationSettings].types == UIRemoteNotificationTypeNone) {
}
}else{ // ios7 一下
if ([[UIApplication sharedApplication] enabledRemoteNotificationTypes] == UIRemoteNotificationTypeNone) {
}
}

 

iOS – 静态库、动态库从浅到深学习之路 (三)

XCFramework  (framework的增强版)
说明:

1. 苹果官方推荐,支持的,可以更加方便多个平台和架构的分发二进制库的格式。

2. 需要xcode11以上支持

3. 在2019年提出的framework的另一种先进格式。

多架构合并
架构打包命令:

// 打包成模拟器架构
xcodebuild archive -project ‘SYTimer.xcodeproj’ \
-scheme ‘SYTimer’ \
-configuration Release \
-destination ‘generic/platform=iOS Simulator’ \
-archivePath ‘../archives/SYTimer.framework-iphonesimulator.xcarchive’ \
SKIP_INSTALL=NO

// 打包成iOS真机架构
xcodebuild archive -project ‘SYTimer.xcodeproj’ \
-scheme ‘SYTimer’ \
-configuration Release \
-destination ‘generic/platform=iOS’ \
-archivePath ‘../archives/SYTimer.framework-iphoneos.xcarchive’ \
SKIP_INSTALL=NO
胖二进制: 多个架构将库文件压缩放在一起,并不是合并。

通过lipo 命令进行将动态库进行合并。

格式 : lipo -output 合并后输出的名称 -create  架构路径1 架构路径2

lipo -output SYTimer -create ../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer ../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer
遇到问题;

相同的架构合并,会出现冲突

解决方法:

去除相同的架构,只留一个架构,相关命令如下:

lipo -output SYTimer -create ../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer ../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer

//提取 x86_64架构
lipo -output SYTimer-x86_64 -extract x86_64 ../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer

lipo -output SYTimer -create ../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer SYTimer-x86_64
苹果为了解决这个问题,所以引进了XCFramework ,优点相比于 lipo

1.  不用处理头文件

2. 会自动处理重复的架构

3. 会自动产生调试符号

4. 会根据运行的架构自动选择对应的架构 。

 

制作一个XCFramework:
xcodebuild -create-xcframework \
-framework ‘../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework’ \
-framework ‘../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework’ \
-output ‘SYTimer.xcframework’ 需要改xcframework放的路径
xcodebuild -create-xcframework \
-framework ‘../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework’ \
-debug-symbols ‘架构1 – bcsymbolmap – 的*对路径’ \
-debug-symbols ‘架构2 – bcsymbolmap – 的*对路径’ \
-debug-symbols ‘架构1 – dsym – 的相对路径’ \
-framework ‘架构2 -framework – 的相对路径’ \
-debug-symbols ‘架构2 – dysm – 的相对路径’ \
-output ‘SYTimer.xcframework’
weak import
1. 弱引用的作用 ,就是当动态库被引入但没有使用的话,会自动置为nil ,不会让程序崩溃,编译通过,一个符号找不到,不会强制找到为止。 在xcconfig文件中格式为:

OTHER_LDFLAGS = $(inherited) -Xlinker -weak_framework -Xlinker “第三方库或动态库名称”
相同静态库的重复引入,符号冲突的解决方法:

通过链接器的 force_load 引入其中一个静态库, 另外一个静态库的符号通过 load_hidden 去隐藏。

命令如下:

OTHER_LDFLAGS = $(inherited) -l”*个静态库名” -l”第二个静态库名” -Xlinker -force_load -Xlinker “*个静态库路径.a” -Xlinker -load_hidden -Xliner “第二个静态库路径.a”
动态库 、静态库的实战
动态库与动态库的链接实战:

思路:  自己的framework,通过pod导入一个第三方,然后自己的项目,导入自己的framework并使用 ,反向使用等。

实现步骤:

1 .创建一个自己的framework (创建方法见 : framework制作)

2. 在framework,通过cocoapods导入一个第三方库。 (pods 只会生成链接器的参数,并不会把库文件放到framework下,pods是通过脚本是复制到指定目录,方式二:再使用的工程中,通过pods的配置文件,再指定加载的第三方库。)

3. 打开我们自己的framework ,添加一个target,模拟成app,进行使用。

4. 现在的流程就是 app -> 自己的动态库  -> 第三方动态库 (AFNetworking) , 大致的动态库与动态库的模型就出来了。

后期上传demo.

主要出现的问题:

1. 运行app,找不到第三方库文件(因为这个第三方库,是动态库中所引用的第三方库),报错信息:

Undefined symbols for architecture x86_64:
“_OBJC_CLASS_$_ZGRAppObject”, referenced from:
objc-class-ref in ZGRAFNetworkingManager.o
解决方法是:在pod 文件中为这个target也加入pod 第三方库的方法,Podfile 代码如下:

platform :ios, ‘14.1’

#自己做的framework ,动态库
target :’ZGRNetworkManager’ do
use_frameworks!
pod ‘AFNetworking’
end

#比喻App
target :’ZGRNetworkManagerTests’ do
use_frameworks!
pod ‘AFNetworking’
end
2. 找不到对应的符号_OBJC_CLASS_$_ZGRAppObject ,  修改pod的配置文件,创建的target 和 动态库对应的配置文件都加上这句话。

OTHER_LDFLAGS = $(inherited) -framework “AFNetworking” -Xlinker -U -Xlinker _OBJC_CLASS_$_ZGRAppObject
动态库与静态库的链接实战:

思路:  自己的framework,通过pod导入一个第三方静态库 AFNetworking.a ,在framework 中添加一个target,模拟app来使用 。

实现步骤:

1 .创建一个自己的framework (创建方法见 : framework制作)

2. 在framework,通过cocoapods导入一个第三方库。 (pods 只会生成链接器的参数,并不会把库文件放到framework下,pods是通过脚本是复制到指定目录,方式二:再使用的工程中,通过pods的配置文件,再指定加载的第三方库。)

3. 打开我们自己的framework ,添加一个target,模拟成app,进行使用。

4. 现在的流程就是 app -> 自己的动态库  -> 第三方静态库 (AFNetworking.a) , 大致的动态库与静态库的模型就出来了。

 

注:

动态库链接静态库,会链接所有的静态库符号, 所以当app去编译动态库的时候,并不会报错。

静态库的导出符号,也会出现在动态库符号里面,所以可以在app中直接使用,导入头文件即可。   (搜索不到,需要设置 header search paths路径 ${SRCROOT}/Pods/Headers/Public)

后期上传demo.

关于静态库的符号隐藏,通过在pod的配置文件加入如下代码:

OTHER_LDFLAGS = $(inherited) -Xlinker -hidden-l “AFNetworking”
 

静态库与静态库的链接实战:

思路:  自己的framework静态库组件,通过pod导入一个第三方静态库 AFNetworking.a ,在framework 中添加一个target,模拟app来使用 。

实现步骤:

1 .创建一个自己的framework (静态库) (创建方法见 : framework制作)

2. 在framework,通过cocoapods导入一个第三方库静态库。

3. 打开我们自己的framework ,添加一个target,模拟成app,进行使用。

4. 现在的流程就是 app -> 自己的静态库组件  -> 第三方静态库 (AFNetworking.a) 。

 

操作记录:

1. 在静态库组件首先要设置 library search path ,  例如 : “${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking”

2. 设置Other Linker Flags , -lAFNetworking

 

原理就是: app去链接静态库组件, 但静态库组件所链接的静态库并没有路径链接到app,所以需要在组件中手动设置下所链接的静态库的路径与头文件。

 

静态库与动态库的链接实战:

公式:  app = app + 静态库 (所有的符号都是可见的)

思路:  自己的framework静态库,通过pod导入一个第三方动态库 AFNetworking ,在framework 中添加一个target,模拟app来使用 。

实现步骤:

1 .创建一个自己的framework ,静态库(创建方法见 : framework制作)

2. 在framework,通过cocoapods导入一个第三方库AFNetworking。

3. 打开我们自己的framework ,添加一个target,模拟成app,进行使用。

4. 现在的流程就是 app -> 自己的静态库  -> 第三方动态库 (AFNetworking) , 大致的静态库与动态库的模型就出来了。

 

Cocoapods 拷贝动态库脚本的用法。

首先,把脚本拷贝到工程根目录下,脚本文件,可以在任意cocoapods的工程中去找,  然后在项目中,在build phase 添加一个脚本,输入:

“${SRCROOT}/Pods-LGNetworkManagerTests-frameworks.sh”
这样在运行项目的时候,脚本会自动将动态库拷贝到product目录中。

 

总结:

1. XCFramework  – sdk开发,组件开发

解决了:

1.头文件的处理

2. 调试符号的保存

3. 相同架构的处理

4.2019年才出。

 

2.  实战

1. weak_import

使用场景:  动态库 运行时 -》 不能确认到指定位置,所以用到了弱引用,当找不到动态库的时候,项目不会报错的作用

2. 静态库冲突 -》 app -> all_load\-Objc

3. app – 动态库 – 动态库链接  ,app不知道所以需要以下操作

方式一: 通过cocoapod脚本复制

方式二: 通过cocoapod 加载第三方库,并不会加载两次。 不用担心

 

动态库 – 静态库

存在问题:

静态库不想暴露到处符号,需要用到 -hidden -l静态库

 

app – 静态库 – 静态库

1. 名称  知道。

2 不知道所在位置: 所以只需要告诉位置

 

app – 静态库 – 动态库

1. 编译报错:  不知道动态库的路径

2. 运行报错: 不知道动态库的rpath

iOS CPU占有率达到了100%甚至更多,然后导致App闪退情况总结及解决过程

今天在真机调试的过程中,发现了一个严重的问题,发现CPU的使用率竟然达到了100%,以至于会导致运行内存占用过高,被系统的看门狗机制给杀掉。

下面就讲一讲怎么去定位这个问题:

1.打开Xcode,把项目跑动起来,然后选择这个选项卡

%title插图%num

2.现在就可以看到这个画面

%title插图%num
3. 现在我们可以看到这个页面,发现我的CPU达到了 105%,这肯定是有问题,那现在怎么办呢,我们可以看到右边的图,点击Profile in Instruments. —》 然后点击Transfer.

%title插图%num

4. 现在就进入到Instruments中,我们看看究竟发生了什么,到底是什么情况,导致出现了这种问题。

1. 首先,我通过观察CPU占用率,各个页面进行排查,看是进行了何种操作后,才出现的这种CPU占用率居高不小。

2. 我很庆幸,我很快就定位到了原因。所以我可以知道是进入某一个页面,触发了某种操作后,然后,就会出现这种情况

3. 现在就可以通过Instruments来进行定位,来看看是执行什么代码,导致了这种非常耗时的操作,让CPU一直如此忙碌。

4.选中Xcode先把程序(command + R)运行起来

5.再选中Xcode,按快捷键(command + control + i)运行起来,此时Leaks已经跑起来了

6.由于Leaks是动态监测,所以我们需要手动操作APP,一边操作,一边观察Leaks的变化,当出现红色叉时,就监测到了内存泄露,点击右上角的第二个,进行暂停检测(也可继续检测,当多个时暂停,一次处理了多个).

扩展: 查内存泄露具体方法 点击打开链接l

5. 电脑卡爆了,哎。 回去了在截图,反正*后是跟踪到了 Runloop下。 有一个行为一直在占据着主线程,并且不释放,所以导致CPU一直在大量消耗,内存也慢慢渐长,一般能造成这种情况的就只有循环,并且一直没有释放,我利用Instruments中的leaks,然后进行了各种各样的内存泄露的检测及修复, 也正是这样,我发现了问题的所在。 原来是我写的有一个方法有问题。 我写的代码如下:

我们很清晰的看到如果条件为真,这就是一个死循环,我的PM那时候,这儿就想做一个图片一直闪烁的效果,这儿可以采用三种方案,一种是用这种循环引用来执行一套方法, 一种是通过NSTimer来定时去调用一个方法。我开始选择了前者,那时候也知道后果,也许这个死循环会一直存在下去,直到这个VC被dealloc,*后一种是通过 core animation来实现。 这种事*推荐的,具体写法,我会在后面开博客进行讲解

-(void)animationAction:(bool)isNeedbreak{
if(!isNeedbreak) {
[self performSelector:@selector(animationAction:) withObject: [NSNumber numberWithBool:YES] afterDelay:2];
}
}
2. 由于有上面这个担心所以,我在popviewcontroller, 控制器出栈的时候,我调用了如下方法,那个时候太粗心了,大概比方,是我想延迟2s执行一个方法,这个过程中,我想终止方法,那就只有通过调用下面两种随意一种,我却很天真的以为,这样就可以完美的终止死循环的调用。

//这个是取消所有的延迟执行函数。
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(animationAction:) object:[NSNumber numberWithBool:YES]];
[NSObject cancelPreviousPerformRequestsWithTarget:self];

3. 发现问题依然存在,所以只能用我的第二种解决办法, 用NSTimer来代替他。代码如下

NSTimer *animationTwoTime = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(productBrandIconAnimationWithIsBreak:) userInfo:[NSNumber numberWithBool:NO] repeats:YES];

4. 然后在vc出栈的时候,然后把NSTimer进行 invalidate下。

总结:

1.以后一定要慎用用for循环来进行实现动画的连续执行.
2. 这种动画效果尽量用 core animation来进行解决。

 

iOS – Shell 脚本学习入门

解释器与编译器
1. 编译器过程:  源代码 – 预处理器 – 编译器 – 目标代码 – 链接器 – 可执行程序

2. 解释器过程:  源代码 – 解释器  (python ,shell , js)

 

如何学习脚本: 三步骤
1. 学语法

2. 看脚本

3. 抄

 

基本语法:省略 。 自己去 w3c学习 。

常用语法记录:

#!/bin/bash

IS_ZSH=””
# bash-3.2 和 zsh

: << !
Shebang(Hashbang):一个由井号和叹号构成的字符序列#!出现在文本文件的*行的前两个字符。
操作系统的程序加载器会分析Shebang后的内容,将这些内容作为解释器指令。
并调用该指令,并将载有Shebang的文件路径作为该解释器的参数。

#!/usr/bin/python

#!/usr/bin/env pyhon

env:不同对操作系统,脚本解释器可能被安装于系统的不同的目录,设置到系统的PATH中。
env可以在系统的PATH目录中查找。
上述命令,使用在用户路径中找到的*个Python版本。但是可以通过指定版本号:
#!/usr/bin/env pythonX.x

env也可以指定搜索目录:
#!/usr/bin/env -S -P/usr/local/bin:/usr/bin:${PATH} python
会在/usr/local/bin、/usr/bin、系统PATH搜索python。
!

# echo “单行注释”

: << !
多行注释方式一:
echo “多行注释”
!

: << COMMENT
多行注释方式二:
echo “多行注释”
COMMENT

: ‘
多行注释方式三:
echo “多行注释”

if false; then
多行注释方式四:
echo “多行注释”
fi

((0)) && {
多行注释方式五:
echo “多行注释”
}

# 有空格时,将串包裹起来
VARIABLE=”Some string”
LUE=VARIABLE
# FOO=””
LONG_STRING=”I am Cat\\”
LONG_LONG_STRING=”I am Cat!CAT!Cat!Cat”

: ‘COMMENT
单引号与双引号,括号一定要成对
冒号(:)作为内建命令:占位符、参数扩展和重定向
子进程:在当前的shell下,去打开另一个新shell
env:查看环境变量与常见环境变量说明

PS1:提示符设置 [\u@\h \w \A #\#]\$
1. \h 主机名缩写
2. \u 用户账号名称
3. \w 完整工作路径
4. \A 24小时时间
5. \# 第几个命令
6. \$ 提示符,如果是root,提示符为#,否则是$
HOME:代表用户主文件夹。
SHELL:当前使用的是那个SHELL
PATH:执行文件查找路径
export: 自定义变量转环境变量
locale:显示语系变量
read [-pt] 变量 读取键盘变量
-p:提示符
-t:等待秒数

$(( 20 + 5 * 6)):返回双括号内算数运算的结果。
expr命令是一款表达式计算工具,使用它完成表达式的求值操作。
eval会对后面的cmdLine进行两遍扫描,如果*遍扫描后,cmdLine是个普通命令,则执行此命令;
如果cmdLine中含有变量的间接引用,则保证间接引用的语义。
type:显示命令属性,不加参数时,会显示该命令是内置命令还是其他。
-t:显示命令属性缩写
file:代表外部命令。
alias:代表该命令为别名。
builtin:内置命令。
-p:为外部命令时,会显示命令所在的文件
-a:将PATH中设置的所有的与命令名相关的列出来
echo [-ne][字符串]
-n 不要在*后自动换行
-e 若字符串中出现以下字符,则特别加以处理
\a 发出警告;
\b 删除前一个字符;
\c 不产生进一步输出 (\c 后面的字符不会输出);
\f 换行但光标仍旧停留在原来的位置;
\n 换行且光标移至行首;
\r 光标移至行首,但不换行;
\t 插入tab;
\v 与\f相同;
\\ 插入\字符;
\nnn 插入 nnn(八进制)所代表的ASCII字符;
用echo命令打印带有色彩的文字:
文字色:
echo -e “\e[1;31mThis is red text\e[0m”
\e[1;31m 将颜色设置为红色
\e[0m 将颜色重新置回
颜色码:重置=0,黑色=30,红色=31,绿色=32,黄色=33,蓝色=34,洋红=35,青色=36,白色=37
背景色:
echo -e “\e[1;42mGreed Background\e[0m”
颜色码:重置=0,黑色=40,红色=41,绿色=42,黄色=43,蓝色=44,洋红=45,青色=46,白色=47
文字闪动:
echo -e “\033[37;31;5mMySQL Server Stop…\033[39;49;0m”
红色数字处还有其他数字参数:0 关闭所有属性、1 设置高亮度(加粗)、4 下划线、5 闪烁、7 反显、8 消隐
COMMENT’

#
# if [ true ]; then
# :
# fi

#${VAR:=DEFAULT}
: ${VAR:=DEFAULT}
echo $VAR

: ‘COMMENT
1. Shell变量默认为字符串。shell不关心这个串是什么含义。
2. Shell默认的数值运算是整数类型。所以若要进行数学运算,必须使用一些命令例如declare、expr、双括号等。
3. Shell变量可分为两类:
i. 局部变量:只在创建它们的shell中可用。在函数内定义,函数执行后就被删除。
ii. 环境变量:可以在创建它们的shell及其派生出来的任意子进程中使用。在整个脚本执行期间,只要没有被删除就一直存在。
3. 定义规则:变量名必须以字母或下划线字符开头。其余的字符可以是字母、数字(0~9)或下划线字符。任何其他的字符都标志着变量名的终止。
小写敏感。
4. 给变量赋值时,等号周围不能有任何空白符。
5. 通常大写字符为系统默认变量。个人习惯。
6. set:查看所有变量(含环境变量与自定义变量),以及设置shell变量的新变量值。
-a:标示已修改的变量,以供输出至环境变量。
-b:使被中止的后台程序立刻回报执行状态。
-e:若指令传回值不等于0,则立即退出shell。
-f:取消使用通配符。
-h:自动记录函数的所在位置。
-H Shell:可利用”!”加<指令编号>的方式来执行history中记录的指令。
-k:指令所给的参数都会被视为此指令的环境变量。
-l:记录for循环的变量名称。
-m:使用监视模式。
-n:只读取指令,而不实际执行。
-p:启动优先顺序模式。
-P:启动-P参数后,执行指令时,会以实际的文件或目录来取代符号连接。
-t:执行完随后的指令,即退出shell。
-u:当执行时使用到未定义过的变量,则显示错误信息。
-v:显示shell所读取的输入值。
-x:执行指令后,会先显示该指令及所下的参数。
7. declare/typeset [-aixrp] 变量
-a 将变量定义成数组
-i 将变量定义成整数
-x 将变量定义成环境变量
-r 将变量定义成readonly
-p:显示变量定义的方式和值
+:取消变量属性,但是 +a 和 +r 无效,无法删除数组和只读属性,可以使用unset删除数组,但是 unset 不能删除只读变量。
8. local关键字,用来在作用域内创建变量。出来作用域被销毁。
9. export为shell变量或函数设置导出属性,成为环境变量。无法对未定义的函数添加导出属性。同时,重要的一点是,export的效力仅及于该次登陆操作。
注销或者重新开一个窗口,export命令给出的环境变量都不存在了。
-f:代表[变量名称]为函数名称。。
-n:删除变量的导出属性。变量实际上并未删除,只是不会输出到后续指令的执行环境中。
-p:显示全部拥有导出属性的变量。
-pf:显示全部拥有导出属性的函数。
-nf:删除函数的导出属性。
–:在它之后的选项无效。
10. 通配符
*:匹配任意字符串,包括空字符串,不包含对“/”字符的匹配。
?:匹配任意单个字符,不能匹配“/”字符。
[abc]:匹配“a”或者“b”或者“c”字符。
[^abc]:不匹配“a”或者“b”或者“c”字符。
[a-z]:匹配26个英文小写字符中任意一个。
用set命令可以查看所有的变量,unset var命令可以清除变量var,var相当于没有定义过。
readonly var可以把var变为只读变量,定义之后不能对var进行任何更改。

COMMENT’

if [[ -n $IS_ZSH ]]; then
# 根据变量属性强制转换值的英文大小写。
declare -u uc_var=’abc’
declare -l lc_var=’ABC’
# 显示’ABC abc’;
echo “${uc_var} ${lc_var}”
fi

# 执行后显示7,注意空格。
# expr 3 + 4
# result=`expr 2 + 3`
# echo $result

# # 没有指定整型属性,输出为字符串’a+b’。
# declare a=3 b=4 c
# c=a+b
# # a+b
# echo ${c}
# declare -p a

# # 不过可以使用以下方式赋值。
# c=$((a+b))
# # 7
# echo ${c}

# 设置了整型属性就可以直接加了。
# declare -i a=3 b=4 c
# c=a+b
# # 7
# echo ${c}
# declare -p a

# declare -p VARIABLE

# # 定义函数内的全局变量
# function test() {
# declare testA=3
# VARIABLE=”Value”
# local testB=3
# # 让我们查看它们的属性。
# declare -p testA VARIABLE testB
# }
# # 执行函数。
# test
# #Value
# echo $testA $VARIABLE $testB

# export a b=3
# # 当然也可以先定义后增加导出属性
# export VARIABLE
# 删除变量的导出属性
# export -n a b
# function func_1(){ echo ‘123’; }
# # 为已定义函数增加导出属性
# export -f func_1
# # 删除函数的导出属性
# export -fn func_1

# set 11 22 33 44
# # 44
# echo $4
# # $4
# echo “\$$#”
# # 44 *遍得到变量个数4,第二遍去第四个
# eval echo “\$$#”

# declare mylove=’Cat’ #定义新变量
# # env | grep mylove mylove=Cat
# set -a mylove #设置为环境变量

# echo “./*.sh”
# echo “./*”.sh
# MMM=`echo “./*.sh”`
# echo $MMM
# echo `echo “./*.sh”`
# echo ./*.sh
# set — “./”*.sh
# echo “$1″

: ‘COMMENT
数组:
var[1]=”var1”
var[2]=1
COMMENT’

# source Shell.sh
export VARIABLE

unset VARIABLE
echo $VARIABLE

: ‘COMMENT
运行方式:
1. sh:
使用$ sh script.sh执行脚本时,当前shell是父进程,生成一个子shell进程,在子shell中执行脚本。
脚本执行完毕,退出子shell,回到当前shell。$ ./script.sh与$ sh script.sh等效。
2. source:
使用$ source script.sh方式,在当前上下文中执行脚本,不会生成新的进程。脚本执行完毕,回到当前shell。
$ . script.sh与$ source script.sh等效。
3. exec方式:
使用exec command方式,会用command进程替换当前shell进程,并且保持PID不变。
执行完毕,直接退出,不回到之前的shell环境。
COMMENT’

: ‘COMMENT
参数扩展:通过符号$获得参数中存储的值。
1. 间接参数扩展${!parameter},,zsh不支持
i. ${parameter-string}:当parameter未设置则替换成string,不更改parameter值。否则,不做处理。
ii. ${parameter=string}:当parameter未设置则替换成string,更改parameter值。否则,不做处理。
iii. ${parameter?string}:parameter没有设置,则把string输出到标准错误中。否则,不做处理。
iiii. ${parameter+string}:当parameter为空的时替换成string。否则,不做处理。
2. 冒号后面跟 等号,加号,减号,问号(⚠不能有空格):
i. ${parameter:-string}:当parameter未设置或者为空则替换成string,不更改parameter值。
ii. ${parameter:=string}:当parameter未设置或者为空则替换成string,更改parameter值。
iii. ${parameter:?string}:若变量parameter不为空,则使用变量parameter的值。
若为空,则把string输出到标准错误中,并从脚本中退出。
iiii. ${parameter:+string}:当parameter不为空的时替换成string。若为空时则不替换或者说是替换空值。
3. 子串扩展:${parameter:offset}和${parameter:offset:length}。
从offset位置开始截取长度为length的子串,如果没有提供length,则是从offset开始到结尾。
i. offset可以是负值,且必须与冒号有间隔或者用()包裹。开始位置是从字符串末尾开始算起,然后取长度为length的子串。
例如,-1代表是从*后一个字符开始。
ii. parameter是@,也就是所有的位置参数时,offset必须从1开始。
4. 替换:${parameter/pattern/string}、${parameter//pattern/string}、${parameter/pattern}和${parameter//pattern}。
大小写敏感。string为空时,则相当于将匹配的子串删除。 parameter之后如果是/,则只匹配遇到的*个子串;
parameter之后如果是//,则匹配所有的子串。
5. 删除:${parameter#pattern}、${parameter##pattern}、${parameter%pattern}和${parameter%%pattern}。
i. # 是去掉左边,% 是去掉右边。单一符号是*小匹配﹔两个符号是*大匹配。
6. 参数长度:${#parameter}
COMMENT’

: ‘COMMENT
标准输入(stdin):代码为0,使用<或<<;
标准输出(stdout):代码为1,使用>或>>;
标准错误输出(stderr):代码为2,使用2>或2>>;
1> 以覆盖的方式将正确的数据输出到指定到文件或设备;
1>> 以累加到方法将正确到数据输出到指定到文件或者设备上;
2> 以覆盖的方式将错误的数据输出到指定到文件或设备;
2>> 以累加的方式将错误的数据输出到指定到文件或设备;
2>/dev/null 将错误到数据丢弃,只显示正确到数据
2>&1 或者 &>将正确到数据和错误到数据写入同一个文件
cmd;cmd 不考虑命令相关性,连续执行。
当前一个命令执行成功会回传一个 $?=0的值。
cmd1 && cmd2 如果*个命令的$?为0,则执行第二个命令。
cmd1 || cmd2 如果*个命令的$?为0,则不执行第二个命令。否则执行第二个命令。
|:管道仅能处理前面一个命令传来的正确信息,将正确信息作为stdin传给下一个命令
– :stdin和stdout利用减号“-“来代替
COMMENT’

: ‘COMMENT
sh [-nvx] scripts.h
-n:不执行,仅检查语法。
-v:在执行脚本之前,先将脚本内容输出。
-x:将使用到的脚本内容,输出。
COMMENT’

: ‘COMMENT
当条件成立,就进行循环:
while [ condation ] #判断条件
do #循环开始
程序
done #循环结束
当条件成立,就终止循环:
until [ condation ] #判断条件
do #循环开始
程序
done #循环结束
按照指定次数循环:
for var in con1 con2 con3 …
do
程序
done
for (( 初始值; 限制值; 执行步长 ))
do
程序
done
COMMENT’

: ‘COMMENT
多分支语句判断
除*后一个分支外(这个分支可以是普通分支,也可以是*)分支),其它的每个分支都必须以;;结尾,;;代表一个分支的结束,不写的话会有语法错误。
*后一个分支可以写;;,也可以不写,因为无论如何,执行到 esac 都会结束整个 case in 语句。
case $变量 in
“*个变量内容”)
程序
;; #结束
*) # 用来托底,没有匹配到数据
;;
esac
COMMENT’

: ‘COMMENT
一个条件判断:
if [ condation ]; then
成立
else
不成立
fi
多条件判断:
if [ condation ]; then
成立
elif [ condation ]; then
成立
else
不成立
fi
COMMENT’

: ‘COMMENT
shift:参数号码偏移。会移动变量,可以接数字,代表移动前面几个参数的意思
COMMENT’

: ‘COMMENT
[]:判断符号,两个等号和一个等号,效果类似。
1. 中括号里面的每个组件都需要空格分隔。
2. 中括号的变量,使用双引号
3. 中括号的常量,使用单引号或双引号
COMMENT’

: ‘COMMENT
test命令测试:
1. test n1 -eq n2:
-eq:相等
-ne:不等
-gt:大于
-lt:小于
-ge:大于等于
-le:小于等于
2. 字符串判断
-z string:判断string是否为0,为空,则为true。
-n string:判断string是否非0,为空,则为false。
string1 = string2:字符串是否相等,相等为true。
string1 != string2:字符串是否不等,相等为false。
3. 多重条件判断
-a:两个条件同时成立,为true。
-o:两个条件任何一个成立,为true。
!:反向。
4. 文件类型判断
-e:文件名是否存在。
-f:该文件名是否存在且是否为文件。
-d:该名称是否存在且为目录。
-L:该名称是否存在且是否为链接文件。
5. 文件权限检测
-r:是否存在是否有可读权限。
-w:是否存在是否有可写权限。
-x:是否存在是否有可执行权限。
-s:是否存在且为非空白文件。
6. 两文件比较
-nt 文件1是否比文件2新。
-ot 文件1是否比文件2旧。
-ef 文件1和文件2是否为同一个文件。
COMMENT’

# # Some string
# echo “${!VALUE}”

# #
# echo “${FOO-“Cat-“}”
# # Cat-
# echo “${FOO=”Cat-“}”
# #
# echo “${FOO+”Cat-“}”
# # Cat-
# echo “${FOO?”Cat—-“}”

# # Cat-
# echo “${FOO:-“Cat-“}”
# # Cat=
# echo “${FOO:=”Cat=”}”
# # Cat=
# echo “${FOO:?”Cat?”}”
# #Cat+
# echo “${FOO:+”Cat+”}”

# #Cat\
# echo “${LONG_STRING:5}”
# #Cat
# echo “${LONG_STRING:5:3}”
# #Cat
# echo “${LONG_STRING: -4:3}”
# #Cat
# echo “${LONG_STRING: -4:3}”
# #Cat
# echo “${LONG_STRING:(-4):3}”

# # I am Cat!CAT!Cat!Cat
# echo “${LONG_LONG_STRING/cat}”
# # I am LGCat!CAT!LGCat!LGCat
# echo “${LONG_LONG_STRING//Cat/LGCat}”

# # am Cat!CAT!Cat!Cat
# echo “${LONG_LONG_STRING#* }”
# # am Cat!CAT!Cat!Cat
# echo “${LONG_LONG_STRING#? }”

# # at!CAT!Cat!Cat
# echo “${LONG_LONG_STRING#*[Cc]}”

# if [[ -n $IS_ZSH ]]; then
# # Cat!CAT!Cat!Cat
# echo “${LONG_LONG_STRING#*(AT|m)}”
# echo “—–${LONG_LONG_STRING#*(AT|m)}”
# echo “—–${LONG_LONG_STRING#[A-z]**(AT|m)}”
# # !Cat!Cat
# echo “—–${LONG_LONG_STRING#*(AT|mm)}”
# fi

# # m Cat!CAT!Cat!Cat
# echo “${LONG_LONG_STRING#*[a-t]}”
# # m Cat!CAT!Cat!Cat
# echo “${LONG_LONG_STRING#*[a-t]}”
# # am Cat!CAT!Cat!Cat
# echo “${LONG_LONG_STRING#*[^A-Z]}”

# echo `echo “./*.sh”`
# echo `echo “./”*.sh`

# # Cat!CAT!Cat!Cat
# echo “${LONG_LONG_STRING##* }”
# # at
# echo “${LONG_LONG_STRING##*[Cc]}”
# #
# echo “${LONG_LONG_STRING##*[a-t]}”

# # I am
# echo “${LONG_LONG_STRING% *}”
# # I
# echo “${LONG_LONG_STRING%% *}”

# # 20
# echo “${#LONG_LONG_STRING}”

# # i am cat!cat!cat!cat
# echo “$(echo “${LONG_LONG_STRING}” | tr “[:upper:]” “[:lower:]”)”
# # I AM CAT!CAT!CAT!CAT
# echo “$(echo “${LONG_LONG_STRING}” | tr “[:lower:]” “[:upper:]”)”

# if [[ -n $IS_ZSH ]]; then
# # I AM CAT!CAT!CAT!CAT
# echo “${LONG_LONG_STRING:u}”
# # i am cat!cat!cat!cat
# echo “${LONG_LONG_STRING:l}”
# fi

# : ${TEM:=”./”*.sh}
: ‘COMMENT
函数的声明形式:
function 函数名 {
函数体
}
function 函数名() {
函数体
}
函数名() {
函数体
}
1. 有funtion,可以不写(),没有function,必须写()。
2. 函数名和”{“之间必须有空格。
3. 不得声明形式参数。
4. 必须在调用函数地方之前,声明函数
5. 无法重载
6. 后来的声明会覆盖之前的声明
7. 没有返回值的函数,默认返回函数内*后一条指令的返回值。有返回值的函数,只能返回整数。
8. 需要获得函数值,只能通过$?获得。通过=获得是空值。

我们可以将shell中函数,看作是定义一个新的命令,因此各个输入参数直接用空格分隔。
一次,命令里面获得参数方法可以通过:$0…$n得到。$0代表函数本身。
$#:传入的参数的个数。
$*:所有的位置参数(作为单个字符串)。
$@:所有的位置参数(每个都作为独立的字符串)。
$?:当前shell进程中,上一个命令的返回值,如果上一个命令成功执行则$?的值为0,否则为其他非零值。
$$:当前shell进程的pid。
$!:后台运行的*后一个进程的pid。
$-:显示shell使用的当前选项。
$_:之前命令的*后一个参数。
COMMENT’

# 无输出
# logic
# function DoWork {
# local LG_CAT=”LG_Cat”
# echo “logic”
# return 2
# }
# DoWork() {
# local LG_CAT=”LG_Cat”
# echo “logic”
# return 2
# }

# 无输出
# logic
# function DoWork {
# local LG_CAT=”LG_Cat”
# echo “logic”
# return 2
# }
# echo $LG_CAT
# echo `DoWork`
# 无输出
# logic
# function DoWork {
# LG_CAT=”LG_Cat”
# echo “logic”
# return 2
# }
# echo $LG_CAT
# echo `DoWork`
# logic
# 无输出
# function DoWork {
# LG_CAT=”LG_Cat”
# echo “logic”
# return 2
# }
# echo `DoWork`
# echo $LG_CAT
# logic
# logic
# LG_Cat
# function DoWork {
# LG_CAT=”LG_Cat”
# echo “logic”
# return 2
# }
# DoWork
# echo `DoWork`
# echo $LG_CAT
# logic
# 2
# logic
# 0
# function DoWork {
# LG_CAT=”LG_Cat”
# echo “logic”
# return 2
# }
# DoWork
# echo “$?”
# echo `DoWork`
# echo “$?”
# function DoWork {
# echo “特殊变量:\n
# \$#:$#\\n
# \$0:$0\\n
# \$1:$1\\n
# \$2:$2\\n
# \$*:$*\\n
# \$@:$@\\n
# \$$:$$\\n
# \$-:$-\\n
# \$_:$_
# ”
# return 2
# }
# DoWork “Cat” “LGCat”
array=($MMM 1 2)

echo $array

declare -p MMM

declare -p array
if [[ -n $IS_ZSH ]]; then
declare -A fruits=([‘apple’]=’red’ [‘banana’]=’yellow’)
# 显示所有关联数组。
declare -A
# red yellow
echo ${fruits[@]}
echo ${fruits[*]}
echo ${(@k)fruits}
fi

脚本实现:
while [[ $# -gt 0 ]]; do
case $1 in
-d|–directory)
shift
echo “$1—-”
shift
;;
-k|–keyword)
shift
echo “$1 —-”
shift
;;
-s|-source)
echo “$1 —-source”
shift
;;
-f|–framework)
shift
;;
-l|–lib)
shift
;;
-h|–help)
Show_Help
exit 0
;;
*)
echo “Unknown option: $1”
exit 1
esac
done
通过上面的脚本来学习脚本:

while do  case : 循环

[[ $# -gt 0 ]] :

$# ,获取执行脚本的时候传递的参数个数

-gt : 大于

$1: 循环的参数值

exit: 0 正常退出

其他exit: 为自定义意义。

 

自定义一个脚本说明文档

格式如下:

Show_Help() {
# 结束符
cat <<EOF

find_api.sh –directory <dir> 在指定目录指定文件内搜索指定关键字。

-d|–directory <dir> – 指定查找目录,默认当前所在目录
-k|–keyword <word> – 查找关键字
-s|–source – 指定查找源码文件
-f|–framework – 指定查找framework文件
-l|–lib – 指定查找libs文件
–help – prints help screen

EOF

}
注意EOF不要有空格。

cat <<EOF

中间写上脚本的使用文档

EOF

查找、细分查询功能
find . -name “*.framework”
find . -name “*.h” -exec echo {} \;
grep -E “some|weak” -A 1 -B 1 -i
nm -pa <mach-o>
find : 查找文件  -name ‘字串’  查找文件名匹配所给字串的所有文件,字串内可用通配符 *、?、[ ]。

grep: 搜索具体文件中的关键字   -E   –extended-regexp             # 将范本样式为延伸的普通表示法来使用,意味着使用能使用扩展正则表达式

 

数组与For循环
# <<< 表示将右侧的字符串传递到左侧命令的标准输入。
# < 将文件的内容传递到命令的标准输入
# << 将带有结束标志的文档内容传递到命令的标准输入

#test
#-e:文件名是否存在。
#-f:该文件名是否存在且是否为文件。
#-d:该名称是否存在且为目录。
#-L:该名称是否存在且是否为链接文件。

#read
#-a 后跟一个变量,该变量会被认为是个数组,然后给其赋值,默认是以空格为分割符。
#-r 屏蔽\,如果没有该选项,则\作为一个转义字符,有的话 \就是个正常的字符了。

# read通过输入重定向,把file的*行所有的内容赋值给变量line,循环体内的命令一般包含对变量line的处理;然后循环处理file的第二行、第三行。。。一直到file的*后一行。
#for是每次读取文件中一个以空格为分割符的字符串。
#for是每次读取字符串中一个以空格为分割符的字符串。
#weak|cat|Hank
Find_Api() {
local key_word=””
if [[ -n “${KEYWORD}” ]]; then
read -a names <<< “${KEYWORD}”
for name in ${names[@]}
do
if [[ ! -n “${key_word}” ]]; then
key_word=”$name”
else
key_word=”$key_word|$name”
fi
done
else
echo “请输入查找的关键字!”
exit 1
fi
echo “$key_word —-”
}
 

iOS开发之第三方登录微信– 史上*全*新第三方登录微信方式实现

*新版本的微信登录实现步骤实现:

1.在进行微信OAuth2.0授权登录接入之前,在微信开放平台注册开发者帐号,并拥有一个已审核通过的移动应用,并获得相应的AppID和AppSecret,申请微信登录且通过审核后,可开始接入流程。 地址: 点击打开链接

2. 下载*新的SDK   地址: 点击打开链接

%title插图%num

SDK内容如下:

%title插图%num

结构解析:

从上到下依次说明:

1. 静态库,直接拖入工程。

2. ready.text自己看

3. 授权SDK。

4. 登录方法所在类。

5.  一些常用的对象类。

iOS微信登录注意事项:

1、目前移动应用上微信登录只提供原生的登录方式,需要用户安装微信客户端才能配合使用。
2、对于Android应用,建议总是显示微信登录按钮,当用户手机没有安装微信客户端时,请引导用户下载安装微信客户端。
3、对于iOS应用,考虑到iOS应用商店审核指南中的相关规定,建议开发者接入微信登录时,先检测用户手机是否已安装微信客户端(使用sdk中isWXAppInstalled函数 ),对未安装的用户隐藏微信登录按钮,只提供其他登录方式(比如手机号注册登录、游客登录等)。

iOS微信登录大致流程:

1. 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;
2. 通过code参数加上AppID和AppSecret等,通过API换取access_token;
3. 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。

示意图:

%title插图%num

接下来就进入正题:

1.配置工程
1. 新建一个工程。
2. 把下载下来的sdk中的.h文件与静态库全部拖入工程。
3.  加入依赖库

%title插图%num

4.  URL – Types  (加入 appid)
target  –  Info – URL Types
%title插图%num

5. 白名单
当程序出现此错误
-canOpenURL: failed for URL: “weixin://app/wx5efead4057f98bc0/” – error: “This app is not allowed to query for scheme weixin”
就说明没有针对iOS9 增加白名单。 在info.plist文件中加入 LSApplicationQueriesSchemes

App Transport Security 这个是让程序还是用http进行请求。
LSApplicationQueriesSchemes 这个是增加微信的白名单。

%title插图%num

6.  现在编译应该是没有问题了。

2. 终于到令人兴奋的代码部分了。 直接上代码。
//
// AppDelegate.m
// weixinLoginDemo
//
// Created by 张国荣 on 16/6/20.
// Copyright © 2016年 BateOrganization. All rights reserved.
//

#import “AppDelegate.h”
#import “WXApi.h”

//微信开发者ID
#define URL_APPID @”app id”

@end

@implementation AppDelegate

– (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

//向微信注册应用。
[WXApi registerApp:URL_APPID withDescription:@”wechat”];
return YES;
}

-(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options{

/*! @brief 处理微信通过URL启动App时传递的数据
*
* 需要在 application:openURL:sourceApplication:annotation:或者application:handleOpenURL中调用。
* @param url 微信启动第三方应用时传递过来的URL
* @param delegate WXApiDelegate对象,用来接收微信触发的消息。
* @return 成功返回YES,失败返回NO。
*/

return [WXApi handleOpenURL:url delegate:self];
}

/*! 微信回调,不管是登录还是分享成功与否,都是走这个方法 @brief 发送一个sendReq后,收到微信的回应
*
* 收到一个来自微信的处理结果。调用一次sendReq后会收到onResp。
* 可能收到的处理结果有SendMessageToWXResp、SendAuthResp等。
* @param resp具体的回应内容,是自动释放的
*/
-(void) onResp:(BaseResp*)resp{
NSLog(@”resp %d”,resp.errCode);

/*
enum WXErrCode {
WXSuccess = 0, 成功
WXErrCodeCommon = -1, 普通错误类型
WXErrCodeUserCancel = -2, 用户点击取消并返回
WXErrCodeSentFail = -3, 发送失败
WXErrCodeAuthDeny = -4, 授权失败
WXErrCodeUnsupport = -5, 微信不支持
};
*/
if ([resp isKindOfClass:[SendAuthResp class]]) { //授权登录的类。
if (resp.errCode == 0) { //成功。
//这里处理回调的方法 。 通过代理吧对应的登录消息传送过去。
if ([_wxDelegate respondsToSelector:@selector(loginSuccessByCode:)]) {
SendAuthResp *resp2 = (SendAuthResp *)resp;
[_wxDelegate loginSuccessByCode:resp2.code];
}
}else{ //失败
NSLog(@”error %@”,resp.errStr);
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@”登录失败” message:[NSString stringWithFormat:@”reason : %@”,resp.errStr] delegate:self cancelButtonTitle:@”取消” otherButtonTitles:@”确定”, nil];
[alert show];
}
}
}

@end
下面是登录的类。

//
// ViewController.m
// weixinLoginDemo
//
// Created by 张国荣 on 16/6/20.
// Copyright © 2016年 BateOrganization. All rights reserved.
//

#import “ViewController.h”
#import “WXApi.h”
#import “AppDelegate.h”
//微信开发者ID
#define URL_APPID @”appid”
#define URL_SECRET @”app secret”
#import “AFNetworking.h”
@interface ViewController ()<WXDelegate>
{
AppDelegate *appdelegate;
}
@end

@implementation ViewController

– (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
#pragma mark 微信登录
– (IBAction)weixinLoginAction:(id)sender {

if ([WXApi isWXAppInstalled]) {
SendAuthReq *req = [[SendAuthReq alloc]init];
req.scope = @”snsapi_userinfo”;
req.openID = URL_APPID;
req.state = @”1245″;
appdelegate = [UIApplication sharedApplication].delegate;
appdelegate.wxDelegate = self;

[WXApi sendReq:req];
}else{
//把微信登录的按钮隐藏掉。
}
}
#pragma mark 微信登录回调。
-(void)loginSuccessByCode:(NSString *)code{
NSLog(@”code %@”,code);
__weak typeof(*&self) weakSelf = self;

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];//请求
manager.responseSerializer = [AFHTTPResponseSerializer serializer];//响应
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@”text/html”,@”application/json”, @”text/json”,@”text/plain”, nil];
//通过 appid secret 认证code . 来发送获取 access_token的请求
[manager GET:[NSString stringWithFormat:@”https://api.weixin.qq.com/sns/oauth2/access_token?appid=%@&secret=%@&code=%@&grant_type=authorization_code”,URL_APPID,URL_SECRET,code] parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {

} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { //获得access_token,然后根据access_token获取用户信息请求。

NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:nil];
NSLog(@”dic %@”,dic);

/*
access_token 接口调用凭证
expires_in access_token接口调用凭证超时时间,单位(秒)
refresh_token 用户刷新access_token
openid 授权用户唯一标识
scope 用户授权的作用域,使用逗号(,)分隔
unionid 当且仅当该移动应用已获得该用户的userinfo授权时,才会出现该字段
*/
NSString* accessToken=[dic valueForKey:@”access_token”];
NSString* openID=[dic valueForKey:@”openid”];
[weakSelf requestUserInfoByToken:accessToken andOpenid:openID];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@”error %@”,error.localizedFailureReason);
}];

}

-(void)requestUserInfoByToken:(NSString *)token andOpenid:(NSString *)openID{

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager GET:[NSString stringWithFormat:@”https://api.weixin.qq.com/sns/userinfo?access_token=%@&openid=%@”,token,openID] parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {

} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSDictionary *dic = (NSDictionary *)[NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:nil];
NSLog(@”dic ==== %@”,dic);

} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@”error %ld”,(long)error.code);
}];
}

– (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}

@end

大功告成。

 

Mac系统上使用SnailSVN的配置步骤

1·在Apple Store下载SvnSnail: Lite版本(需输入apple id)
2·打开SnailSVN
3·点击General,打开系统设置(Open System Preference),勾上SnailSVNLite: Finder Extensions
4·点击SVN Settings,选择paths
5·创建.ssh路径,先打开终端Terminal,然后输入 mkdir .ssh,创建完成后就可以paths中选择你刚刚创建的.ssh,再依次选择bin以及Applications路径。/Users/xxx/.ssh, /urs/local/bin, /Applications
6·新建一个文件夹名为 svn-workspace 作为工作路径
7·点击SnailSVN Lite左上角的File->Svn Checkout,输入项目的svn地址,检出项目的路径到 svn-workspace,点击确定,弹出验证框,输入svn帐号密码即可。
8·结束