iOS 数组与字符串的相互转化

1.字符串转化为数组
NSString *string = @”a,b,c,d”;
NSArray *array = [string componentsSeparatedByString:@”,”];
2.数组转化为字符串
NSArray *array = @[@”a”,@”b”,@”c”,@”d”];
NSString *string = [array componentsJoinedByString:@”,”];
相互转化得看元素之间是以什么符号分隔开的,就以什么符号拼接,上面的逗号 ” ,“ ,也可以换成点 “ . ” 或者顿号 ” 、“

大家都用用的哪里的国外服务器,求推荐

同主题名,谢谢啦
题名 服务器 谢谢 国外6 条回复 • 2016-12-15 12:11:53 +08:00
tonghuashuai 1
tonghuashuai 2016-08-23 09:21:57 +08:00
conoha
wangzhangwei 2
wangzhangwei 2016-08-23 13:21:02 +08:00
日本东京 美车达拉斯
duola 3
duola 2016-08-25 00:27:04 +08:00
搬瓦工,感觉国外的都不太行。
dreamcountry 4
dreamcountry 2016-09-28 08:50:18 +08:00
鉴于 vultr 在国内的良好表现, vultr 的母公司 choopa 提供的独立服务器在地理位置、价格、服务上也应当不错。
Choopa 独服机房位置:
Piscataway, New Jersey ( 10,000+ server capacity )
洛杉矶( Los Angeles, California , 5,000+ server capacity )
阿姆斯特丹( Amsterdam, Netherlands , 5,000+ server capacity )
东京( Tokyo, Japan , 2,000+ server capacity )
参见: http://choopa.youhuima.cc/
caogen9 5
caogen9 2016-11-22 14:40:55 +08:00
美国洛杉矶的
Devmingwang 6
Devmingwang 2016-12-15 12:11:53 +08:00 via Android
我在用 digitalocean 和 vultr ,这两家服务上和价格上我都还算满意。

关于项目部署的负载均衡、数据库主从等问题

访问说明如下:

1 、 互联网用户通过网络访问 Web 服务器, Web 服务器 1 和 Web 服务器 2 实现负载均衡,应对高峰值访问,保证系统运行稳定;

2 、 部署防火墙及交换机设备,保证 Web 服务器与管理服务器之间数据交互安全;

3 、 部署 2 台管理服务器实现数据同步及双机热备,如管理服务器因故障 down 机,可切换至备份服务器保证系统继续稳定运行;

4 、 部署 2 台数据库服务器实现数据同步及双机热备,如数据库服务器因故障 down 机,可切换至备份服务器保证系统继续稳定运行。


如上图文所示;小弟有如下几个问题

1 、这 6 台服务器应该都是在一个内网当中?或者说是交换机下 4 台才是在一个内网当中,外网无法直接访问吧?

2 、 Web 服务器 1 、 2 应该是用 Nginx 负载均衡实现吗?

3 、防火墙应该用 Linux 的 iptables 来实现吗?如限制 3306 端口只能 Web1 、 2 服务器的指定 IP 访问?

4 、请问“管理服务器“是什么?怎么理解? Web 后台吗?为什么它也要双机热备,数据库主从不就可以了么?

5 、数据库是用的 MySQL ,这里应该是指主从吧?备份容易,那么如何做到主动切换


6 条回复    2016-10-18 09:35:56 +08:00
tomczhen
    1

tomczhen   2016-10-17 22:52:31 +08:00   ❤️ 1

恕我直言,大多数需要开发 /运维来发帖问这种问题的公司都不需要这么复杂的架构。

可靠性集群( HA )和负载均衡集群是两码事(虽然有时也能同时实现)。

Web Server 可以通过网络层做负载均衡集群也可以通过应用层做。

数据库这块,不管你使用哪种可靠性集群或者负载均衡方案都是要在某些方面付出一些代价的,这块的技术方案选型要根据实际业务要求来做。

Jat001
    2

Jat001   2016-10-17 23:11:21 +08:00

