服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - IOS - iOS通过shell脚本批量修改属性

iOS通过shell脚本批量修改属性

2021-04-22 17:50aron1992 IOS

这篇文章主要给大家分享了iOS通过shell脚本批量修改属性的相关知识点,希望我们整理的内容能够帮助到大家。

背景

公司需要做一系列的壳版本,壳版本如果内容雷同提交到app store会有被拒绝的风险,除了我在上一篇文章中说道的在壳版本中注入混淆的代码,防止被苹果检测到内容太过雷同而导致审核被拒绝。还有另一种可行的方法是批量修改源文件中的类名、属性、方法名称等会在二进制文件中留下符号标记的信息,绕过苹果的机器审核。
这篇文章介绍的是如何使用脚本批量修改属性名称,后续还有系列的包括使用脚本批量修改类名称、方法名称等信息的文章。

结果

下面是执行脚本替换了属性的结果图,脚本把所有需要替换的属性添加了abc后缀,当然依然是可以正常编译运行的

源码:https://gitee.com/dhar/yttinjectedcontentkit

iOS通过shell脚本批量修改属性

分析

原理分析

objc代码中的类名、属性、方法、源文件路径等信息最终会被打包到二进制文件中,保存在二进制文件中的.sym符号表段中,可以使用objdump -t命令查看二进制符号信息,以下的命令把objdump -t的结果写入到文件injectedcontentkit_example_symbols中去。

?
1
objdump -t injectedcontentkit_example > injectedcontentkit_example_symbols

文件的内容会很大,所以选择了几个代表性的内容说明:

?
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
0000000100026350 l d __text,__text  __text
# 这里保存的是类源文件的路径符号信息
0000000000000000 l d *und*  /users/aron/putaoworkspace/project/sscatch/devpods/injectedcontentkit/injectedcontentkit/classes/composer/pubsearchdatacomposer.h
 
# 这里保存的是属性对应的var信息
0000000000000000 l d *und*  _objc_ivar_$_textcarditem._title
0000000000000000 l d *und*  _objc_ivar_$_textcarditem._showreact
0000000000000000 l d *und*  _objc_ivar_$_textcarditem._topchart
0000000000000000 l d *und*  _objc_ivar_$_textcarditem._reaction
 
# 这里保存的是属性信息对应的getter方法信息
00000001000264a0 l  f __text,__text -[textcarditem title]
00000001000264c0 l  f __text,__text -[textcarditem showreact]
00000001000264f0 l  f __text,__text -[textcarditem topchart]
0000000100026510 l  f __text,__text -[textcarditem settopchart:]
 
# 这里保存的是属性信息对应的setter方法信息
00000001000028a0 l  f __text,__text -[sscatchinvitescheduler setorganizer:]
00000001000028e0 l  f __text,__text -[sscatchinvitescheduler setinputcardback:]
0000000100002920 l  f __text,__text -[sscatchinvitescheduler setinputtextback:]
 
# 这里保存的是类文件的文件名信息
0000000000000000 l d *und*  pubsearchdatacomposer.m
000000005a937587 l d __text,__stub_helper   __stub_helper
00000001000251c0 l d __text,__text  __text

从上面可以看出,二进制中保留了很多信息和源代码有很大关系,我们做个简单的猜测苹果后台机器审查二进制的时候会通过二进制中的符号进行对比,如果两个二进制(一个主版本、一个壳版本)代码中的符号重合度超过某个阈值,就会判定这是发布壳版本的行为,而这是苹果说不允许的,所以可行的方法是修改源文件中的这些信息来绕过苹果的审查机制。

另外猜测苹果应该是不会根据代码中的流程控制来判断的,因为二进制中的控制流程已经是机器码了,反编译出来也就是汇编代码,只要稍微做点改动二进制(.text段)就会变化很大。所以从这个方面来判断就难度很大了。

步骤分析

主要有以下几个步骤

  1. 寻找到需要替换的源文件中的所有的属性,处理之后保存在配置文件中
  2. 用户自定义一个黑名单配置文件
  3. 某部分需要隔离的代码中的属性生成黑名单配置文件
  4. 把需要替换的源文件中的所有匹配的属性做批量的替换

