OpenHarmony 几乎 fork 了大部分第三方常用库并移植到 HMOS

OpenHarmony 几乎 fork 了大部分第三方常用库并移植到 HMOS

 

yukiww233 · 15 天前 · 3409 次点击

https://gitee.com/organizations/openharmony-tpc/projects

粗略看了下,
-大部分的 Android API 和控件都接近”无缝”切换到了 HM API;
-连这些第三方库都刻意在源码中去掉了 Android 相关字段
https://gitee.com/openharmony-tpc/lottie-ohos/commit

不懂就问, 这样直接 fork 修改之后”去掉原项目相关的一切信息”, 是否违反了开源协议
(如常见的 apache v2, 在延伸的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议,商标,专利声明和其他原来作者规定需要包含的说明)

35 条回复    2021-08-13 09:30:07 +08:00
AoEiuV020
    1

AoEiuV020   15 天前

HM 怎么还没过气,这些鬼问题在知乎都被翻烂了吧,
NewTab12138
    2

NewTab12138   15 天前   ❤️ 14

楼主居然敢提出质疑,我单方面宣布你被开除国籍
IgniteWhite
    3

IgniteWhite   15 天前

我们水热见。感谢楼主分享信息
Trim21
    4

Trim21   15 天前 via Android   ❤️ 3

“Glide is a fast and efficient image loading library for openharmony focused on smooth scrolling”
Mohanson
    5

Mohanson   15 天前   ❤️ 24

你发这些有什么目的?谁指使你的?你的动机是什么?你取得华为许可了吗?他们容许你发了吗?你背后是谁,发这些想做什么?你在讽刺谁?想颠覆什么?破坏什么?影射什么?回答不上来?那么跟我走一趟.
wipbssldo
    6

wipbssldo   15 天前   ❤️ 8

这就是「自主可控」中的可控啦,预防美帝 github 屏蔽我们。

PS:gitee 这货点开 commit 记录居然要登录,吔屎啦!

Rheinmetal
    7

Rheinmetal   15 天前

开源库就不能卡脖子了么
窃码不算偷 :doge
LukeChien
    8

LukeChien   15 天前 via Android   ❤️ 18

和汉芯打磨芯片上的 logo 没有任何区别
Yoefs
    9

Yoefs   15 天前   ❤️ 7

你居然在大是大非面前讲科学??
funbox
    10

funbox   15 天前

看谁流氓时间

reallittoma
    11

reallittoma   15 天前   ❤️ 1

华为这么大一个公司,把这些库重新实现一遍不奇怪吧。
greatghoul
    12

greatghoul   15 天前

gitee 越来越垃圾了。
gainsurier
    13

gainsurier   15 天前   ❤️ 2

华为还算好的,opengauss 至少还保留了 pq 关键字。
以下是 opengauss 对比 kingbase 的配置文件
%title插图%num
shmilwdc
    14

shmilwdc   15 天前

@wipbssldo 确实吔屎啦 我的号只每天看看开源中国的新闻 其他啥都没做过 然后莫名其妙被封
foMM
    15

foMM   15 天前

@gainsurier #13 这……自主特色吗
yukiww233
    16

yukiww233   15 天前   ❤️ 2

不能 append 了…
又去翻了翻, 之前表述的”去掉原项目相关的一切信息”有误:
License 都是保留了的, 只是从大部分项目 readme 确实完全看不出这是一个 fork 的项目以及原项目是什么

就事论事的还是比较少, 点个下沉了

iFlicker
    17

iFlicker   15 天前

有点恶心。。。
ShareDuck
    18

ShareDuck   15 天前   ❤️ 1

能不能好好讨论问题?一个个阴阳怪气的。
ShareDuck
    19

ShareDuck   15 天前

1 、是否合法?
2 、合法的前提下,是否道德?
felixlong
    20

felixlong   15 天前   ❤️ 1

fork 出来但是没有人 follow 的项目出来注定是找死。是不是 HW 里面的人没有什么项目可做在刷 KPI ?
icyalala
    21

icyalala   15 天前

@ShareDuck
用了 Apache License 的那几个项目,如果是通过源码形式开源,那 “用户必须让任何修改过的文件附带明显的通知,声明用户已更改文件”。
用了 GPL License 的几个项目,就要讨论一下看看有没有通过链接形式使用了,如果是的话,相关项目也会被感染,也需要以 GPL 开源。
greatghoul
    22

greatghoul   15 天前 via Android   ❤️ 2

@ShareDuck 啥,说鸿蒙的坏话非法!?
greatghoul
    23

greatghoul   15 天前 via Android

万物互敛
NickyPP
    24

NickyPP   15 天前

@Trim21 这个超搞笑,还有其他的组件也是。一大早把我笑死了,太喜感了。我怀疑 fork 这个的人是高级黑。。
no1xsyzy
    25

no1xsyzy   15 天前

@ShareDuck 关于道德,我认为 ESR 提出的模仿开垦荒地的思路仍然适用。
在别人的地皮边上开了块新地皮,用着别人免费提供给任何人使用的基础设施。
raycool
    26

raycool   14 天前   ❤️ 1

楼主缺乏政治素养
在大是大非问题面前你竟然谈事实和道理
还是不是中国人了。
WebKit
    27

WebKit   14 天前 via Android

@reallittoma 关键要重新实现。现在情况是直接 android 库,把原来的清单文件从 xml 改成 json 格式,然后在做个小修改就信了
ikas
    28

ikas   14 天前   ❤️ 1

确实无耻..也不知道搞这些的开发人员怎么好意思的
WebKit
    29

WebKit   14 天前 via Android

所以连第三方库简单改一下就能用,还说不是套壳
ShareDuck
    30

ShareDuck   13 天前

@greatghoul 眼睛不要可以捐掉。
ShareDuck
    31

ShareDuck   13 天前

@greatghoul 无法想象你是怎样能理解出这个意思的,只能说心中有*,看什么都是*。
greatghoul
    32

greatghoul   13 天前

> 眼睛不要可以捐掉。

@ShareDuck 能不能好好讨论问题?一个个阴阳怪气的。 ?

说鸿蒙的不是

1 、是否合法?
2 、合法的前提下,是否道德?

ShareDuck
    33

ShareDuck   13 天前

@greatghoul 很明显,我是在讨论“OpenHarmony 几乎 fork……”(这个也是本帖主题):1 、是否合法? 2 、合法的前提下,(这种做法)是否道德?

而上一个回复,我继续保持我的观点,大部分回复根本没在讨论问题本身,只是在阴阳怪气而已。

greatghoul
    34

greatghoul   13 天前

@ShareDuck 原来如此,我也只是参与到吃瓜队伍,阴阳怪气一下而已,您稍微忍耐一下,*好拉黑不要再回复我了,不然我心里都是*,看到您的回复怕也看成*
ShareDuck
    35

ShareDuck   13 天前

@greatghoul 哈哈哈,抱歉,我收回这句话。

如果把群晖的moment 文件夹挂载到 Android 下

Smash · 16 天前 · 3791 次点击

看到放桌上的 Pixel 突然想到:

如果把群晖的 moment 文件夹挂载到 Android 下,Google photo 直接同步这个目录,是不是就不用再用 Syncthing 把照片从群晖再同步到 Pixel 的相册目录,再由 Google photo 同步了?

这样还有一个好处就是,不占用 Pixel 的空间.

不知道是否可行.

NAS====>Syncthing====>Pixel====>Google Photo

NAS====>Pixel mount=====>Google Photo

Syncthing 同步的方案是之前 V 友教我的,用了快小半年了吧,挺稳定的,就是 Pixel 发烫厉害…

第 1 条附言  ·  15 天前

贴一下Android10 Pixel的root方法

Pixel Root(Android 10)

OEM 解锁

adb reboot bootloader
fastboot oem unlock

安装 Magisk

从 Github Release 上下载*新版本的 Magisk 安装.

adb install /your/path/to/magisk.apk

下载对应版本的 Firmware

Pixel Firmware

*后的版本应该是10.0.0 (QP1A.191005.007.A3, Dec 2019)

确认当前系统的版本和下载的版本一致,不然就要再刷一次机.

给 Boot.img 补丁

  • 将下载的 firmware 解压,将 boot.img 推送到 Pixel 中
    adb push /Your/Path/To/boot.img  /storage/self/primary/Download/boot.img
    
  • 打开安装好的 Magisk,点击安装>选择并修补一个文件,然后选中 Boot.img
  • 将打好补丁的 img 从源目录拷贝出来
    adb pull adb pull /storage/self/primary/Download/magisk_patched.img ~/Desktop/
    

刷机

刷机前,确保本机已经安装过 fastboot