也恕我直言,你招个能搞定这事的运维不如直接用各种云服务省钱,也省事。
mytsing520
    3

mytsing520   2016-10-18 01:39:59 +08:00

恕我直言,直接上云省事
Infernalzero
    4

Infernalzero   2016-10-18 09:28:46 +08:00   ❤️ 1

这种架构也能说成复杂也是逗,而且明显是传统行业的架构
1.Y
2.架构图里根本没画出 LB ,用 nginx 当然可以
3.iptables 是可以,但是图中应该是指专门的防火墙设备,但只是为了限制数据库访问的话根本不需要加什么防火墙, mysql 配置设置好 ip,限制内网访问就行了
5.主从复制一般是用来做读写分离的,非要用来做备份虽然也可以但是意义不大,只是从库不能设置为 readonly,或者设置为 readonly 通过脚本事件触发切换,要做主动切换也是很简单的, keepalived 把两台 mysql 做 HA 就可以了
mhycy
    5

mhycy   2016-10-18 09:29:57 +08:00

恕我直言,架构图已经跑偏了
mhycy
    6

mhycy   2016-10-18 09:35:56 +08:00   ❤️ 1

补充:
防火墙作用应该是限制端口访问且记录 IP 并查找分析出问题流量并予以拦截。
这类设备建议上硬件设备,开源方案暂时没见到有好选择。
限制端口访问一个外网路由 NAT 即可实现,不一定要防火墙,但后一个需求难以满足。
理想状态下,内网两台服务器应直接应答来自电信与联通双网络数据,并利用 LVS 等方案实现虚 IP 热备。
至于内网那一批服务器,二层交换机直接配 ACL 限制即可, IPTABLE 限制访问也是可以的。

求问每隔五秒有 4k 用户轮询该用什么配置的服务器

每个用户请求就会 UPDATE 一下 mysql 数据库。。 如果只有一台服务器,求问各位 V 友至少要什么配置

33 条回复    2016-11-21 14:13:46 +08:00
abelyao
    1

abelyao   2016-11-19 22:08:42 +08:00

4000 请求是同时产生还是会有错开一两秒? update 的 sql 复杂不?单次执行时间是多少? mysql 是也在本地吗?
prondtoo
    2

prondtoo   2016-11-19 22:15:04 +08:00

刷单么?
ipconfiger
    3

ipconfiger   2016-11-19 22:16:05 +08:00

这个量级基本上大多数服务器都很轻松能扛下来嘛, Linux 修改一下 open file 的 limit 就好了
powergx
    4

powergx   2016-11-19 22:16:56 +08:00 via iPhone

intel 3700 ssd 一块搞定,为了安全*好 raid1
qq915458022
    5

qq915458022   2016-11-19 22:45:35 +08:00

@abelyao 会错开一两秒。。 sql 非常简单,就是基本的 Update 一格。 mysql 在本地,服务器还没租所以也不知道时间。。
我主要是担心 cpu 吃不消
一般这种 CPU 和内存该配多大啊?
qq915458022
    6

qq915458022   2016-11-19 22:46:10 +08:00

@ipconfiger 谢谢了,那我租阿里 2 核 2G 够吗?
qq915458022
    7

qq915458022   2016-11-19 22:46:45 +08:00

@powergx 租服务器
qq915458022
    8

qq915458022   2016-11-19 22:47:50 +08:00

@prondtoo 刷单的话服务器的承载能力就不是我考虑的了?
powergx
    9

powergx   2016-11-19 22:54:38 +08:00 via iPhone

@qq915458022 阿里的磁盘 大概几十 ipos ,你要么放到 memcache /redis ,要么独立开数据库服务
txlty
    10

txlty   2016-11-19 22:56:16 +08:00

*好优化下架构,把这个 update 缓进内存。

ipconfiger
    11

ipconfiger   2016-11-19 23:07:19 +08:00

@qq915458022 *好用 SSD 的 VPS, 不然 MySQL 装本地性能堪忧.