这里说明下为什么第一步需要保存在配置文件中,因为第三步的操作有部分和第一步是相同的,所有这部分单独出来一个模块共用,都是输入一个文件夹,最终保存在指定的文件中,后面的代码中可以看到这部分。

实现

单步实现

1、寻找到需要替换的源文件中的所有的属性,处理之后保存在配置文件中

这一步的功能是客户端输入一个需要处理的源码文件夹,递归遍历该源码文件夹获取所有源码文件(.h .m 文件)。使用正则匹配找到属性名称,暂时保存到数组中,最后经过黑名单过滤、去重过滤、其他过滤条件过滤,最终把待处理的属性保存到客户端输入的输出文件中。

可以分解为一下几个小步骤

  • 递归遍历文件夹获取源码文件
  • 正则匹配源码文件的属性
  • 过滤属性(可选)
  • 保存属性到文件

这部分功能的源码如下:

文件名: getandstoreproperties.sh

该脚本在多个地方都有用到,所以作为一个单独的模块,定义了一些参数,以适应不同的应用场景。在下面可以看到使用该脚本的地方。

?
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#!/bin/bash
########################
# 脚本功能:从指定目录获取和保存属性到指定的文件
# 输入参数 -i 输入的文件夹
# 输入参数 -o 保存的文件
# 输入参数 -f 使用黑名单和自定义过滤条件的参数
# 输入参数 -c 自定义的黑名单文件
########################
 
####### 参数定义
param_input_dir=""
param_output_file=""
param_custom_filter_file=""
param_should_use_filter=0
 
####### 参数解析
while getopts :i:o:c:f opt
do
    case "$opt" in
        i) param_input_dir=$optarg
            echo "found the -i option, with parameter value $optarg"
            ;;
        o) param_output_file=$optarg
            echo "found the -o option, with parameter value $optarg"
            ;;
        c) param_custom_filter_file=$optarg
            echo "found the -c option, with parameter value $optarg"
            ;;
        f) echo "found the -f option"
            param_should_use_filter=1
            ;;
        *) echo "unknown option: $opt";;
    esac
done
 
 
####### 配置
 
# 属性黑名单配置文件
blacklist_cfg_file="$(pwd)/defaultblacklistpropertiesconfig.cfg"
 
####### 数据定义
 