adb reboot bootloader
fastboot flash boot magisk_patched.img
# 线刷完成后,重启手机
fastboot reboot

重启

你的手机应该重新启动,你已经成功地将你的 Pixel Android 10 Root!

测试是否 Root 成功

adb shell su
# 如果弹窗请求root权限,则root成功

32 条回复    2021-08-23 21:27:14 +08:00

laucenmi
    1

laucenmi   16 天前

不 root 没找到可行的 mount 方案,google 自己提供的 smb mount 后在文件里面看不到 mount 的节点
Smash
    2

Smash   16 天前

@laucenmi #1 已经准备 root 了,syncthing 的方案还是有点不是很完美.

之前从群晖 6 升级到群晖 7.群晖套件版本的 syncthing 直接就不兼容了,结果没办法又搞了个 Docker 来跑 syncthing 的服务器端.

laucenmi
    3

laucenmi   15 天前

@Smash 有解决方案了分享一下。我在用 Resilio Sync,还没找到只同步增量文件的办法,为节省空间本地删除后下次同步又全部都下载来了。搞得只能用了一个单独的文件夹更新照片
zmxnv123
    4

zmxnv123   15 天前

为什么不支持 nas => GooglePhoto
jazzychai
    5

jazzychai   15 天前

@zmxnv123 因为 pixel 可以白嫖
mortal
    6

mortal   15 天前

我的 Pixel Root 了,之前好像试过 mount 网络路径,但是似乎是有问题的。也不知道是不是没配置好。
Smash
    7

Smash   15 天前

@mortal #6 失败了,各种测试*后都不行,Google 上面说 Android 的 nfs 支持的版本是 version 3,但是我群晖的是 4.1,不晓得有关系没.
“`bash
sudo mount -o nolock,ro,hard,intr,vers=3 -t nfs 192.168.1.100:/volume1/download /sdcard/NAS

# 输出 mount: ‘192.168.1.100:/volume1/download’->’/sdcard/NAS’: No such device
“`

heqirong
    8

heqirong   15 天前

magisk 有 rclone 的模块,可以试试
dai462
    9

dai462   15 天前

感觉去咸鱼找点老机子刷下不错
Smash
    10

Smash   15 天前 via Android

@heqirong 传输文件不是问题,目的是不想占用 Pixel 的空间,减少一层中转,实现 mount 局域网网上的文件夹。只有系统级别的 mount,Goolge Photo App 才能识别到。

Smash
    11

Smash   15 天前 via Android

@dai462 只有 Pixel 一代有白嫖的价值。。。
cheng6563
    12

cheng6563   15 天前

nfs 驱动是在内核里的吗?刷个内核看看?
不行就 chroot 到个 debian 上再挂载看看?
gaolycn
    13

gaolycn   15 天前

要刷支持 nfs 的内核,我试过,挂载几万张照片,发烫厉害基本不可用,现在用 syncthing
gaolycn
    14

gaolycn   15 天前

当初刷的 ElementalX 的内核,同时要关闭 SELinux
Smash
    15

Smash   15 天前

@gaolycn #13 syncthing 也发烫…我才 40 多 GB 的照片加视频,就很烫了
Smash
    16

Smash   15 天前

@gaolycn #13 我在网上看到一个不用刷内核的方法,只要内核支持 fuse 就可以用,我试试看.
coolmint
    17

coolmint   15 天前

折腾了半天可能还是 syncthing 香啊,而且发热问题也还好,你不可能总在大量的批量同步,上传吧。
话说 dsm7 里面,我现在用的套件的 syncthing,在 docker 里面的 syncthing,该如何配置该容器访问 photos 文件夹的权限呢?套件的直接会给加一个用户,docker 的由于小白,不知道怎么给权限。
coolmint
    18

coolmint   15 天前

@laucenmi syncthing 是可以同步增量的,photos 同步好照片后,直接在 photos 里面释放空间就好。
Smash
    19

Smash   15 天前

我放弃了,还是继续我的 syncthing 吧,如果有 V 友有更好的方案,欢迎回帖.
Smash
    20

Smash   15 天前   ❤️ 1

@coolmint #17 我用的是 linuxserver/syncthing 这个 image.

主要是 PGID 和 PUID 这两个参数要配置对,配好了才有访问权限.如果你不知道是多少,ssh 上群晖,输入 id 会打印出来.

我是直接把整个 homes 文件夹都暴露给 syncthing 了,然后在 syncthing 里面再配置好我需要同步的目录.

%title插图%num
%title插图%num

tankren
    21

tankren   15 天前

@coolmint #18 释放了不就又 syncthing 回来了?
Smallsun1231
    22

Smallsun1231   14 天前

*貌提问 :
此方式解锁 OEM 后会锁定 OEM 为解锁状态嘛?尝试过能正常获取 Root 权限后,重启发现无法正常开机 提示 OEM lock on
暂无解决方案
laucenmi
    23

laucenmi   14 天前

@tankren 我在用 Resilio Sync,也是这样的尴尬,128g 的手机不能完整 sync, 所以用来单独的文件夹来同步. 不确定 syncthing 会怎么样,有机会试试
Smash
    24

Smash   14 天前

@Smallsun1231 #22 重启的时候会有一个⚠️,不理他,过几秒自动跳转到 Google 启动页面,下面有一把小锁,是打开的状态.

我已经验证了.

yuantinghg
    25

yuantinghg   14 天前

比如一加 5 刷 PE plus 能够白嫖吗?
tankren
    26

tankren   14 天前

@laucenmi #23 刚查了一下 syncthing 有一个 ignoreDelete 参数 如果在 master (同步源)设置这个为 true,应该就可以达成目的
Zel
    27

Zel   14 天前

我是用 resilio sync 将所有设备上的照片,同步到 Pixel 上的。
Smash
    28

Smash   13 天前

@tankren #26 帮你贴一下操作路径,在网页后台=>右上角的操作按钮=>高级=>默认文件夹=>IgnoreDelete
ma6174
    29

ma6174   12 天前 via iPhone   ❤️ 1

root 之后 EasySSHFS 可行,需要注意挂载点填 /storage/emulated/0/DCIM/ 里面的一个目录。
ma6174
    30

ma6174   12 天前 via iPhone

远程挂载不像本地有新文件能立即发现,有时候会延后几个小时才会同步。
wifi *好使用 5G 信号,靠路由器近一点,延迟低了加载更快。
目前貌似做不到开机自启自动挂载,另外 NAS 服务器或者路由器重启都会导致挂载掉,需要人工点一下 mount 重新挂载,一般没事不要重启。
ShakeChan
    31

ShakeChan   3 天前

@ma6174 感谢你的方案,我在实施过程中,EasySSHFS 挂载提醒「 Pattern xxx@192.168.xx: /storage/emulated/0/DCIM fuse.sshfs is not in /proc/mounts,请问你有遇到类似的问题吗?
ma6174
    32

ma6174   2 天前 via iPhone

上面目录错了,应该是 /mnt/runtime/{read,write,default}/emulated/0/XXXX 这样的,参考 https://github.com/bobrofon/easysshfs/issues/3#issuecomment-478190548
@ShakeChan

OPPO 手机真机调试 instant run,每次必须重新安装

OPPO 手机真机调试 instant run,每次必须重新安装

 

honglei92 · 9 天前 · 1116 次点击

OPPO 手机真机调试每次必须重新安装 r11s 安卓 9 的系统

7 条回复    2021-08-18 01:36:17 +08:00

AoEiuV020
    1

AoEiuV020   9 天前

意思是重新安装可以正常调试而 attach debugger to android process 无法使用吗?
这我只有 attach 过的进程断开 debugger 后会出现无法 attach, 重启 app 就能 attach,
honglei92
    2

honglei92   9 天前

@AoEiuV020 用的 apply changes,按理只需要重启 activity,但是它会重新安装一次 app 再打开、
AoEiuV020
    3

AoEiuV020   9 天前

@honglei92 啊这,我平时不用 apply changes, 一直是直接 run,高版本安卓 run 也会快很多,
刚试了下 apply changes 和 apply changes and restart activity, 代码修改甚至没生效,
这种东西有过当年 instant run 好处没啥 bug 一堆*后被废弃的先例我就没对 apply changes 报什么希望,
john6lq
    4

john6lq   9 天前 via iPhone

这玩意就是徒增耗时,还关不掉。谷歌做东西总是这样。
pcatzj
    5

pcatzj   9 天前

oppo debug 好像经常有问题。我们项目在一个 OPPO 测试机上安装*次总是白屏,要杀进程再装一次才能正常显示。
Helsing
    6

Helsing   9 天前 via iPhone

每次输密码才是*坑的
zerofancy
    7