另外如果每次请求都有一次 update 的请求的话, 那基本就是在考 mysql 的性能了, 加缓存什么的治标不治本, 除非你的服务请求的频度是根据时间还有不同, 分了峰值和谷值的.

有很多情况还不清楚, 所以暂时没法给你进一步的建议

qq915458022
    12

qq915458022   2016-11-19 23:16:14 +08:00

@ipconfiger 使用阿里的独立数据库呢?
qq915458022
    13

qq915458022   2016-11-19 23:16:56 +08:00

@powergx 我也觉得独立数据库比较可行,但是阿里的独立数据库是内网的吗?网络延迟如何?
ipconfiger
    14

ipconfiger   2016-11-19 23:22:09 +08:00

@qq915458022 看你独立数据库服务器选的等级咯, 我觉得你还是先从访问模式的分析开始比较好, 你的业务什么访问模式都不清晰的情况下盲目做架构基本都跟找死没区别, 除非钱多, 用服务器来堆
moult
    15

moult   2016-11-19 23:30:41 +08:00

1 、如果更新的是同一行记录的话,或者就那么几行的话,可以用 Redis 缓存一下更新请求,然后汇总之后放到 SQL 上面。
2 、如果更新记录不固定的话,可以在给更新请求加个队列,把 4K 的请求分散到 5 秒,不就是 800QPS 了。
shiny
    16

shiny   2016-11-19 23:33:09 +08:00

擦车做的好,低配机器轻松扛下来。做得不好,主要看 io 的。
qq915458022
    17

qq915458022   2016-11-19 23:57:13 +08:00

@ipconfiger 软件性质比较敏感,不好细说。。
但也就是很简单的用 php update 一下
源码: http://ofhr82r8c.bkt.clouddn.com/%E6%97%A0%E6%A0%87%E9%A2%98.png
qq915458022
    18

qq915458022   2016-11-19 23:59:02 +08:00

@moult 那就直接开一个 redis 的数据库应该就可以吧?
ipconfiger
    19

ipconfiger   2016-11-20 00:06:05 +08:00

@qq915458022 我是说的访问模式, 比如你说的每 5 秒陆续会产生 4K 次请求, 那么是一直都是这个频度还是峰值是这个频度然后会有一段时间没有这么高的频度
qq915458022
    20

qq915458022   2016-11-20 00:07:26 +08:00 via iPhone

@ipconfiger 一直保持
billlee
    21

billlee   2016-11-20 00:36:14 +08:00

如果均匀分布的,那么 IOPS 是 800 s^-1, 如果要持久化到磁盘就必须要用 SSD.
如果可以放弃持久性要求,可以用 redis 或者把 innodb_flush_log_at_trx_commit 设置成 0.
qq915458022
    22

qq915458022   2016-11-20 00:39:45 +08:00 via iPhone

@billlee 我先上个 ssd 试试吧
实在不行就准备弄分布式了?
ipconfiger
    23

ipconfiger   2016-11-20 00:54:21 +08:00

@qq915458022 如果一直是这个频度的话, 基本上压力都是在数据库上了, 前端加什么都不好使, 先上个阿里云的 RDS 自己测一下 TPS, 能超过 1000 就 ok, 可以从*小规格的开始实验.
我在网上找到一个评测文章你可以参考一下:
http://chuansong.me/n/2057150

另外, 如果全是 update, 而且 MySQL 测试出来不 满足 1000 的 TPS, 比如只有 200(原来年幼无知的时候在阿里云的*挫的 VPS 上本机装 mysql 测出来的结果). 那么可以用 redis 来做个对列, 如果光用对列是没用的, 因为写入数据库的频度始终比来数据的小, 所以对列会一直堆积耗光内存, 我原来做的对列是可以合并 update 请求的, 比如用 update 一个计数作为例子, 如果发现前面有未执行的 update 的值, 就把几次请求合并成一次数据库写入, 这样子数据库就不需要那么高的 TPS 了.

qq915458022
    24

qq915458022   2016-11-20 01:14:16 +08:00 via iPhone

@ipconfiger 因为数据是实时的而且要能在后台实时反应出来。。如果用内存积压的方法还要再从内存中读出来,感觉很麻烦。