# 定义保存源文件的数组
declare -a implement_source_file_array
implement_source_file_count=0
 
 
# 定义保存属性的数组
declare -a tmp_props_array
props_count=0
 
 
# mark: p384
# 递归函数读取目录下的所有.m文件
function read_source_file_recursively {
    echo "read_implement_file_recursively"
    if [[ -d $1 ]]; then
        for item in $(ls $1); do
            itempath="$1/${item}"
            if [[ -d $itempath ]]; then
                # 目录
                echo "处理目录 ${itempath}"
                read_source_file_recursively $itempath
                echo "处理目录结束====="
            else
                # 文件
                echo "处理文件 ${itempath}"
                if [[ $(expr "$item" : '.*\.m') -gt 0 ]] || [[ $(expr "$item" : '.*\.h') -gt 0 ]]; then
                    echo ">>>>>>>>>>>>mmmmmmm"
                    implement_source_file_array[$implement_source_file_count]=${itempath}
                    implement_source_file_count=$[ implement_source_file_count + 1 ];
                fi
                echo ""
            fi
        done
    else
        echo "err:不是一个目录"
    fi
}
 
 
# 读取源码中的属性,保存到数组中
# 参数一: 源码文件路径
function get_properties_from_source_file {
    local class_file=$1;
    echo "class_file=${class_file}"
 
    properties=$(grep "@property.*" ${class_file})
    ifs_old=$ifs
    ifs=$'\n'
    for prop_line in $properties; do
        echo ">>>>>${prop_line}"
 
        asterisk_seperator_pattern="\*"
        if [[ ${prop_line} =~ ${asterisk_seperator_pattern} ]]; then
            # 从左向右截取最后一个string后的字符串
            prop_name=${prop_line##*${asterisk_seperator_pattern}}
            # 从左向右截取第一个string后的字符串
            seal_pattern=";*"
            seal_pattern_replacement=""
            prop_name=${prop_name//${seal_pattern}/${seal_pattern_replacement}}
            subsring_pattern="[ |;]"
            replacement=""
            prop_name=${prop_name//${subsring_pattern}/${replacement}}
 
            if [[ ${param_should_use_filter} -gt 0 ]]; then
                grep_result=$(grep ${prop_name} ${blacklist_cfg_file})
                echo "grep_result = >>${grep_result}<<"
                custom_grep_result=""
                if [[ -n ${param_custom_filter_file} ]]; then
                    custom_grep_result=$(grep ${prop_name} ${param_custom_filter_file})
                fi
                if [[ -n ${grep_result} ]] || [[ -n ${custom_grep_result} ]]; then
                    echo "--${prop_name}--存在配置文件中"
                else
                    echo "--${prop_name}--xxx不存在配置文件中"
 
                    tmp_props_array[$props_count]=$prop_name
                    props_count=$[ props_count + 1 ]
                    echo ">>>>>>>result_prop_name=${prop_name}"
                fi
            else
                tmp_props_array[$props_count]=$prop_name
                props_count=$[ props_count + 1 ]
            fi         
        fi
    done
    ifs=$ifs_old
}
 
# 获取目录下的所有源文件,读取其中的属性
function get_properties_from_source_dir {
 
    local l_classed_folder=$1
 
    echo "获取需要处理的源文件... ${l_classed_folder}"
    # 读取需要处理目标文件
    read_source_file_recursively ${l_classed_folder}
 
    echo "读取源文件中的属性..."
    for(( i=0;i<${#implement_source_file_array[@]};i++))
    do
        class_file=${implement_source_file_array[i]};
        echo "处理源文件:${class_file}"
        get_properties_from_source_file ${class_file}
    done;
}
 
# 把获取到的属性过滤之后写入文件中
# 过滤步骤包含去重、去掉简单词汇、去掉长度少于多少的词汇
# 如果在执行的过程中遇到特殊情况,添加到黑名单配置(defaultblacklistpropertiesconfig.cfg文件中添加配置)
function post_get_properties_handle {
 
    local prop_config_file=$1
 
    # 写入文件中
    echo "# properties configs" > ${prop_config_file}
    for key in $(echo ${!tmp_props_array[*]})
    do
     # echo "$key : ${tmp_props_array[$key]}"
     echo ${tmp_props_array[$key]} >> ${prop_config_file}
    done
 
    # 去重
    cfg_back_file="${prop_config_file}.bak"
    mv ${prop_config_file} ${cfg_back_file}
    sort ${cfg_back_file} | uniq > ${prop_config_file}
    
    # 过滤
    if [[ ${param_should_use_filter} -gt 0 ]]; then
        mv ${prop_config_file} ${cfg_back_file}
        echo "# properties configs filtered" > ${prop_config_file}
        ifs_old=$ifs
        ifs=$'\n'
        # 上一行的内容
        lastline="";
        for line in $(cat ${cfg_back_file} | sed 's/^[ \t]*//g')
        do
            if [[ ${#line} -le 6 ]] || [[ $(expr "$line" : '^#.*') -gt 0 ]]; then
                # 长度小于等于6或者注释内容的行不处理
                echo "less then 6 char line or comment line"
            else
                if [[ -n ${lastline} ]]; then
                    # 上一行是非空白行
                    # 比较上一行内容是否是当前行的一部分,不是添加上一行
                    if [[ ${line} =~ ${lastline} ]]; then
                        echo "${line} 和 ${lastline} 有交集"
                    else
                        echo ${lastline} >> ${prop_config_file}
                    fi
                fi
                # 更新上一行
                lastline=${line}
            fi 
        done
        ifs=${ifs_old}
    fi
 
    # 删除临时文件
    rm -f ${cfg_back_file}
}
 
 
get_properties_from_source_dir ${param_input_dir}
post_get_properties_handle ${param_output_file}

使用以上脚本生成的配置文件 propertiesconfigs.cfg 部分如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# properties configs filtered
userrestrictionlabel
aboutusbutton
activitysamplers
addaddresspress
addresssamplers
addresstextbox
appealpress
appliedgroupedsamplers
appliedsamplers
applypress
asyncarray
asynclistsampler
audioplayer

2. 用户自定义一个黑名单配置文件

在实践的过程中,替换属性的符号有时候会把系统类的属性替换了,比如

  • 把 appdelegate 中的 window 属性替换了,导致了编译链接没错,但是界面出不来了,因为初始的window对象找不到了
  • 把 uibutton 中的 titlelabel 属性替换了,直接导致了编译出错

对于这类问题,需要在黑名单中配置一些默认的过滤属性,对于黑名单中的这些属性不处理即可,在我的业务场景下,黑名单文件的配置如下:

文件名:defaultblacklistpropertiesconfig.cfg

?
1
2
3
4
5
6
7
8
# blacklistpropertiesconfig.cfg
# 属性黑名单配置,在此配置文件中的属性不需要替换名称
window
name
title
titlelabel
layout
appealsamplers

在 getandstoreproperties.sh 脚本使用到的代码片段如下,其实就是使用了 grep 命来查找,判断时候有找到,如果有就不处理,具体的可以看上面提供的完整的 getandstoreproperties.sh 脚本代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if [[ ${param_should_use_filter} -gt 0 ]]; then
    grep_result=$(grep ${prop_name} ${blacklist_cfg_file})
    echo "grep_result = >>${grep_result}<<"
    custom_grep_result=""
    if [[ -n ${param_custom_filter_file} ]]; then
        custom_grep_result=$(grep ${prop_name} ${param_custom_filter_file})
    fi
    if [[ -n ${grep_result} ]] || [[ -n ${custom_grep_result} ]]; then
        echo "--${prop_name}--存在配置文件中"
    else
        echo "--${prop_name}--xxx不存在配置文件中"
 
        tmp_props_array[$props_count]=$prop_name
        props_count=$[ props_count + 1 ]
        echo ">>>>>>>result_prop_name=${prop_name}"
    fi
else
    tmp_props_array[$props_count]=$prop_name
    props_count=$[ props_count + 1 ]
fi 

3. 某部分需要隔离的代码中的属性生成黑名单配置文件

这部分的功能其实就是调用 getandstoreproperties.sh 这个脚本,最终把文件输出的文件以追加的方式写入到用户自定义的黑名单属性文件中。

?
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
#...
# 黑名单类目录
declare -a custom_blacklist_search_dirs
custom_blacklist_search_dirs=("/users/aron/putaoworkspace/project/sscatch/sscatch/classes/sscatchapi"
    "/users/aron/putaoworkspace/project/sscatch/sscatch/classes/categories"
    "/users/aron/putaoworkspace/project/sscatch/sscatch/classes/components"
    "/users/aron/putaoworkspace/project/sscatch/sscatch/classes/external"
    "/users/aron/putaoworkspace/project/sscatch/sscatch/classes/handytools"
    "/users/aron/putaoworkspace/project/sscatch/sscatch/classes/macros" )
# ...
 
# 属性黑名单配置文件
custom_blacklist_cfg_file="$(pwd)/customblacklistpropertiesconfig.cfg"
 
# ...
# 获取自定义的黑名单属性并保存到文件中
echo "" > ${custom_blacklist_cfg_file}
for (( i = 0; i < ${#custom_blacklist_search_dirs[@]}; i++ )); do
    custom_blacklist_search_dir=${custom_blacklist_search_dirs[${i}]}
    ./getandstoreproperties.sh \
        -i ${custom_blacklist_search_dir}\
        -o ${custom_blacklist_cfg_tmp_file}
    cat ${custom_blacklist_cfg_tmp_file} >> ${custom_blacklist_cfg_file}
done
#...

最终生成的用户自定义的黑名单文件部分如下

文件:customblacklistpropertiesconfig.cfg

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# properties configs
dbfilepath
validitystring
accessqueue
age
attributednamestring
avatarurlstring
avatarurlstring
backcolorstring
bodyscheduler
bodyview
catchdatestring
cellheight
channelkey
cityname
conditionstring
# ....

4. 把需要替换的源文件中的所有匹配的属性做批量的替换

这一步在前面三部的基础上,查找并替换源码目录中在 propertiesconfigs.cfg 配置文件中出现的属性和属性的引用,查找使用grep命令、替换使用了sed命令。脚本代码如下

?
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#!/bin/bash
# 属性重命名脚本
 
####### 配置
# classes类目录
classes_dir="$(pwd)/../injectedcontentkitx"
# 黑名单类目录
declare -a custom_blacklist_search_dirs
custom_blacklist_search_dirs=("/users/aron/putaoworkspace/project/sscatch/sscatch/classes/sscatchapi"
    "/users/aron/putaoworkspace/project/sscatch/sscatch/classes/categories"
    "/users/aron/putaoworkspace/project/sscatch/sscatch/classes/components"
    "/users/aron/putaoworkspace/project/sscatch/sscatch/classes/external"
    "/users/aron/putaoworkspace/project/sscatch/sscatch/classes/handytools"
    "/users/aron/putaoworkspace/project/sscatch/sscatch/classes/macros" )
# 配置文件
cfg_file="$(pwd)/propertiesconfigs.cfg"
# 属性黑名单配置文件
blacklist_cfg_file="$(pwd)/defaultblacklistpropertiesconfig.cfg"
# 属性黑名单配置文件
custom_blacklist_cfg_file="$(pwd)/customblacklistpropertiesconfig.cfg"
custom_blacklist_cfg_tmp_file="$(pwd)/tmpcustomblacklistpropertiesconfig.cfg"
# 属性前缀,属性前缀需要特殊处理
class_prefix=""
# 属性后缀
class_suffix="abc"
 
 
# 检测文件是否存在,不存在则创建
checkorcreatefile() {
    file=$1
    if [[ -f $file ]]; then
        echo "检测到配置文件存在 $file"
    else
        echo "创建配置文件 $file"
        touch $file
    fi
}
 
# 配置文件检查
checkorcreatefile $cfg_file
 
# 循环检测输入的文件夹
function checkinputdestdir {
    echo -n "请输入需处理源码目录: "
    read path
    if [[ -d $path ]]; then
        classes_dir=$path
    else
        echo -n "输入的目录无效,"
        checkinputdestdir
    fi
}
 
# 需处理源码目录检查
if [[ -d $classes_dir ]]; then
    echo "需处理源码目录存在 $classes_dir"
else
    echo "请确认需处理源码目录是否存在 $classes_dir"
    checkinputdestdir
fi
 
 
####### 数据定义
 
# 定义属性保存数组
declare -a rename_properties_config_content_array
cfg_line_count=0
 
 
# 读取属性配置文件
function read_rename_properties_configs {
    ifs_old=$ifs
    ifs=$'\n'
    # 删除文件行首的空白字符 //www.zzvips.com/article/57972.htm
    for line in $(cat $cfg_file | sed 's/^[ \t]*//g')
    do
        is_comment=$(expr "$line" : '^#.*')
        echo "line=${line} is_common=${is_comment}"
        if [[ ${#line} -eq 0 ]] || [[ $(expr "$line" : '^#.*') -gt 0 ]]; then
            echo "blank line or comment line"
        else
            rename_properties_config_content_array[$cfg_line_count]=$line
            cfg_line_count=$[ $cfg_line_count + 1 ]
            # echo "line>>>>${line}"
        fi 
    done
    ifs=${ifs_old}
}
 
function print_array {
    # 获取数组
    local newarray
    newarray=($(echo "$@"))
    for (( i = 0; i < ${#newarray[@]}; i++ )); do
        item=${newarray[$i]}
        echo "array item >>> ${item}"
    done
}
 
# 重命名所有的属性
function rename_properties {
 
    # 读取属性配置文件
    read_rename_properties_configs
    # print_array ${rename_properties_config_content_array[*]}
 
    # 执行替换操作
    for (( i = 0; i < ${#rename_properties_config_content_array[@]}; i++ )); do
        original_prop_name=${rename_properties_config_content_array[i]};
        result_prop_name="${class_prefix}${original_prop_name}${class_suffix}"
        sed -i '{
            s/'"${original_prop_name}"'/'"${result_prop_name}"'/g
        }' `grep ${original_prop_name} -rl ${classes_dir}`
        echo "正在处理属性 ${original_prop_name}....."
    done
}
 
checkorcreatefile ${custom_blacklist_cfg_tmp_file}
 
# 获取自定义的黑名单属性并保存到文件中
echo "" > ${custom_blacklist_cfg_file}
for (( i = 0; i < ${#custom_blacklist_search_dirs[@]}; i++ )); do
    custom_blacklist_search_dir=${custom_blacklist_search_dirs[${i}]}
    ./getandstoreproperties.sh \
        -i ${custom_blacklist_search_dir}\
        -o ${custom_blacklist_cfg_tmp_file}
    cat ${custom_blacklist_cfg_tmp_file} >> ${custom_blacklist_cfg_file}
done
 
 
# 获取和保存属性到熟悉配置文件
./getandstoreproperties.sh \
    -i ${classes_dir}\
    -o ${cfg_file}\
    -f \
    -c ${custom_blacklist_cfg_file}
 
 
# 执行属性重命名
rename_properties
 
echo "done."

总结

以上就是基于shell脚本,以壳版本为场景,把属性的批量替换做了一个半自动化的实现步骤,如果不妥之处,还请不吝赐教。

原文链接:https://my.oschina.net/FEEDFACF/blog/1626928

延伸 · 阅读

精彩推荐
  • IOS解析iOS开发中的FirstResponder第一响应对象

    解析iOS开发中的FirstResponder第一响应对象

    这篇文章主要介绍了解析iOS开发中的FirstResponder第一响应对象,包括View的FirstResponder的释放问题,需要的朋友可以参考下...

    一片枫叶4662020-12-25
  • IOSIOS开发之字典转字符串的实例详解

    IOS开发之字典转字符串的实例详解

    这篇文章主要介绍了IOS开发之字典转字符串的实例详解的相关资料,希望通过本文能帮助到大家,让大家掌握这样的方法,需要的朋友可以参考下...

    苦练内功5832021-04-01
  • IOSiOS通过逆向理解Block的内存模型

    iOS通过逆向理解Block的内存模型

    自从对 iOS 的逆向初窥门径后,我也经常通过它来分析一些比较大的应用,参考一下这些应用中某些功能的实现。这个探索的过程乐趣多多,不仅能满足自...

    Swiftyper12832021-03-03
  • IOSiOS中tableview 两级cell的展开与收回的示例代码

    iOS中tableview 两级cell的展开与收回的示例代码

    本篇文章主要介绍了iOS中tableview 两级cell的展开与收回的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    J_Kang3862021-04-22
  • IOSIOS 屏幕适配方案实现缩放window的示例代码

    IOS 屏幕适配方案实现缩放window的示例代码

    这篇文章主要介绍了IOS 屏幕适配方案实现缩放window的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要...

    xiari5772021-06-01
  • IOSiOS布局渲染之UIView方法的调用时机详解

    iOS布局渲染之UIView方法的调用时机详解

    在你刚开始开发 iOS 应用时,最难避免或者是调试的就是和布局相关的问题,下面这篇文章主要给大家介绍了关于iOS布局渲染之UIView方法调用时机的相关资料...

    windtersharp7642021-05-04
  • IOS关于iOS自适应cell行高的那些事儿

    关于iOS自适应cell行高的那些事儿

    这篇文章主要给大家介绍了关于iOS自适应cell行高的那些事儿,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的...

    daisy6092021-05-17
  • IOSiOS 雷达效果实例详解

    iOS 雷达效果实例详解

    这篇文章主要介绍了iOS 雷达效果实例详解的相关资料,需要的朋友可以参考下...

    SimpleWorld11022021-01-28