zerofancy   8 天前 via Android

Oppo 坑爹的很,开发的时候适配出很多问题
比如,拉起系统相册选择图片,试了这么多手机就 oppo 和 vivo 的不支持多选

iOS 滚动数字控件:DPScrollNumberLabel 实现

写在前面
*次写博客,有点小激动,同时也害怕写的很烂,所以希望大家能够包容,如果大家觉得看不下去我的博客,可以直接翻到*后有源码和demo的github地址。开发ios也有大半年了,所以想要尝试一下写点博客,好了废话不多说了下面开始正题了。

正文
简介
由于公司前段时间项目里要用到一个可以滚动的数字标签,所以就写了这样一个控件,现在有时间了,就写篇博客记录一下实现这个控件的过程。

上一张gif动画

这个控件的逻辑是当个位数从0~9~0时十位数向上滚动1,当十位完成一个0~9~0循环时,百位向上滚动1依次类推

实现思路
先说一下我实现这个控件的思路,其实比较简单,数字的每一列都是一个很长的UILabel,然后由上至下是数字0~9,当数字改变时,让这个label上下移动,产生滚动的动画,只需要计算循环的次数,滚动的方向,滚动的时间就可以了。

Talk is cheap,show you The code!
首先看一下头文件里的内容:
//
// DPScrollNumberLabel.h
// DPScrollNumberLabelDemo
//
// Created by Dai Pei on 16/5/23.
// Copyright © 2016年 Dai Pei. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface DPScrollNumberLabel : UIView

@property (nonatomic, strong)NSNumber *displayedNumber;

– (instancetype)initWithNumber:(NSNumber *)originNumber fontSize:(CGFloat)size;
– (instancetype)initWithNumber:(NSNumber *)originNumber fontSize:(CGFloat)size textColor:(UIColor *)color;
– (instancetype)initWithNumber:(NSNumber *)originNumber font:(UIFont *)font;
– (instancetype)initWithNumber:(NSNumber *)originNumber font:(UIFont *)font textColor:(UIColor *)color;
– (instancetype)initWithNumber:(NSNumber *)originNumber fontSize:(CGFloat)size rowNumber:(NSUInteger)rowNumber;
– (instancetype)initWithNumber:(NSNumber *)originNumber fontSize:(CGFloat)size textColor:(UIColor *)color rowNumber:(NSUInteger)rowNumber;
– (instancetype)initWithNumber:(NSNumber *)originNumber font:(UIFont *)font textColor:(UIColor *)color rowNumber:(NSUInteger)rowNumber;//rowNumber should less than or equal 8

– (void)changeToNumber:(NSNumber *)number animated:(BOOL)animated;
– (void)changeToNumber:(NSNumber *)number interval:(CGFloat)interval animated:(BOOL)animated;
@end
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
可以看到头文件里有7个初始化方法,分为两大类:规定列数和不规定列数。当规定列数时,则控件的列数固定,当传入的数字大于列数限制时函数直接返回,当不规定列数时,以初始化传入的数字的列数为初始列数,当后面传入的数字大于初始列数时,会自动在左边补加列(*大列数不能超过8列)
其中*重要的一个参数是字体的大小,我需要这个参数去计算此控件的大小。
另外两个方法当需要显示的数字改变时调用,animated传入YES时会有动画,传入NO时直接改变不播放动画。

下面只贴上其中一个init方法:
– (instancetype)initWithNumber:(NSNumber *)originNumber fontSize:(CGFloat)size textColor:(UIColor *)color rowNumber:(NSUInteger)rowNumber {
self = [super init];
if (self) {
self.displayedNumber = originNumber;
self.font = [UIFont systemFontOfSize:size];
self.textColor = color;
self.isAnimation = NO;
self.finishedAnimationCount = 0;
self.rowNumber = (rowNumber > 0 && rowNumber <= 8) ? rowNumber : 0;
self.maxRowNumber = (self.rowNumber == 0) ? 8 : rowNumber;
[self commonInit];
}
return self;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
初始化后进入commonInit方法:
– (void)commonInit {
[self initCell];
[self initParent];
}
1
2
3
4
然后先初始化cell,再初始化parent:
– (void)initCell {
int originNumber = self.displayedNumber.intValue;
//如果没有规定列数 就自己计算 方法具体实现会在后面贴出
if (self.rowNumber == 0) {
self.rowNumber = [self calculateRowNumber:originNumber];
}
//用于保存所有的cell
self.cellArray = [[NSMutableArray alloc] init];
NSString *text = @”0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n0″;
//这个方法很赞 可以直接根据内容来计算一个view的大小
CGRect rect = [text boundingRectWithSize:CGSizeZero
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName:self.font} context:nil];
//然后保存宽度和高度值,后面很多地方都会用到
self.cellWidth = rect.size.width;
self.cellHeight = rect.size.height;
//拿到每个位数上显示的数字并保存在一个数组中
NSArray *displayNumberArray = [self getCellDisplayNumberWithNumber:self.displayedNumber.integerValue];
//初始化每个cell 并且保存在数组中
for (NSInteger i = 0; i < self.rowNumber; i++) {
UILabel *scrollCell = [self makeScrollCell];
scrollCell.frame = CGRectMake((self.rowNumber – 1 – i) * self.cellWidth, 0, self.cellWidth, self.cellHeight);
scrollCell.text = text;
NSNumber *displayNum = [displayNumberArray objectAtIndex:i];
//此方法调整cell的位置,使其显示相应的数字 方法具体实现会在后面贴出
[self setScrollCell:scrollCell toNumber:displayNum.integerValue];
[self.cellArray addObject:scrollCell];
}
}
#pragma mark – Getters
– (UILabel *)makeScrollCell {
UILabel *cell = [[UILabel alloc] init];
cell.font = self.font;
cell.numberOfLines = 11;
cell.textColor = self.textColor;
return cell;
}
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
然后是实现parent:

– (void)initParent{
self.bounds = CGRectMake(0, 0, self.rowNumber * self.cellWidth, self.cellHeight / 11);
self.backgroundColor = [UIColor clearColor];
self.layer.masksToBounds = YES;
//把cell布局到parent中
[self layoutCell:self.rowNumber withAnimation:YES];
}
1
2
3
4
5
6
7
– (void)layoutCell:(NSUInteger)rowNumber withAnimation:(BOOL)animated{
//先将子view全部移除 此处移除是有原因的
for (UIView *subView in self.subviews) {
[subView removeFromSuperview];
}
//再添加进去
for (UILabel *cell in self.cellArray) {
[self addSubview:cell];
}
//此处用动画重新排列所有的cell
__weak typeof(self) weakSelf = self;
[UIView animateWithDuration:0.2 * (rowNumber – self.rowNumber) animations:^{
for (int i = 0; i < rowNumber; i++) {
UILabel *cell = [weakSelf.cellArray objectAtIndex:i];
cell.frame = CGRectMake((rowNumber – 1 – i) * weakSelf.cellWidth,
cell.frame.origin.y,
weakSelf.cellWidth,
weakSelf.cellHeight);
}
self.frame = CGRectMake(self.frame.origin.x,
self.frame.origin.y,
rowNumber * self.cellWidth,
self.cellHeight/11);
} completion:nil];
}
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
至此,初始化的工作全部完成

下面让我们关注头文件里另外的两个公有方法:
– (void)changeToNumber:(NSNumber *)number animated:(BOOL)animated {

[self changeToNumber:number interval:0 animated:animated];
}

– (void)changeToNumber:(NSNumber *)number interval:(CGFloat)interval animated:(BOOL)animated {
//因为没有做负数部分,所以当传入的数字小于0 直接返回
if (number.integerValue < 0) {
return ;
}
//当大于*大行数,直接返回
if ([self calculateRowNumber:number.integerValue] > self.maxRowNumber) {
return ;
}
//如果传入数字和本身显示相同,直接返回
if (number.integerValue == self.displayedNumber.integerValue) {
return ;
}
//当传入数字时,当前动画还没有播放完成,加入数组,等待动画播放完成
if (self.isAnimation) {
if (!self.taskArray) {
self.taskArray = [NSMutableArray array];
}
[self.taskArray addObject:@{keyTaskDisplayNumber:number, keyTaskChangeNumber:@(number.integerValue – self.displayedNumber.integerValue),keyTaskInterval:@(interval)}];
}else {
if (animated) {
//animated 为YES时 进入此方法
[self playAnimationWithChange:number.integerValue – self.displayedNumber.integerValue displayNumber:number interval:interval];
self.isAnimation = YES;
}else {
//animated 为NO时 直接改变
//这个方法在前面说过,具体实现会在后面贴出
NSArray<NSNumber *> *displayNumbers = [self getCellDisplayNumberWithNumber:number.integerValue];
for (int i = 0; i < displayNumbers.count; i++) {
[self setScrollCell:self.cellArray[i] toNumber:displayNumbers[i].integerValue];
}
}
}
self.displayedNumber = number;
}
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
下面就要进入动画部分了
static const CGFloat bufferModulus = 0.7f;