目前配置可以往上堆,但是日后用户可能还要增长几倍(现在是测试小组 4000 人?),又只有一台服务器,所以有点伤感?

qq915458022
    25

qq915458022   2016-11-20 01:16:01 +08:00 via iPhone

@ipconfiger 其实也可以考虑合并呢。。隔 5s update 一次也会缓解很多,而且这下只考内存了
billlee
    26

billlee   2016-11-20 01:25:18 +08:00

@ipconfiger 其实不需要在逻辑上对 UPDATE 请求做合并,只要把一串 UPDATE 放到一个 transaction 里去执行,速度就会快很多了吧,瓶颈一般是每次提交事务时 flush redo log 产生的 IO 操作。
fuxkcsdn
    27

fuxkcsdn   2016-11-20 12:22:11 +08:00   ❤️ 1

1 , SELECT * FROM userstatus 改掉,只取要用到的字段,还有,如果 phone 字段不是主键或者没有唯一索引的话, SQL 语句后面加上 LIMIT 1 (除非你的业务逻辑存在取多条记录的可能)
2 , UPDATE 语句打印 explain 出来看看(需要 MySQL 5.6 以上),可能的话把 UPDATE 语句完整的贴出来

BTW ,可以 isset($_GET[‘phone’], $_GET[‘sim’], $_GET[‘group’]) && is_numeric($_GET[‘phone’]) && is_numeric($_GET[‘sim’]) && ….

billwang
    28

billwang   2016-11-20 12:32:20 +08:00

一般来说,试一下,扛得住就行,看不住再升级。:)
ipconfiger
    29

ipconfiger   2016-11-20 12:56:05 +08:00

@billlee 因为逻辑上来说只需要同步*终状态所以合并处理是*优的, 这个方案是原来做游戏服务器的时候用的, 主要的数据操作都在内存完成, 往数据库存只是为了持久化, 那么其实只需要持久化*终状态即可
kaneg
    30

kaneg   2016-11-20 13:53:21 +08:00   ❤️ 1

看了楼主发的代码截图,其实就是在数据库中持久化以 phone 为 key , owner 和 sim 为 value 的一个 map ,而这个 map 的大小为 4k 。所以*简单的办法就是在内存中保持这个 map ,新来的请求就只是更新这个 map 。而这个 map 多长时间刷新到数据库就看你的数据库的压力承受能力。

如果要读取用户的状态,只要先在内存 map 中查,能查到这就是*新状态,查不到再在数据库中读取。

这样下来,数据库的 IO 压力是可调整的。而能不能抗得住 5 秒内 4k 的 http 请求主要看服务器的 CPU 了。

quericy
    31

quericy   2016-11-20 14:35:12 +08:00   ❤️ 1

17 楼是源码么…
单用 is_numeric 过滤,可以被 16 进制绕过,某些情况下可以注入的…
goodryb
    32

goodryb   2016-11-20 15:32:17 +08:00

30 说的有道理,不过不想这么复杂的话,还是建议用 RDS 测试一下,先买个按量的实例,扛不住了就换个高规格的,没问题之后再买个包年包月的
当然了,省事就是费钱,量上来之后不行买高规格就搭配 redis 做缓存,没必要这么纠结
qq915458022
    33

qq915458022   2016-11-21 14:13:46 +08:00 via iPhone

@quericy 谢谢提醒,已经换成 is_int

服务器域名同步问题求解决方案

我有几个台服务器,经常被打,我想了一个方案,买多台服务器,利用 dns 的轮询,一直轮流换 IP ,打死就换。但是问题来了。我买了多台服务器。但是如果我在一台机子上添加域名,其他机子如何才能同步添加域名。我使用的是 windows server 2008 请问有好的解决方案吗?

域名 服务器 机子 轮询16 条回复 • 2016-11-29 12:02:45 +08:00
bdbai 1
bdbai 2016-11-27 00:07:41 +08:00 via Android
不用 CDN ?
Kilerd 2
Kilerd 2016-11-27 00:08:40 +08:00 via iPhone
那还不如直接多机子做负载均衡
jarlyyn 3
jarlyyn 2016-11-27 00:41:00 +08:00
多域名是什么鬼……

被打前端加负载均衡或者 cdn 啊。
hanmeimei 4
hanmeimei 2016-11-27 00:42:33 +08:00 via Android
@bdbai cdn 不太懂 我去多多了解一下
hanmeimei 5
hanmeimei 2016-11-27 00:43:41 +08:00 via Android
@Kilerd 就是做负载均衡。。如何多个机子同步全部数据呢
techmoe 6
techmoe 2016-11-27 00:55:13 +08:00 via Android
还是考虑 cdn 吧,翻来覆去被打机子再多负载均衡做的再好不早晚也被人打死
hanmeimei 7
hanmeimei 2016-11-27 01:29:25 +08:00 via Android
@techmoe 但是没有备案,国内不能用 cdn 吧
iA7489 8
iA7489 2016-11-27 01:39:47 +08:00 via iPhone
加钱上 cloudflare
SharkIng 9
SharkIng 2016-11-27 05:47:31 +08:00 via iPhone
没懂为什么要在不同机子上做 dns ,你是做 dns 服务器吗?

如果只是网站啥的,同步网站内容 (例如 rsync )然后用带负载均衡的解析就行(记得 dnspod 就行好像)
ryd994 10
ryd994 2016-11-27 06:02:18 +08:00 via Android
被打是怎么打? 4 层还是 7 层?是 cc 还是 flooding ?如果不涉及应用层的话可以用一堆反代。涉及应用层的话也可以利用反代限制频率。然而一堆反代还不如买个 CDN

manhere 11
manhere 2016-11-27 08:05:31 +08:00 via iPhone
这种防范方法不过是加的多死的多而已啊?
lhbc 12
lhbc 2016-11-27 08:44:11 +08:00 via iPhone
攻击流量的调度速度比 DNS 调度快多了。
xzem 13
xzem 2016-11-27 11:03:05 +08:00
楼主是想同步 vhost 记录吗?
hanmeimei 14
hanmeimei 2016-11-27 18:36:51 +08:00
@xzem 是的并且同步启动,更好也是
nanjishidu 15
nanjishidu 2016-11-28 08:55:28 +08:00 via iPhone
通过脚本配置吧
hanmeimei 16
hanmeimei 2016-11-29 12:02:45 +08:00 via Android
@nanjishidu 有现成的脚本没有,不懂如何实现

用 Let’s Encrypt 加密,如果有多台 reverse proxy,那怎么得到证书呢?

以前只有一台前端 proxy 时,官方认证工具我都是装在 proxy 端的,proxy -> 源 是走 http ,现在需要多台 proxy ,那怎么处理好?在每个 proxy 都运行认证工具来取得证书应该不行吧,难道要想个办法在其中一台运行认证工具,再定时同步生成的证书到所有别的 proxy 上去?

PS. 我用的是官方工具,在 web root 下加文件的方式。

proxy 工具 证书 认证13 条回复 • 2016-11-29 11:30:59 +08:00
sneezry 1
sneezry 2016-11-28 19:56:50 +08:00
有多台 proxy 肯定是需要在每台上都部署证书的,你说的思路应该就是通常的做法
doubleflower 2
doubleflower 2016-11-28 20:01:17 +08:00
@sneezry 在每台上都运行工具来得到 /更新同一个域名的证书没有问题吗?
sneezry 3
sneezry 2016-11-28 21:40:48 +08:00
@doubleflower 有问题啊,用一台机器更新证书,其他的机器和它同步
doubleflower 4
doubleflower 2016-11-28 22:29:58 +08:00
@sneezry 打算换用 acme.sh 的 dns 方式。用官方的 web 方式是有验证问题, acme.sh 的 dns 方式我不确定分别在不同的机器上分别签发有没有问题。目前还是打算麻烦点在本地 dns 方式得到证书并定期同步到所有服务器算了。
neilp 5
neilp 2016-11-28 22:37:48 +08:00
用 dns 方式当然是首选了, 你可以在多个 proxy 上同时申请.