– (void)playAnimationWithChange:(NSInteger)changeNumber displayNumber:(NSNumber *)displayNumber interval:(CGFloat)interval{
//改名后的列数
NSInteger nextRowNumber = [self calculateRowNumber:displayNumber.intValue];
//只有当列数增加时才重新布局
if (nextRowNumber > self.rowNumber) {
[self reInitCell:nextRowNumber];
[self layoutCell:nextRowNumber withAnimation:YES];
self.rowNumber = nextRowNumber;
}
//储存每一位循环次数的数组
NSArray *repeatCountArray = [self getRepeatTimesWithChangeNumber:changeNumber displayNumber:displayNumber.integerValue];
//储存每一位将显示的数值
NSArray *willDisplayNums = [self getCellDisplayNumberWithNumber:displayNumber.integerValue];

//如果没有设置动画间隔,则根据改变的大小来进行计算
if (interval == 0) {
interval = [self getIntervalWithOriginalNumber:displayNumber.integerValue – changeNumber displayNumber:displayNumber.integerValue];
}
//获得滚动的方向
ScrollAnimationDirection direction = (changeNumber > 0)? ScrollAnimationDirectionUp : ScrollAnimationDirectionDown;

CGFloat delay = 0.0f;

if (repeatCountArray.count != 0) {
for (NSInteger i = 0; i < repeatCountArray.count; i++) {
NSNumber *repeat = [repeatCountArray objectAtIndex:i];
NSInteger repeatCount = repeat.integerValue;
NSNumber *willDisplayNum = [willDisplayNums objectAtIndex:i];
UILabel *cell = [self.cellArray objectAtIndex:i];
CGFloat startDuration = 0;
//当不是一个完整0~9~0循环时,只进行一个Single动画(我将这里的动画分为两类:single和multi)
if (repeatCount == 0) {
[self makeSingleAnimationWithCell:cell duration:interval delay:delay animationCount:repeatCountArray.count displayNumber:willDisplayNum.integerValue];
}else {
//当>=一个循环时,进行multi动画
if (direction == ScrollAnimationDirectionUp) {
//此处计算三个部分的动画时间
startDuration = interval * (10 – [self getDisplayNumberOfCell:cell]) / ceilf(fabs(changeNumber / pow(10, i)));
CGFloat cycleDuration = interval * 10 / fabs(changeNumber / pow(10, i));
if (repeatCount == 1) {
cycleDuration = 0;
}
CGFloat endDuration = bufferModulus * pow(willDisplayNum.integerValue, 0.3) / (i + 1);
NSDictionary *attribute = @{keyStartDuration: @(startDuration),
keyStartDelay: @(delay),
keyCycleDuration: @(cycleDuration),
keyEndDuration: @(endDuration),
keyRepeatCount: @(repeatCount – 1),
keyDisplayNumber: willDisplayNum};
[self makeMultiAnimationWithCell:cell direction:direction animationCount:repeatCountArray.count attribute:attribute];
}else if (direction == ScrollAnimationDirectionDown) {
startDuration = interval * ([self getDisplayNumberOfCell:cell] – 0) / ceilf(fabs(changeNumber / pow(10, i)));
CGFloat cycleDuration = interval * 10 / fabs(changeNumber / pow(10, i));
//此处可能有些疑问,因为这个repeatCount不是真正循环的次数,是cycle的次数加end部分的1,所以如果repeat为1 真正循环次数是0
if (repeatCount == 1) {
cycleDuration = 0;
}
CGFloat endDuration = bufferModulus * pow(10 – willDisplayNum.integerValue, 0.3) / (i + 1);
NSDictionary *attribute = @{keyStartDuration: @(startDuration),
keyStartDelay: @(delay),
keyCycleDuration: @(cycleDuration),
keyEndDuration: @(endDuration),
keyRepeatCount: @(repeatCount – 1),
keyDisplayNumber: willDisplayNum};
[self makeMultiAnimationWithCell:cell direction:direction animationCount:repeatCountArray.count attribute:attribute];
}
}
delay = delay + startDuration;
}
}
}
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
动画分为single和multi两种,multi动画里包括3个部分start,cycle,end;start部分是当前数值~9的动画,cycle部分是0~9的数次循环,end部分为0~*终数值(此处以数字增加举例,数字减少时相反)

下面贴出single和multi两种动画的代码:
– (void)makeSingleAnimationWithCell:(UILabel *)cell duration:(CGFloat)duration delay:(CGFloat)delay animationCount:(NSInteger)count displayNumber:(NSInteger)displayNumber{

[UIView animateWithDuration:duration delay:delay options:UIViewAnimationOptionCurveEaseOut animations:^{
[self setScrollCell:cell toNumber:displayNumber];
} completion:^(BOOL finished) {
//当动画结束,检查是否有待执行的动画
[self checkTaskArrayWithAnimationCount:count];
NSLog(@”single animation finish!”);
}];
}
– (void)makeMultiAnimationWithCell:(UILabel *)cell
direction:(ScrollAnimationDirection)direction
animationCount:(NSInteger)count
attribute:(NSDictionary *)attribute{
NSNumber *startDuration = [attribute objectForKey:keyStartDuration];
NSNumber *cycleDuration = [attribute objectForKey:keyCycleDuration];
NSNumber *endDuration = [attribute objectForKey:keyEndDuration];
NSNumber *repeatCount = [attribute objectForKey:keyRepeatCount];
NSNumber *willDisplayNum = [attribute objectForKey:keyDisplayNumber];
NSNumber *startDelay = [attribute objectForKey:keyStartDelay];

[UIView animateWithDuration:startDuration.floatValue delay:startDelay.floatValue options:UIViewAnimationOptionCurveEaseIn animations:^{
//这是开始部分的动画
[self setScrollCell:cell toNumber:(direction == ScrollAnimationDirectionUp)?10 : 0];
} completion:^(BOOL finished) {
NSLog(@”start animation finish!”);
//开始动画结束后将cell归位到循环开始的地方
[self setScrollCell:cell toNumber:(direction == ScrollAnimationDirectionUp)?0 : 10];

if (cycleDuration.floatValue == 0) {
//当循环次数为0,直接执行结束部分的动画
[UIView animateWithDuration:endDuration.floatValue delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
[self setScrollCell:cell toNumber:willDisplayNum.integerValue];
} completion:^(BOOL finished) {
[self checkTaskArrayWithAnimationCount:count];
NSLog(@”end animation finish!”);
}];
}else {
//否则进入循环动画
[UIView animateWithDuration:cycleDuration.floatValue delay:0 options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionRepeat animations:^{
[UIView setAnimationRepeatCount:repeatCount.integerValue];
switch (direction) {
case ScrollAnimationDirectionUp:
[self setScrollCell:cell toNumber:10];
break;
case ScrollAnimationDirectionDown:
[self setScrollCell:cell toNumber:0];
break;
default:
break;
}
} completion:^(BOOL finished) {
NSLog(@”cycle animation finish!”);
[self setScrollCell:cell toNumber:(direction == ScrollAnimationDirectionUp)?0 : 10];
//这是循环后的结束动画
[UIView animateWithDuration:endDuration.floatValue delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
[self setScrollCell:cell toNumber:willDisplayNum.integerValue];
} completion:^(BOOL finished) {
[self checkTaskArrayWithAnimationCount:count];
NSLog(@”end animation finish!”);
}];
}];
}
}];
}
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
核心动画部分代码都已经贴出来了,下面看下一些privite方法