如果你用 http 回源的方式也可以. 通过 `–post-hook` 来通过 ssh 命令部署到所有的 proxy 上.
zealic 6
zealic 2016-11-28 22:40:03 +08:00
Caddy Automatic TLS
kuretru 7
kuretru 2016-11-28 22:41:55 +08:00
用 Certbot ,计划任务定时更新的时候顺便同步到其他服务器
sneezry 8
sneezry 2016-11-28 23:16:46 +08:00
@doubleflower 也可以使用我写的 SSL.md ,昨天刚加的 API ,几台服务器定期去 wget 就行了,如果已签发证书过期时间距离现在不到 20 天 API 就会返回新证书,否则返回旧证书,几台服务器去抓,*个抓到的会续签证书,后面几个得到的证书回和*台拿到的相同,不会出现重复签发的问题。

https://www.v2ex.com/t/323677
doubleflower 9
doubleflower 2016-11-28 23:42:41 +08:00
@sneezry 话说如果用 acme.sh 在不同的服务器上同一时间分别用 dns 方式签证书会有问题吗?会分别在不同的 proxy 上得到不同的证书内容吗?如果内容不同是不是会有问题?(文档里没提这个,刚才本地试了下好象每次重签内容都不同)

我还是想偷个懒,如果没问题的话只要在每个服务器上运行个脚本设置下就行了,就省了同步了这个麻烦事了。
cnnblike 10
cnnblike 2016-11-29 02:22:03 +08:00
@sneezry 不是说单个 IP 每天申请的数量有上限吗?

lightening 11
lightening 2016-11-29 03:45:49 +08:00
有上限,每周每个域名 20 个证书,每个证书可以重复 5 次。
sneezry 12
sneezry 2016-11-29 10:06:39 +08:00 via iPhone
@cnnblike https://letsencrypt.org/docs/rate-limits/

似乎签发证书的数量限制仅针对域名,不针对 ip
cnnblike 13
cnnblike 2016-11-29 11:30:59 +08:00
@sneezry 噢噢噢噢……原来是这样!谢谢大佬指点!

iOS Sign in with Apple 苹果登录带配置步骤代码封装

%title插图%num
2.配置key

%title插图%num

3.

%title插图%num

4.key 与 Certificates 关联

%title插图%num

5

%title插图%num

6. 更新Profiles的开发配置文件并下载

7. Xcode 开启 Sign In with Apple

%title插图%num

苹果授权登录代码封装
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, AppleLoginType) {
AppleLoginTypeUnknown = 0,
AppleLoginTypeSuccessful,
AppleLoginTypeUserSuccessful,
AppleLoginTypeFailure
};

@interface AppleLogin : UIView

/// Apple登录
/// @param user 选填,已存user可以直接快速验证,没有传nil ,断网可验证。
/// @param view <#view description#>
/// @param rect <#rect description#>
/// @param block <#block description#>
+(instancetype)appLogoinFromUser:(NSString *)user view:(UIView *)view rect:(CGRect)rect block:(void(^)(NSInteger state,NSString *msg,id data))block;

@end

NS_ASSUME_NONNULL_END

#import “AppleLogin.h”
#import <AuthenticationServices/AuthenticationServices.h>

typedef void(^AppleLoginBlock)(NSInteger state,NSString *msg,id data);
@interface AppleLogin ()
<
ASAuthorizationControllerDelegate
,ASAuthorizationControllerPresentationContextProviding
>
@property(nonatomic,strong)NSString *userId;
@property(nonatomic,strong)UIView *view;
@property(nonatomic)CGRect rect;
@property(nonatomic,copy)AppleLoginBlock appleLoginBlock;
@end

@implementation AppleLogin

+(instancetype)appLogoinFromView:(UIView *)superView rect:(CGRect)rect block:(void(^)(NSInteger state,NSString *msg,id data))block
{
AppleLogin * appleLogin = [self appLogoinFromView:superView rect:rect];
appleLogin.appleLoginBlock = block;
return appleLogin;
}

+(instancetype)appLogoinFromUser:(NSString *)user view:(UIView *)view rect:(CGRect)rect block:(void(^)(NSInteger state,NSString *msg,id data))block
{
AppleLogin * appleLogin = [self appLogoinFromView:view rect:rect];
appleLogin.userId = user;
appleLogin.appleLoginBlock = block;
return appleLogin;
}

+(instancetype)appLogoinFromView:(UIView *)superView rect:(CGRect)rect
{
AppleLogin * appleLogin = [[AppleLogin alloc]initWithFrame:rect];
appleLogin.view = superView;
appleLogin.rect = rect;
[appleLogin initUI];
return appleLogin;
}

– (void)initUI
{
if (@available(iOS 13.0, *))
{
ASAuthorizationAppleIDButton *button = [ASAuthorizationAppleIDButton buttonWithType:ASAuthorizationAppleIDButtonTypeSignIn style:ASAuthorizationAppleIDButtonStyleBlack];
button.frame = CGRectMake(0, 0, self.rect.size.width, self.rect.size.height);
button.layer.masksToBounds = YES;
button.layer.cornerRadius = self.frame.size.width/2;
[button addTarget:self action:@selector(signInWithApple) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
[self.view addSubview:self];
}
}

#pragma mark ————————————— 登录按钮点击事件 —————————————
– (void)signInWithApple API_AVAILABLE(ios(13.0))
{
if (@available(iOS 13.0, *))
{
NSLog(@”Apple Login Click”);
//基于用户的Apple ID授权用户,生成用户授权请求的一种机制
ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
if (_userId.length == 0)
{
NSLog(@”授权请求AppleID”);
ASAuthorizationAppleIDRequest *request = appleIDProvider.createRequest;
[request setRequestedScopes:@[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail]];
//由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器
ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
//设置授权控制器通知授权请求的成功与失败的代理
controller.delegate = self;
//设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户
controller.presentationContextProvider = self;
//在控制器初始化期间启动授权流
[controller performRequests];
}
else
{
// NSLog(@”快速登录使用授权登录返回的 user “);
//快速登录
[appleIDProvider getCredentialStateForUserID:_userId completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError * _Nullable error) {

if (credentialState == ASAuthorizationAppleIDProviderCredentialAuthorized)
{
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
if (self.appleLoginBlock)
{
self.appleLoginBlock(AppleLoginTypeUserSuccessful,@”ok”,dic);
}
}
else
{ NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setValue:error.description forKey:@”errorMsg”];
[dic setValue:[NSNumber numberWithInteger:error.code] forKey:@”code”];
if (self.appleLoginBlock)
{
self.appleLoginBlock(AppleLoginTypeFailure,error.description,dic);
}
}
}];
}
}
else
{

}
}

#pragma mark ————————————— 成功回调 —————————————
– (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0))
{
// NSLog(@”授权完成:::%@”, authorization.credential);
// NSLog(@”%s”, __FUNCTION__);
// NSLog(@”%@”, controller);
// NSLog(@”%@”, authorization);

if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]])
{
// 用户登录使用ASAuthorizationAppleIDCredential
ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential;
NSString *user = appleIDCredential.user;
// 使用过授权的,可能获取不到以下三个参数
NSString *familyName = appleIDCredential.fullName.familyName;
NSString *givenName = appleIDCredential.fullName.givenName;
NSString *nickname = appleIDCredential.fullName.nickname;
NSString *email = appleIDCredential.email;
NSString *state = appleIDCredential.state;
NSData *identityToken = appleIDCredential.identityToken;
NSData *authorizationCode = appleIDCredential.authorizationCode;
ASUserDetectionStatus realUserStatus = appleIDCredential.realUserStatus;

// 服务器验证需要使用的参数
NSString *identityTokenStr = [[NSString alloc] initWithData:identityToken encoding:NSUTF8StringEncoding];
NSString *authorizationCodeStr = [[NSString alloc] initWithData:authorizationCode encoding:NSUTF8StringEncoding];
// NSLog(@”%@\n\n%@”, identityTokenStr, authorizationCodeStr);

NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setValue:state forKey:@”state”];
[dic setValue:user forKey:@”user”];
[dic setValue:email forKey:@”email”];
[dic setValue:familyName forKey:@”familyName”];
[dic setValue:givenName forKey:@”givenName”];
[dic setValue:nickname forKey:@”nickname”];
// [dic setValue:appleIDCredential forKey:@”appleIDCredential”];
[dic setValue:authorizationCodeStr forKey:@”authorizationCode”];
[dic setValue:identityTokenStr forKey:@”identityToken”];
// [dic setValue:@(realUserStatus) forKey:@”realUserStatus”];