首先是每个动画结束的checkTaskArrayWithAnimationCount:方法
//这里传入的cout是一次变化总共的动画次数
– (void)checkTaskArrayWithAnimationCount:(NSInteger)count {
//每个动画结束,计数加一,当等于count时,说明所有动画都已经结束了
self.finishedAnimationCount++;
if (self.finishedAnimationCount == count) {
self.finishedAnimationCount = 0;
if (self.taskArray.count != 0) {
NSDictionary *task = [self.taskArray objectAtIndex:0];
[self.taskArray removeObject:task];
NSNumber *displayNumber = [task objectForKey:keyTaskDisplayNumber];
NSNumber *changeNumber = [task objectForKey:keyTaskChangeNumber];
NSNumber *interval = [task objectForKey:keyTaskInterval];
//如果taskArray里有task,则进行下一次动画
[self playAnimationWithChange:changeNumber.integerValue displayNumber:displayNumber interval:interval.floatValue];
}else {
self.isAnimation = NO;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
计算列数的方法:calculateRowNumber:
这个方法很简单,就不做解释了

– (NSInteger)calculateRowNumber:(NSInteger)number {
NSInteger rowNumber = 1;
while ((number = number / 10) != 0) {
rowNumber++;
}
return rowNumber;
}
1
2
3
4
5
6
7
使指定cell显示相应数值的方法:setScrollCell: toNumber:
– (void)setScrollCell:(UILabel *)cell toNumber:(NSInteger)number {
CGFloat originX = cell.frame.origin.x;
CGFloat floatNumber = number;
CGFloat y = – ((CGFloat)floatNumber / 11) * self.cellHeight;
cell.frame = CGRectMake(originX, y, self.cellWidth, self.cellHeight);
}
1
2
3
4
5
6
计算repeatCount的方法:getRepeatTimesWithChangeNumber: displayNumber:
这个方法我想用几个小例子来解释,等看完这个例子,再去看代码就不会觉得那么难懂了:比如当前展示数为521,要让它改变到530,注意个位数要循环多少次呢,其实它只有开始部分和结束部分,并没有循环部分,我代码的操作是将个位置0,520和530,530-520=10,10/10^1=1所以得到的循环次数是1,而我前面说过这个1不是真正的循环次数,而是循环次数加上end部分的1,所以当repeatCount=1时,只会有开始动画和结束动画。再举一个例子:521和542,先置0,520和540,520-540=20,20/10^1=2,repeatCount=2,而真正循环次数为1,我们看,当521经过开始动画变位530,随后经过一个循环变为540,再经过结束动画到达542,那么真正循环确实是1次,说明这种算法没有问题。当然这只举了个位为例,其它位以此类推都是相同的。

– (NSArray<NSNumber *> *)getRepeatTimesWithChangeNumber:(NSInteger)change displayNumber:(NSInteger)number{
NSMutableArray *repeatTimesArray = [[NSMutableArray alloc] init];
NSInteger originNumber = number – change;
if (change > 0) {
do {
number = (number / 10) * 10;
originNumber = (originNumber / 10) * 10;
NSNumber *repeat = @((number – originNumber) / 10);
[repeatTimesArray addObject:repeat];
number = number / 10;
originNumber = originNumber / 10;
} while ((number – originNumber) != 0);
}else {
do {
number = (number / 10) * 10;
originNumber = (originNumber / 10) * 10;
NSNumber *repeat = @((originNumber – number) / 10);
[repeatTimesArray addObject:repeat];
number = number / 10;
originNumber = originNumber / 10;
} while ((originNumber – number) != 0);
}
return repeatTimesArray;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
将一个数字准换为各位上应当展示的数值的方法:getCellDisplayNumberWithNumber:
举个例子就是这个数字是:521,则返回一个数组:[@1,@2,@5]

– (NSArray<NSNumber *> *)getCellDisplayNumberWithNumber:(NSInteger)displayNumber {
NSMutableArray *displayCellNumbers = [[NSMutableArray alloc] init];
NSInteger tmpNumber;
for (NSInteger i = 0; i < self.rowNumber; i++) {
tmpNumber = displayNumber % 10;
NSNumber *number = @(tmpNumber);
[displayCellNumbers addObject:number];
displayNumber = displayNumber / 10;
}
return displayCellNumbers;
}
1
2
3
4
5
6
7
8
9
10
11
获得指定cell展示的数值的方法:getDisplayNumberOfCell:
– (NSInteger)getDisplayNumberOfCell:(UILabel *)cell {
CGFloat y = cell.frame.origin.y;
CGFloat tmpNumber = (- (y * 11 / self.cellHeight));
NSInteger displayNumber = (NSInteger)roundf(tmpNumber);
return displayNumber;
}
1
2
3
4
5
6
计算动画总时间的方法:getIntervalWithOriginalNumber: displayNumber:
这个时间是根据*高位改变的位数来计算的,位数越高,每改变一次时间就越长。

static const CGFloat normalModulus = 0.3f;

– (CGFloat)getIntervalWithOriginalNumber:(NSInteger)number displayNumber:(NSInteger)displayNumber {

NSArray *repeatTimesArray = [self getRepeatTimesWithChangeNumber:displayNumber – number displayNumber:displayNumber];
NSUInteger count = repeatTimesArray.count;
NSInteger tmp1 = displayNumber / (NSInteger)pow(10, count – 1);
NSInteger tmp2 = number / (NSInteger)pow(10, count – 1);

NSLog(@”tmp1:%ld tmp2:%ld”, (long)tmp1, (long)tmp2);
NSInteger maxChangeNum = labs(tmp1 % 10 – tmp2 % 10);

return normalModulus * count * maxChangeNum;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
代码部分到此结束
————————————————
版权声明:本文为CSDN博主「太肥小次郎」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dp948080952/article/details/51871485

Linux 下有 supervisord, Windows 下就没有类似的?各位还有哪些管理服务器的黑科技?

以前服务器上就跑几个程序,每次重启服务器,都是再手动启动程序

通过 Windows 的计划任务或者设置开机启动,能自动启动

但是

如果服务器不重启,程序挂掉了,有类似 supervisord 的监控程序么?

不知道大家在管理服务器方面还有哪些黑科技?比如管理多台服务器、再比如快速部署之类的

谢谢大家

第 1 条附言  ·  2017-08-24 08:43:56 +08:00

那些收藏的 v 友们为什么不顺手给一楼点个感谢?
9 条回复    2017-08-25 16:26:03 +08:00
wxlg1117
    1

wxlg1117   2017-08-24 01:39:44 +08:00 via Android   ❤️ 6

alwaysup 或者 firedaemon,都挺好用
only0jac
    2

only0jac   2017-08-24 08:06:47 +08:00 via Android

@wxlg1117 感谢,前者比后者小很多
hjc4869
    3

hjc4869   2017-08-24 08:18:45 +08:00 via Android

计划任务可以自动重启挂掉的进程。taskschd.dll 可以用 vbs/js 脚本或者 C#/C++等语言调用,也可以 powershell remote,部署起来方便*了。
only0jac
    4

only0jac   2017-08-24 08:42:47 +08:00 via Android

@hjc4869 计划任务只能定时执行或者重启时执行吧?你说的那种是不是基于事件消息?那个事件消息,普通程序挂掉是没有消息的,mysql 有
hjc4869
    5

hjc4869   2017-08-24 09:18:36 +08:00

@only0jac Settings 里有,If the task fails, restart every ____,这个 fails 就是指 task 的那个进程 exit code 不为 0。
当然,你必须让你启动的那个进程一直活着。如果你想监控它子进程的状态,就办不到了。
only0jac
    6

only0jac   2017-08-24 23:40:51 +08:00

@hjc4869 谢谢,不过有缺陷,你说的计划任务 If the task fails,是指在计划任务主动启动程序时,失败的话会再次重试,如果这个程序中途挂掉,就没办法自动拉起来
hjc4869
    7

hjc4869   2017-08-25 00:34:25 +08:00

@only0jac 然而对于一个进程来讲没有所谓的“启动程序时”和“中途”的区分,创建一个进程不管过多久返回非 0 值在 taskschd 看来都是 failed。
only0jac
    8

only0jac   2017-08-25 09:00:48 +08:00 via Android

@hjc4869 多谢,我描述一个场景,比如,服务器正常运行着几个程序,忽然其中一个挂掉了,我希望能自动拉起这个挂掉的程序并邮件通知我
QQ2171775959
    9

QQ2171775959   2017-08-25 16:26:03 +08:00

监控方面,可以利用的工具还是蛮多。阿里监控也可以啊。

iOS开发中的Assets.xcassets和Assets.car

写在前面
在iOS开发中,*终项目的大小跟项目中的图片关系很大,因为代码的大小是很小的,可以说影响*终项目大小的主要就是项目中的图片资源,所以如何组织项目中的图片资源显得尤为重要。

正文
Assets.xcassets文件
当你创建一个空项目时,会默认帮你生成一个Assets.xcassets文件,当然你也可以自己创建:

%title插图%num
如果没有特殊的需求,一个工程里有一个xcassets文件就可以了,项目中所有图片都可以放在里面,为了组织更有条理更清晰,可以在这里面用文件夹分类,可以按照功能的模块来分类,比如一个模块的功能放在一个文件夹中。

%title插图%num

在这个文件中默认会有一个AppIcon的文件,可以将相对应大小的应用图标直接拖到右边对应大小的地方,就可以了。处理AppIcon这里面还可以放LaunchImage,只要在这个AppIcon这个栏内右键新建即可,还可以针对不同设备新建不同的LaunchImage和AppIcon

%title插图%num

如果只想往项目里加入一张普通的图片,就选择New Image Set,这个Image Set实质就是一个文件夹,里面包括不同分辨率的图片和一个Contents.json文件,内容如下包含着这个图片集中图片文件的基本信息,同AppIcon一样,只要将自己的图片拖到相应的位置即可,然后在项目中使用imageNamed:方法时,使用的名字是这个Image Set的名字,而不是图片真实的名字,这里可以注意的是图片真实的名字虽然无所谓,但*好还是按照一些规律来,*好不要出现中文,而Image Set的名字可以定义一些规则:如用image开头,中间加上模块名,*后加上图片本身的名字,中间用下划线连接,当然这里只是我个人的提议,我们项目中图片就是用这种方式进行命名的。

{
“images” : [
{
“idiom” : “universal”,
“filename” : “sPECIALOFFERIconSurvey1.png”,
“scale” : “1x”
},
{
“idiom” : “universal”,
“filename” : “sPECIALOFFERIconSurvey1@2x.png”,
“scale” : “2x”
},
{
“idiom” : “universal”,
“filename” : “sPECIALOFFERIconSurvey1@3x.png”,
“scale” : “3x”
}
],
“info” : {
“version” : 1,
“author” : “xcode”
}
}

 

将图片放在xcassets文件中的好处:

组织清晰
不同功用的图片有专门的格式
不同分辨率的图片好管理
工程打包后会对图片进行压缩
这里我要着重说一下第四点,包的大小,如果将图片直接放在工程目录下面,项目打包后图片文件也是散落在包里面,而且不会对图片进行压缩,而如果放在xcassets中,在打包后会将这些图片(除了AppIcon和LaunchImage,这两种图片是直接放在包中的)统一压缩成一个Assets.car的文件,大大减小包的大小,具体是几倍的关系我记不清了,但是相当的可观。

Assets.car文件
说完Assets.xcassets,那么说说由它生成的Assets.car文件,这个文件是一种压缩文件。
我们在开发过程中肯定会遇到一种情况就是把一个ipa的包解压出来看看里面有哪些图片,不管是不是自己的项目,总可能会有这种需求,那如果图片都在Assets.car中该怎么获取呢,直接解压是不行的,这时候就需要用到一个命令行工具叫cartool,这是一个开源软件,可以从github下载,这里给出github地址:https://github.com/steventroughtonsmith/cartool

使用方法是cartool Assets.car outputDirectory

还是给大家上一张图

%title插图%num

先将ipa解压缩,然后显示app包的内容,将Assets.car复制到桌面,然后就可以像上图一样,当然要在桌面上新建一个文件夹,如果你想直接把图片解压到桌面上我也是不介意的!

后记
这篇博客大概就是这些内容,主要就是给大家分享这个叫做cartool的工具还有就是科学的组织项目中的图片,当然要感谢大神写出这样方便的工具。

话说这个月已经过去一半了,然而这是这个月的*篇博客,还需加油,努力学习,努力写博客,分享知识,分享快乐,每月四篇一定要实现。

服务器被一大波 CC 攻击,应该怎么防一下?

如图: http://wz.yongshou.me/attachment/images/5/2016/12/qfiEBdSZe7BSR4kIRZjs7g444jeRii.png

早上起来发现成这个样子,好凶残, CPU 分分钟过 100 ,有解决办法吗?大神们….

17 条回复    2017-08-04 11:54:43 +08:00
haopic
    1

haopic   2016-12-01 08:43:33 +08:00

没办法~
qcloud
    2

qcloud   2016-12-01 08:49:07 +08:00 via iPhone

防御 cc 。可以尝试卸载安全狗,然后用云群配置下,简单的 cc 可以防御
KenGe
    3

KenGe   2016-12-01 08:52:50 +08:00

安全狗是可以防 cc 的啊
daolin998
    4

daolin998   2016-12-01 08:58:10 +08:00

@KenGe 抗不住
xuhaoyangx
    5

xuhaoyangx   2016-12-01 08:59:48 +08:00

@KenGe 效果堪忧
bhqt
    6

bhqt   2016-12-01 09:53:09 +08:00

限制单 IP 时段内访问次数,页面增加刷新时间限制,服务器上做针对超过限制 IP 的自动黑名单。
daolin998
    7

daolin998   2016-12-01 10:19:57 +08:00

@bhqt 全部是随机外国 IP 啊,肉鸡比较多
yu1u
    8

yu1u   2016-12-01 10:57:37 +08:00

挂 cdn 来抗就行了,基本这些 cc 小规模的 ddos 都能抗下
daolin998
    9

daolin998   2016-12-01 11:19:47 +08:00 via iPhone

@yu1u 没抗住……百度云加速 360 都试了
adminsvv
    10

adminsvv   2016-12-01 11:30:00 +08:00

上 CDN ,推荐换 ip 然后再上 CDN 。前段时间也被 CC 打了 3 天,一开机宽带就跑满。*后还是 CDN 稳。

daolin998
    11

daolin998   2016-12-01 11:55:43 +08:00 via iPhone

@adminsvv 求推荐个稳定 cdn
xiaoz
    12

xiaoz   2016-12-01 12:32:21 +08:00 via iPhone

用智能解析,将国外指向 127.0.0.1 可以解决大部分国外 ip
vitas
    13

vitas   2016-12-02 02:17:28 +08:00

openresty or {nginx+lua} + httpguard
实测有效
johnnyR
    14

johnnyR   2017-04-02 16:31:34 +08:00

@adminsvv 上了 CDN 再上 CND ?怎么上呢
cpublic
    15

cpublic   2017-04-30 22:12:52 +08:00

我用百度云加速抗住了百万级别的 CC 攻击!昨天下午 14 点开始到今天早上 9 点多开始减缓了!
daolin998
    16

daolin998   2017-04-30 22:29:12 +08:00 via iPhone

@cpublic 不知道是不是用百度云特别容易被攻击啊,*近几天平均每小时被 cc 攻击 300 万次,一条六七万次,只能开启强力防护
fengniao
    17

fengniao   2017-08-04 11:54:43 +08:00

@daolin998 可以试试知道创宇抗 D 保,这个产品据说是国内做 CC 做得*好的了

ios开发:Xcode8制作framework及使用

前言
网上关于Framework制作的教程数不胜数,然而都过于陈旧,*新的也是使用Xcode7的教程,而且有些设置也只给出步骤,并没有给出原因,而且按照有些教程制作出的framework还有些问题,所以我把自己制作framework的过程记录下来,并且使用的是*新的Xcode8环境。

正文
首先新建一个项目,选择Cocoa touch Framework

然后随便取一个名字

然后将要封装成framework的代码拖入工程

选择工程文件>target*项>Build Setting>搜索linking,然后几个需要设置的选项都显现出来,首先是Dead Code Stripping设置为NO,网上对此项的解释如下,大致意思是如果开启此项就会对代码中的”dead”、”unreachable”的代码过滤,不过这个开关是否关闭,似乎没有多大影响,不过为了完整还原framework中的代码,将此项关闭也未曾不可。

The resulting executable will not include any “dead” or unreachable code

然后将Link With Standard Libraries关闭,我想可能是为了避免重复链接

*后将Mach-O Type设为Static Library,framework可以是动态库也可以是静态库,对于系统的framework是动态库,而用户制作的framework只能是静态库。

然后将需要公开的头文件从Project中拖入Public,至于是否需要将私有的头文件拖入Private,我觉得直接放在Project中即可,若是Private中有头文件,打包以后的framework中会多出一个Private的文件夹包含着放入Private的头文件,不过我觉得如果是私有*好还是不要让别人看到。

为了对一些特殊机型的支持,添加armv7s架构,当然不添加也没什么问题,只有5和5c使用了此架构。

下面就开始编译了,先选择Generic iOS Device,按下Command+B:Build一下

然后随意选择一个模拟器再次Build一下

找到Build后的framework

我们看到总共有两个文件夹,上面是真机编译生成的文件,下面是模拟器编译生成的文件。

然后用命令行将下面两个文件进行合成

将合成后的文件输出到桌面之上,这里解释一下这看似很复杂的命令:lipo -create +上面两个文件的路径 + -output + 合成后文件的输出路径

然后用输出在桌面上的YuXinSDK替换掉前面Debug-iphoneos中framework中的YuXinSDK

将这做好的framework直接拖入要使用的工程中

注意将Copy items if needed打勾

然后在工程的search path中加上此framework的Headers的路径

本以为大功告成,按下了Command+R悠闲的等着编译运行,然而在链接的时候却出了问题

很经典的错误,库中缺少了i386架构的文件,这时我就比较困惑了,明明将模拟器生成的库文件合并进来了,怎么在模拟器上编不通呢!用lipo命令查看一下,果然没有i386架构的文件

再看一下用模拟器生成的framework

居然只有x86_64架构

我换成iPhone 4s模拟器,编译后生成的文件居然包含了i386

经过我的测试,在iPhone5之前编译的framework都是i386架构,iPhone 5s之后都是x86_64架构,然而在所有模拟器上运行时,这两种架构缺一不可,然后我将这两种架构的framework合并在与真机的合并

拖入工程后终于能够正常运行了

后来经过摸索发现之前按照网上的教程却把自己坑了

开始将上图中的选项设为YES,导致其编译时只生成当前机器的框架,将其设置为NO后,发现用模拟器编译后生成的framework同时包含x86_64和i386架构。

bitCode错误
在项目中引入静态库后,archive的时候又会出现错误

bitcode bundle could not be generated because … was built without full bitcode.All object files and libraries for bitcode must be generated from Xcode Archive or Install build for architecture arm64
1
Bitcode是苹果在Xcode7及以后推出的新功能。用于代码的二次编译,针对CPU进行优化,编译工作由苹果AppStore后台来完成。
针对iOS是可选项,默认打开。watchOS 和 tvOS 是必选项。

所以需要打开库工程的此选项并加上-fembed-bitcode参数,重新编译

如此archive时就不会出问题了,到此我在制作使用framework时遇到的问题都在这里了。

总结
架构
对于模拟器来说4s和5的模架构是i386的32位架构,5s至今是x86_64的64位架构。对于真机来说3GS~4s是armv7架构,5和5c是armv7s架构,5s至今是arm64架构

静态库
静态库: 链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。

动态库
动态库:链接时不会拷贝至可执行文件中,运行时动态加载进内存,供程序调用,只加载一次,多个程序可以共用。

一个iOS项目总结(二):界面

写在前面
今年暑假,自己独立完成了一个简单的iOS的APP,是一个bbs的客户端,叫做喻信星空。现在正在测试,准备将其上架app store。但是光做项目不做总结肯定不行,所以这里写篇博客,把项目里遇到的坑都记录下来,所以这篇博客里肯定是有干货的,所以如果你看到了这里,希望你能把它看完,并顶我一下(^-^)

这是我项目总结的系列博文中的第二篇,此篇文章中列出了在界面编程过程中遇到的问题和一些心得体会,分享给大家,如果有错误或不妥的地方,欢迎大家在评论中指出!

正文
LaunchImage的缺失问题
对于iOS的应用,有一个特点就是必须要有一张LaunchImage,在工程的设置里可以进行设置,LaunchImage可以有两种选择一个是自己提供图片,另一个是LaunchScreen.storyboard。
如果LaunchImage缺失,在iPhone5以后的设备上会出现无法全屏的问题,在应用的上方和下方会有两条黑条,如下方图片。

而且编译时会出现警告:Missing “Default-568h@2x.png” launch image,当你点击这个警告时,Xcode会提示你是否添加一张启动图片,如果你选择添加,Xcode则会帮你添加一张全黑的640 × 1136的启动图片,而此时再次编译运行,你会发现应用刚启动时是黑屏,进入应用后不能全屏的问题消失了。

当然你可以用自己的启动图片来替换这张启动图,或者是使用storyboard,而在我看来使用storyboard应该会更好,storyboard不仅易于适配不同设备,也是苹果所推荐的。

启动动画
现在的应用大多都有启动动画,当然我也想加在这个客户端中,而iOS的APP又必须要有LaunchImage,那为何不将LaunchImage和启动动画结合在一起,想到这里我十分激动,这样的结合感觉十分完美。

于是到网上搜索了一番,发现了将这二者结合起来的方法:

UIViewController *viewController = [[UIStoryboard storyboardWithName:@”LaunchScreen” bundle:nil] instantiateViewControllerWithIdentifier:@”LaunchScreen”];

通过此法,便能得到LaunchScreen的VC,如此问题便简单多了,通过这个VC获取VC的view,用这个view便可以制作动画了,如下图。

登录界面
登录界面看似简单,但也是有玄机在这其中,比如键盘的适配,当点击输入框,键盘便会弹出,此时就有可能遮挡住登录按键或是其他输入框,这个问题该如何解决呢,其实不难,iOS中在键盘状态改变时会发送全局通知,只要注册观察者接收这些通知即可,需要注意的是注册的通知*好是WillShow和WillHiden,这样会显得自然些,不然会有比较严重的延迟:

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWasShown:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillBeHidden)
name:UIKeyboardWillHideNotification
object:nil];

 

然后在这下面两个方法里处理键盘弹起和收回的情况,在

– (void)keyboardWasShown:(NSNotification *)notification {
NSDictionary *info = [notification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;

UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.f, 0.f, kbSize.height, 0.f);
self.backgroundView.contentInset = contentInsets;
self.backgroundView.scrollIndicatorInsets = contentInsets;

CGRect rect = self.loginButton.frame;
rect.size.height += rect.size.height;
[self.backgroundView scrollRectToVisible:rect animated:YES];

}
– (void)keyboardWillBeHidden {
self.backgroundView.contentInset = UIEdgeInsetsZero;
self.backgroundView.scrollIndicatorInsets = UIEdgeInsetsZero;
}

 

系统发送的通知中,包含了键盘高度的信息,拿到这个高度后,便可以将登录的界面向上移动。

这里我的登录界面是放在一个UIScrollView之上,其实和微信的登录界面有些相似,可以随着你的手指上下滚动,我很同意我一个同事的说法,他说每个界面都应该是一个ScrollView,都应该能够响应用户是手势,就是用户在滑动或是拖动的时候,都应该去响应,我觉得这会让用户觉得很爽,会让人觉得比较灵动。

封装简单的tabbar
由TabBar和几个VC作为主界面的应用很是常见,当然系统提供了UITabBarController和UITabBar,不过如果想要定制想要的样式的tabbar,用系统的似乎就有些困难了,像tabbar这样并不复杂的控件,自己写一个是很方便的,我想要一个只有图片没有文字的tabbar,于是就自己写了一个DPTabBar。

首先封装一个DPTabBarItem,继承自UIButton,其实想来也简单,tabbar中不同的tab其实就是几个button,此处使用button作为cell十分合适,而且button具有选中和不选中的属性,而且可以为两种状态设置不同的图片,在此处十分适合。

@interface DPTabBarItem : UIButton

– (instancetype)initWithImage:(UIImage *)image selectedImage:(UIImage *)selectedImage;

@end

 

随后便是封装TabBar了

@class DPTabBarItem;

@protocol DPTabBarDelegate;

@interface DPTabBar : UIView

@property (nonatomic, readwrite, weak) id<DPTabBarDelegate> delegate;
@property (nonatomic, readwrite, assign) NSUInteger selectedIndex;

– (instancetype)initWithTabBarItems:(NSArray<DPTabBarItem *> *)items;

@end

 

初始化方法只有一个,便是使用DPTabBarItem进行初始化,如果不设置*开始的selectedIndex,则默认为0,此外还有两个属性一个表示被选中的tab,另一个是代理,该代理定义如下:

@protocol DPTabBarDelegate <NSObject>

– (void)itemDidSelectAtIndex:(NSUInteger)index;

@end

 

当选中的Tab变化时,调用此代理方法,代理根据index来调整显示的页面。

如何写出一个优雅的tableView
首先上一张图看一下这个tableView:

这是一个很简单的tableView,这里面大部分cell显示的内容都是固定的东西,所以我们就当这个tableView的数据源都是固定的而不是从网上拉到的数据,所以这一部分内容是针对一个简单的tableView,如何优雅的进行组织。

Model
MVC的设计模式是十分经典的模式,在iOS开发中这种模式也是十分常见,而这个tableView也是使用MVC的设计模式,既然是MVC,首先肯定是要有Model,用于保存展示的数据

@interface DPProfileItem : NSObject

@property (nonatomic, strong) UIImage *userImage;
@property (nonatomic, strong) NSString *title1;
@property (nonatomic, strong) NSString *title2;
@property (nonatomic, assign) NSIndexPath *indexPath;

@end

 

此处我的这个Model叫做DPProfileItem,而正常情况之下Model都只是在头文件中进行声明,而.m文件中很少有实现代码,而生成Model的代码却又专门放在一个Manager或dataController类似的单例中,又或是直接将生成的工作放在容纳tableView的VC中,如果需要从网络拉取数据,那么写一个dataController来生成Model倒也是很正常的写法,但是无论如何放在VC中却真是不够优雅。

那么Model生成的代码到底应该放在哪里呢?我觉得*合适的地方应是放在Model的实现文件中,首先Model的生成与Model本身联系十分紧密,放在Model中体现了较高的内聚性,就相当于Model的初始化方法;其次Model的实现文件中本就几无代码,加上这部分代码,显得Model更加饱满而不是仅仅只有头文件的声明;再者这种写法和工厂方法有异曲同工之妙:+ (DPProfileItem *)itemWithType:(DPProfileItemType)type;

tableViewCell
如何优雅的重写UITableViewCell

@interface DPProfileCell : UITableViewCell

@property (nonatomic, weak) id<DPProfileCellDelegate> delegate;

– (void)fillDataWith:(DPProfileItem *)item indexPath:(NSIndexPath *)indexPath;

@end

 

我们都知道tableView具有复用cell的机制,我们在dataSource的代理中使用此方法dequeueReusableCellWithIdentifier:来获取cell然后然后对其进行数据填充,而如果tableView中如果有不同类型的cell,那是否要定义多个继承自UITableViewCell的cell,其实并不需要,我们只要重写一个方法,再定义几个ReuseIdentifier即可:

@implementation DPProfileCell
– (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
if ([reuseIdentifier isEqualToString:DPProfileNormalCellReuseIdentifier]) {
self.type = DPProfileCellTypeNormal;
}
else if ([reuseIdentifier isEqualToString:DPProfileUserCellReuseIdentifier]) {
self.type = DPProfileCellTypeUser;
}
else if ([reuseIdentifier isEqualToString:DPProfileSwitchCellReuseIdentifier]) {
self.type = DPProfileCellTypeSwitch;
}
}
return self;
}
@end

 

然后根据ReuseIdentifier生成不同的cell。

数字的意义
在代码中经常会出现一些数字常量,而在承载tableView的VC中,数字的常量也是很多的,尤其是在tableView的代理中我们需要根据indexPath去判断section、row。

typedef NS_ENUM(NSUInteger, DPProfileSection) {
DPProfileSectionOne = 0,
DPProfileSectionTwo = 1,
DPProfileSectionThree = 2,
DPProfileSectionFour = 3,
DPProfileSectionFive = 4
};

typedef NS_ENUM(NSUInteger, DPProfileRow) {
DPProfileRowOne = 0,
DPProfileRowTwo = 1,
DPProfileRowThree = 2,
DPProfileRowFour = 3
};

 

与其直接使用数字,不如定义一个枚举让数字的意义显得更加明显,除此之外其他的数字数字都可以定义为静态常量

static const NSUInteger rowNumOfSectionOne = 1;
static const NSUInteger rowNumOfSectionTwo = 1;
static const NSUInteger rowNumOfSectionThree = 4;
static const NSUInteger rowNumOfSectionFour = 2;
static const NSUInteger rowNumOfSectionFive = 1;
static const NSUInteger numOfSection = 5;

 

总而言之,尽量不要在代码中直接使用数字常量,让自己的代码具有更好的可读性、维护性。拿上面中的numOfSection举例,如果在代码中多次用到该常量,如果不定义一个静态常量,当你想要修改此值时你便要去代码中各处寻找一一修改,如果你少改了一个地方那么就可能出现某些bug,而如果你定义了一个常量,当你想要修改时,只要修改此处便可,也不用担心bug的出现。

UITableView自适应高度
UITableView可以算是iOS应用的中*常用的控件了,在我的这个应用中大部分的页面都主要由UITableView组成,对于cell高度固定的UITableView倒是很简单,但是对于高度不定的UITableView却有许多可以优化的地方。

在UITableViewDelegate中- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;的方法用于返回tableView中cell的高度,而在tableView初始化时该方法便会调用数次,调用的次数便是UITableViewDataSource中- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section; 和- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; 返回的cell数总和,用于计算tableView的总高度,随后还会调用数次,用于返回将要显示在屏幕上的cell的高度。

所以如果要根据内容来计算不同cell的高度,便要在此方法返回前计算出每个cell的高度,然而此时cell还未生成,而cell的高度不仅由cell显示的内容决定,还会受cell中的布局影响,所以计算高度的工作不应该由展示tableView的VC来做,而是应该交给cell去做,若是放在VC之中,那么cell和VC的耦合就太高了,一旦cell的布局改变,那么计算高度的方法就要做出改变,而一般如果要是手动计算则会用下面的方法:

– (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(nullable NSDictionary<NSString *, id> *)attributes context:(nullable NSStringDrawingContext *)context NS_AVAILABLE(10_11, 7_0);
1
但是如果cell布局复杂,那么用这个方法计算高度则要写很多代码,十分复杂,而且很不灵活,那么是否能够根据cell内容自动设定高度呢,答案是有的,在iOS7中UITableView出现了新的功能可以达到此效果:estimatedRowHeight。

此属性是用于估算tableView的高度,大致计算出tableView的总高度,而此tableView高度自适应要配合着iOS6中出现的autolayout使用才可以,cell中用autolayout填充满contentView的高度和宽度,然后对tableView进行一定设置,cell的高度就能自动计算,然而这种方式却有一些让人难以忍受的缺点,在tableView滑动的过程中主要是由下往上划的过程中界面会出现抖动甚至跳跃,给人一种十分卡顿的感觉,这种情况的主要原因是因为在滚动的过程中cell的高度由估算的高度变成真实的高度造成的,本身这种新特性的引入就是为了优化性能,然而这种体验还不如不要这优化的性能。

那么是否有更加优雅的方式做到UITableView高度的自适应,经过一番搜索,找到了一个合适的方案:UITableView-FDTemplateLayoutCell至于如何使用大家可以参考这篇文章:优化UITableViewCell高度计算的那些事。

这篇文章中说到此控件具有预缓存机制,而且进行了优化:利用RunLoop空闲时间执行预缓存任务。我也没有看过源码,但至少是在使用的过程中并没有发现这个功能,此控件在tableView的data source的高度方法中即对cell进行内容填充利用autolayout计算高度,进行储存后并返回给代理中的方法,在tableView初始化的过程中即计算出所有cell的高度,后面再需要某个cell的高度时,直接获取缓存中的高度,所以此控件并非对性能做出什么优化,方便的只是高度自适应和cell高度的缓存,避免反复的计算。

模仿苹果信息应用灵动效果
首先看一下效果:

我们看到每个cell之间似乎是用弹簧连接,当滑动的时候,离的远的cell会有一定迟钝,给人一种很灵动的感觉,而此效果实现起来并不复杂,只要重写UICollectionViewFlowLayout即可,配合iOS7中出现的UIDynamic特性,下面我们就看具体实现。

首先重写UICollectionViewFlowLayout要重写3个方法:

– (void)prepareLayout;
– (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
– (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath

 

首先在prepareLayout方法中用自身实例化一个UIDynamicAnimator,然后获取每个cell的attribute,用每个cell初始化一个UIAttachmentBehavior,加入到animator中。

-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
return [_animator itemsInRect:rect];
}
– (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
return [_animator layoutAttributesForCellAtIndexPath:indexPath];
}

然后将attribute的管理全都交给animator来处理,然而到这里并没有结束,还要再看一段稍长的代码

-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
UIScrollView *scrollView = self.collectionView;
CGFloat scrollDelta = newBounds.origin.y – scrollView.bounds.origin.y;
CGPoint touchLocation = [scrollView.panGestureRecognizer locationInView:scrollView];

for (UIAttachmentBehavior *spring in _animator.behaviors) {
CGPoint anchorPoint = spring.anchorPoint;
CGFloat distanceFromTouch = fabs(touchLocation.y – anchorPoint.y);
CGFloat scrollResistance = distanceFromTouch / self.resistanceFactor;

UICollectionViewLayoutAttributes *item = (UICollectionViewLayoutAttributes *)[spring.items firstObject];
CGPoint center = item.center;
center.y += (scrollDelta > 0) ? MIN(scrollDelta, scrollDelta * scrollResistance)
: MAX(scrollDelta, scrollDelta * scrollResistance);
item.center = center;

[_animator updateItemUsingCurrentState:item];
}
return NO;
}

 

只要UICollectionView边界发生改变就会调用这个方法,在这个方法中,根据手指滑动的位置和cell的位置进行计算,调整cell中心的位置从而产生灵动的效果。

这个并不是我写的,而是喵神王巍的作品,只是对其进行了分析,这里给出其github地址:VVSpringCollectionViewFlowLayout

结语
界面部分的总结大致就是这样,如果你能看到这里,我想真心的对你说一声感谢,能够看完我上面的”胡言乱语”,*后再次复习一下此项目的github地址:喻信星空