if (self.appleLoginBlock)
{
self.appleLoginBlock(AppleLoginTypeSuccessful, @”ok”,dic);
}
// 需要使用钥匙串的方式保存用户的唯一信息 Keychain
}
else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]])
{
// 这个获取的是iCloud记录的账号密码,需要输入框支持iOS 12 记录账号密码的新特性,如果不支持,可以忽略
// Sign in using an existing iCloud Keychain credential.
// 用户登录使用现有的密码凭证
ASPasswordCredential *passwordCredential = authorization.credential;
// 密码凭证对象的用户标识 用户的唯一标识
NSString *user = passwordCredential.user;
// 密码凭证对象的密码
NSString *password = passwordCredential.password;
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setValue:user forKey:@”user”];
[dic setValue:password forKey:@”password”];
if (self.appleLoginBlock)
{
self.appleLoginBlock(AppleLoginTypeSuccessful, @”ok”,dic);
}
}
else
{
// NSLog(@”授权信息均不符”);
NSString *errorMsg = @”授权信息不符”;
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setValue:errorMsg forKey:@”errorMsg”];
if (self.appleLoginBlock)
{
self.appleLoginBlock(AppleLoginTypeFailure,errorMsg,dic);
}
}
}

#pragma mark ————————————— 失败回调 —————————————
– (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0))
{
NSString *errorMsg = nil;
switch (error.code) {
case ASAuthorizationErrorCanceled:
errorMsg = @”用户取消了授权请求”;
break;
case ASAuthorizationErrorFailed:
errorMsg = @”授权请求失败”;
break;
case ASAuthorizationErrorInvalidResponse:
errorMsg = @”授权请求响应无效”;
break;
case ASAuthorizationErrorNotHandled:
errorMsg = @”未能处理授权请求”;
break;
case ASAuthorizationErrorUnknown:
errorMsg = @”授权请求失败未知原因”;
break;
}
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setValue:errorMsg forKey:@”errorMsg”];
[dic setValue:[NSNumber numberWithInteger:error.code] forKey:@”code”];
if (self.appleLoginBlock)
{
self.appleLoginBlock(AppleLoginTypeFailure,errorMsg,dic);
}
}

#pragma mark ————————————— 告诉代理应该在哪个window 展示内容给用户 —————————————
– (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0))
{
return [UIApplication sharedApplication].windows.lastObject;
}

@end
调用
#import “ViewController.h”
#import “AppleLogin.h”
@interface ViewController ()

@end

@implementation ViewController

– (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.

// user在*次苹果验证时传空,如果对user做了存储,传user可以快速验证(简单说就是你验证过的user手机里会存,并且每次返回的user都是不一样的,user正确断网也可以验证)
NSString *user ;
[AppleLogin appLogoinFromUser:user view:self.view rect:CGRectMake(100, 100, 60, 60) block:^(NSInteger state, NSString * _Nonnull msg, id _Nonnull data) {
if (state == AppleLoginTypeSuccessful)
{
NSLog(@”授权成功 %@”,data);
}
else if (state == AppleLoginTypeUserSuccessful)
{
NSLog(@”账号验证成功”);
}
else
{
NSLog(@”验证失败: %@”,msg);
}

}];
}

@end