当前位置: 首页 > news >正文

镇江专业建网站甘肃自助建站系统怎么用

镇江专业建网站,甘肃自助建站系统怎么用,wordpress图片标题,陈木胜文章目录 前言一、什么是Blocks二、Blocks模式1.Block语法2.Block类型变量3.截获自动变量值4.__block说明符5.截获的自动变量 三、Blocks的实现1.Block的实质__main_block_impl_0Block对象的实现结构体初始化 2.截获自动变量值3.__block说明符4.Block存储域5.__block变量存储域… 文章目录 前言一、什么是Blocks二、Blocks模式1.Block语法2.Block类型变量3.截获自动变量值4.__block说明符5.截获的自动变量 三、Blocks的实现1.Block的实质__main_block_impl_0Block对象的实现结构体初始化 2.截获自动变量值3.__block说明符4.Block存储域5.__block变量存储域6.截获对象7.__block变量与对象8.Block循环引用 前言 先前看了小蓝书上的block发现不甚了解现重新学习block 一、什么是Blocks Blocks 是 C 语 言 的 扩 充 功 能 。 可 以 用 一 句 话 来 表 示 Blocks 的 扩 充 功 能 : 带 有 自 动 变 量 (局 部变量)的匿名函数。 我们把这句话拆分开来理解 自动变量: 也就是局部变量 匿名函数 即不带名称的函数 二、Blocks模式 1.Block语法 因为我们知道Block是带有自动变量的匿名函数 其与C函数只有两点不同 没有函数名带有^ ^记号叫做插入记号这样可以让我们在项目中方便查找 接下来我们将一下Block的范式 例子 ^int (int count) {return count 1; }同时Block还可以进行省略 如果参数列表没有参数或是返回值类型为void那么这两个地方都可以被省略 例子 ^void (void) {printf(1); }可以被省略为 ^{printf(1); }2.Block类型变量 上一部分我们讲到了Block语法我们这一部分将一下Block类型变量 在定义C 语言函数时就可以将所定义函数的地址赋值给函数指针 类型变量中。 同样的我们可以将Block语法赋值给声明为Block类型的变量中 Block类型变量实例如下 int (^blk) (int)声明Block 类型变量仅仅是将声明函数指针类型 变量的 “*” 变为“^”。 下面我们将Block赋值给Block类型变量 int (^blk) (int) ^int (int count) {return count 1; }同样Block类型变量也可以作为正常变量进行使用如进行参数传递 可以用 typedef简化他 这样^blk_t就变成了一个block数据类型 我们可以像使用函数一样使用block 3.截获自动变量值 我们先来看一段代码 int val 111;char *fmt val %d\n;void (^blk)(void) ^ {printf(fmt, val);};blk();val 222;fmt val2 %d;blk();可以看到我们两次调用blk使用的自动变量都是第一次保存自动变量的瞬间值。这里的原因其实是因为在第一次执行blk时自动变量被加入到了blk的结构体中 这里的具体实现在后面的源代码会进行讲解 4.__block说明符 我们在上面截获自动变量时block保存了执行时的自动变量的瞬间值保存后就无法改写该值 但是当我们使用__block修饰时就可以进行改写 5.截获的自动变量 我们截获自动变量后不能对变量进行赋值但是可以对变量进行使用 如上会产生错误 但是使用Array就不会发生错误 如果我们要修改截获的自动变量就要加上__block修饰符 另外不能再block中使用c语言数组因为截获自动变量的方法并没有实现对c语言的捕获 三、Blocks的实现 1.Block的实质 要深入了解Block的实质我们就必须看他的源码 我们在终端输入命令来查看其源码 int main(){void (^blk)(void) ^{printf(Block\n);};blk();return 0;}上面代码转换为源码就变为了 //block变量结构体struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr; } ... struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags0) {impl.isa _NSConcreteStackBlock;impl.Flags flags;impl.FuncPtr fp;Desc desc;} }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf(block\n);} static struct __main_block_desc_0 {size_t reserved;size_t Block_size; } __main_block_desc_0_DATA { 0, sizeof(struct __main_block_impl_0)}; int main() {void (*blk)(void) ((void (*)())__main_block_impl_0((void *)__main_block_func_0, __main_block_desc_0_DATA));((void (*)(__block_impl *))((__block_impl *)blk)-FuncPtr)((__block_impl *)blk);return 0; }我们一步步来分析我们的代码 首先 被转换为了 static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf(block\n);}我们逐步分析 __main_block_func_0 是函数的名称。这个名称是由编译器自动生成的与 Block 的创建有关。 struct __main_block_impl_0 *__cself 是函数的参数。这个参数是一个指向 __main_block_impl_0 结构体的指针。__main_block_impl_0 是 Block 对象的实现结构体包含了 Block 的一些元数据和状态信息。 __cself 是一个惯用的名称表示 “current self”即当前 Block 实例。 __main_block_impl_0 __cself 是 __main_block_impl_0结构体的指针该结构声明如下 //去除构造函数 struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc; };我们这里理解一下Block对象的实现结构体他是用于表示Block对象的一种数据结构封装了Block的元数据、捕获变量以及执行代码等信息 __block_impl 首先看他的第一个成员变量__block_impl他也是一个结构体他封装了一些Block的核心元素以及信息 struct __block_impl {void *isa; //如同对象类型的Class isa将Block作为Objc对象是关于该对象类的信息放置于isa指向的对象中int Flags; //某些标志int Reserved; //保留区域void *FuncPtr; //函数指针 }我们来解释一下这些参数 isa:我们知道Block其实质上也是一个对象我们会在运行时使用isa指针指向对象所属的类别但是对于Block其会根据Block对象内存的分配位置分别指向不同位置 栈_NSConcreteStackBlock与堆_NSConcreteMallocBlock Flags这是一组标志位,用于存储Block对象的一些特性和状态信息。例如,有一个标志位表示Block对象是否被复制到堆上,另一个标志位表示Block是否使用了C捕获变量。 Reserved这是一个保留区域,目前并没有被使用。可能是为了对齐或者将来的扩展而预留的空间。 void *FuncPtr是一个函数指针指向Block的内部实现代码当Block执行时会调用这个函数执行block内部的代码 __main_block_desc_0 我们再来看一下__main_block_desc_0这个结构体 其声明如下 static struct __main_block_desc_0 {size_t reserved; //保留区域size_t Block_size; //Block的大小 };下面我们分析一下这段代码的源代码 void (^blk)(void) ^{printf(Block\n);};void (*blk)(void) ((void (*)())__main_block_impl_0((void *)__main_block_func_0, __main_block_desc_0_DATA));总结下来就是通过生成一个Block对象的结构体实例将其地址赋值给__main_block_impl_0结构体的指针类型变量blk 分开来看就清晰多了 struct __mian_block_impl_0 tmp __main_block_impl_0(__main_block_func_0, __main_block_desc_0_DATA); struct __mian_block_impl_0 *blk tmp;分析一下block实例的构造函数__main_block_impl_0(__main_block_func_0, __main_block_desc_0_DATA) __main_block_impl_0((void*)__main_block_func_0,__main_block_desc_0_DATA);: a. __main_block_impl_0 是一个结构体这里调用它的构造函数或类似的初始化函数并传入两个参数 b. 第一个参数 ((void *)__main_block_func_0) 是一个类型转换将 __main_block_func_0 的地址转换为 void *。__main_block_func_0 是实现 Block 功能的函数的地址。 c. 第二个参数 __main_block_desc_0_DATA 是指向另一个结构体描述符__main_block_desc_0_DATA的地址。这个描述符通常包含有关 Block 的一些元数据如其大小等。 Block对象的实现结构体初始化 我们来看一下__main_block_impl_0结构体实例是如何初始化得到的 isa _NSConcreterStackBlock; Flags 0; Reserved 0; FuncPtr __main_block_func_0; Desc __main_block_desc_0_DATA;我们再来看一下函数中的这段代码 ((void (*)(__block_impl *))((__block_impl *)blk)-FuncPtr)((__block_impl *)blk);我们在void (*blk)(void) ((void (*)())__main_block_impl_0((void *)__main_block_func_0, __main_block_desc_0_DATA));中已经生成了一个Block的实现结构体的实例并将其地址赋给指针变量blk 下一步我们就需要实现Block的内部代码前面我们说到了FuncPtr是一个函数指针指向Block的内部实现代码接下来我们就可以通过调用FuncPtr来实现内部代码 去除转换部分就是 (*blk-FuncPtr)(blk)这段代码就是简单的函数指针调用函数 这里有一个问题就是为什么Block的实质为OC对象后面学到会进行补充 2.截获自动变量值 本部分主要讲解源代码如何截获自动变量值 int main() {int dmy 256;int val 10;const char *fmt val %d\n;void (^blk)(void) ^{printf(fmt,val);};blk();return 0; }源代码如下 struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr; }; struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;const char *fmt;int val;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags0) : fmt(_fmt), val(_val) {impl.isa _NSConcreteStackBlock;impl.Flags flags;impl.FuncPtr fp;Desc desc;} }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) {const char *fmt __cself-fmt; // bound by copyint val __cself-val; // bound by copy printf(fmt,val);}static struct __main_block_desc_0 {size_t reserved;size_t Block_size; } __main_block_desc_0_DATA { 0, sizeof(struct __main_block_impl_0)}; int main() {int dmy 256;int val 10;const char *fmt val %d\n;void (*blk)(void) ((void (*)())__main_block_impl_0((void *)__main_block_func_0, __main_block_desc_0_DATA, fmt, val));((void (*)(__block_impl *))((__block_impl *)blk)-FuncPtr)((__block_impl *)blk);return 0; }可以看到在Block语法表达式中使用的自动变量被追加到了Block的实现结构体__main_block_impl_0中 同时声明的成员变量类型与自动变量类型完全相同 这里需要注意 Block语法表达式中没有使用的自动变量不会被追加Block自动变量的截获只针对Block中使用的自动变量 下面再来看一下Block结构体实例的构造函数与先前有什么不同 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags0) : fmt(_fmt), val(_val);void (*blk)(void) ((void (*)())__main_block_impl_0((void *)__main_block_func_0, __main_block_desc_0_DATA, fmt, val));再来看一下Block实例的内部代码实现 ^{printf(fmt,val);}; static void __main_block_func_0(struct __main_block_impl_0 *__cself) {const char *fmt __cself-fmt; // bound by copyint val __cself-val; // bound by copyprintf(fmt,val); }可以看到在Block的内部函数实现代码中使用的变量是Block对象后的实现结构体的成员变量也就是__cself-fmt与__cself-val 这也说明这些变量在执行Block内部函数表达式前就被定义因此源代码表达式不用改动就可以使用自动变量 同时这也说明了Block获取的是自动变量的瞬间值 3.__block说明符 我们在先前说过Block截获自动变量后无法再修改自动变量那么如何才能再Block内部实现修改保存的自动变量的值呢 在__block还没出现之前可以通过c语言的一些特性来对自动变量进行修改 1、静态变量 2、静态全局变量 3、全局变量 下面来看这段代码 int global_val 1; static int static_global_val 2; int main() {static int static_val 3;void (^blk)(void) ^{global_val * 1;static_global_val * 2;static_val * 3;};return 0; }源代码 struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr; }; int global_val 1; static int static_global_val 2;struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;int *static_val; //静态变量static_val的指针__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags0) : static_val(_static_val) {impl.isa _NSConcreteStackBlock;impl.Flags flags;impl.FuncPtr fp;Desc desc;} }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int *static_val __cself-static_val; // bound by copyglobal_val * 1;static_global_val * 2;(*static_val) * 3;}static struct __main_block_desc_0 {size_t reserved;size_t Block_size; } __main_block_desc_0_DATA { 0, sizeof(struct __main_block_impl_0)}; int main() {static int static_val 3;void (*blk)(void) ((void (*)())__main_block_impl_0((void *)__main_block_func_0, __main_block_desc_0_DATA, static_val)); //静态变量static_val的指针传递给__main_block_impl_0结构体的构造函数return 0; }我们看到对于静态全局变量static_global_val和全局变量global_val在转换后的函数中直接使用。但是static_val的指针被加到了Block对象的实现结构体中。在使用static_val时需要通过其指针对其进行访问。 这样一来我们就可以实现在Block实现代码内部修改变量 但是这样就引出了一个新的问题 为什么自动变量不和静态变量一样通过指针作为成员变量保存在__main_block_impl_0中这样不也可以对自动变量进行修改了吗 在Block中我们可以存有超出其变量作用域的自动变量但是我们常常在超出变量作用域的时候使用Block因为超出变量作用域时变量已经被废弃如果我们再次通过指针访问变量那么将会发生错误 解决Block中不能保存值的第二个方法是使用__block说明符 int main() {__block int val 3; //__block修改val自动变量void (^blk)(void) ^{val * 1; //修改自动变量的值};return 0; }源代码 struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr; }; · · //新增的结构体 struct __Block_byref_val_0 {void *__isa; __Block_byref_val_0 *__forwarding;int __flags;int __size;int val; };struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__Block_byref_val_0 *val; // by ref__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags0) : val(_val-__forwarding) {impl.isa _NSConcreteStackBlock;impl.Flags flags;impl.FuncPtr fp;Desc desc;} }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_val_0 *val __cself-val; // bound by ref(val-__forwarding-val) * 1;} static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)dst-val, (void*)src-val, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src-val, 8/*BLOCK_FIELD_IS_BYREF*/);}static struct __main_block_desc_0 {size_t reserved;size_t Block_size;void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0}; int main() {__attribute__((__blocks__(byref))) __Block_byref_val_0 val {(void*)0,(__Block_byref_val_0 *)val, 0, sizeof(__Block_byref_val_0), 3};void (*blk)(void) ((void (*)())__main_block_impl_0((void *)__main_block_func_0, __main_block_desc_0_DATA, (__Block_byref_val_0 *)val, 570425344));return 0; }可以看到增加了一个__block代码含量剧增 我们发现__block使val变成了一个结构体 struct __Block_byref_val_0 {void *__isa; __Block_byref_val_0 *__forwarding;int __flags;int __size;int val; };可以看到初始化如下 __Block_byref_val_0 val {0, //__isaval, //__forwarding指向自己0, //__flags 0sizeof(__Block_byref_val_0), //__size 为自身__Block_byref_val_0的大小3, //自动变量的值 }再来看一下源代码中是如何给变量进行修改的 这个过程更加复杂,__main_block_impl_0结构体持有了指向__block变量的__Block_byref_val_0的结构体实例指针 同时__Block_byref_val_0结构体中的成员变量__forwarding持有指向该实例自身的指针然后我们可以通过__forwarding__forwarding持有指向该实例自身的指针来访问原成员变量val 为什么需要__forwarding呢后面的部分会讲到 另外不将__Block_byref_val_0结构体添加到__main_block_impl_0是为了在多个Block中都可以使用__block变量 4.Block存储域 通过我们前面的源码可以得知Block转换为Block结构体类型变量__main_block_impl_0,__block转换为block变量的结构体类型变量__Block_byref_val_0 所谓结构体类型的自动变量就是栈上生成的结构体的实例 另外我们知道Block也是OC对象同时我们知道他也有isa指针他的isa指针表明其当前所存储的区域 然后我们来梳理上节遗留的问题 Block超出变量作用域可存在的原因block变量用结构体成员变量_ _ forwarding存在的原因 当Block超出其变量作用域该Block会被废弃配置在栈上的__block 也会被废弃 Blocks提供讲Block 和_ _block 变量从栈上复制到堆上的方法来解决这个问题这 样 即 使Block语 法 记 述 的 变 量 作 用 域 结 束 堆 上的Block还可以继续存在 然后堆上的Block的isa指针就会指向_NSConcreteMallocBlock类 而__block变量中使用结构体成员变量__forwarding可以实现无论__block变量配置在栈上还是堆上都可以被正确访问。这也就是__forwarding成员变量存在的原因。 这里引用一下上面出现过的代码 (val-__forwarding-val) * 1;我们在上面讲过在使用__block变量时我们可以通过__forwarding访问原成员变量val 当block从栈上复制到堆上的时候__forwarding的指向就由val变为了堆上的block结构体实例。 ps: 栈上和堆上同时保持__block变量实例但是访问和修改值则是在堆上 我们这里分析一下Blocks提供的复制方法究竟是什么 在ARC有效的时候编译器会自行判断并自动生成将Block从栈上复制到堆上的代码 该源代码返回配置在栈上的Block当函数返回时变量作用域结束而后栈上的Block被废弃。但ARC会自动帮我们处理这个问题 在ARC有效时blk_t tmp实际上带有__strong修饰符 通过objc_retainBlock可知函数实际上是Block_copy 在这之间发生的情况书上讲解的很清楚 将Block作为函数值返回时编译器就会自动生成复制到堆上的到吗 在大多数情况下编译器会自行判断当然也有一些情况需要我们手动复制 向方法或者函数的参数传递Block时。 但是在方法或者函数里面适当的复制了传递的参数时那么就不需要手动复制GCD的API不需要cocoa框架方法且方法名包含usingBlock 同样Block类型变量也可以使用copy方法进行复制 blkCopy [blk copy];5.__block变量存储域 这一部分我们讲一下当__block变量从栈上被复制到堆的时候其会受什么影响 当Block被从栈复制到堆上时__block变量也会一起被复制到堆上并且被Block持有 如果多个Block使用同一个__block变量那么复制到堆上的Block都会持有__block对象 如果堆上的Block被废弃那么其所使用的__block变量也会被释放 由此我们可以知道__block的思考方式与OC引用计数管理完全相同 同时通过Block从栈上复制到堆上之后原来的栈上的__block变量的__forwarding指针从指向自身变味指向堆上的__block结构体由此不管__block变量配置在栈上还是堆上都可以顺利访问同一个__block变量 6.截获对象 首先来看一段源代码 blk_t blk; int main() {id array [[NSMutableArray alloc] init];blk [^(id obj) {[array addObject:obj];} copy];blk([[NSObject alloc] init]);blk([[NSObject alloc] init]);blk([[NSObject alloc] init]); }这段输出意味着array超出其变量作用域而存在 同时通过解析后的源码可知Araay被夹到了Block的实现结构体中 同时OC为了准确把握Block从栈上复制到堆上以及堆上的Block的废弃的时机在Block结构体中含有__strong与__weak修饰符的变量也会被恰当的初始化以及废弃。为此在__main_block_desc_0结构体中增加了成员变量copy与dispose作为指针赋值给函数__main_block_copy_0以及 __main_block_dispose_0 static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)dst-array, (void*)src-array, 3/*BLOCK_FIELD_IS_OBJECT*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src-array, 3/*BLOCK_FIELD_IS_OBJECT*/);}_Block_object_assign 函数调用相当于retain实例方法的函数。将对象赋值在对象类型的结构体成员变量中。有retain方法肯定有release方法。__main_block_dispose_0函数调用__main_block_dispose_0函数释放赋值在Block中的结构体成员变量arrar中的对象。 同时我们了解一下调用这两个函数的时机 那么什么时候栈上的block会复制到堆上呢 Block 被作为函数返回值时 如果函数返回一个栈上分配的 Block,那么在函数返回后,栈帧会被销毁,Block 就会失效。为了避免这种情况,编译器会自动将返回的 Block 复制到堆上。 int (^makeIncrementer)(int)) {int value 0;int (^incrementer)(void) ^{value;return value;};return incrementer; // 编译器会自动将 incrementer 复制到堆上 }int (^increment)(void) makeIncrementer(0); NSLog(%d, increment()); // 输出 1 NSLog(%d, increment()); // 输出 2Block 被赋值给 __block 修饰的变量时 如果你将一个栈上的 Block 赋值给一个 __block 修饰的变量,编译器会自动将这个 Block 复制到堆上。__block 变量用于存储指向堆上 Block 的指针。 __block int (^blockVar)(void);int value 42; blockVar ^{return value; // 编译器会自动将该 Block 复制到堆上 };NSLog(%d, blockVar()); // 输出 42调用 Block_copy() 函数时 你可以手动调用 Block_copy() 函数将栈上的 Block 复制到堆上。这通常用于确保 Block 在超出其定义作用域后仍然有效。 int (^blockObj)(void) ^{return 100; };blockObj Block_copy(blockObj); // 手动将 blockObj 复制到堆上 NSLog(%d, blockObj()); // 输出 100Block 被 GCD API 持有时 如果你将一个 Block 传递给 Grand Central Dispatch (GCD) API (如 dispatch_async),GCD 会自动将该 Block 复制到堆上,以确保在异步执行期间 Block 是有效的。 dispatch_queue_t queue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); int value 42; dispatch_async(queue, ^{NSLog(%d, value); // GCD 会自动将该 Block 复制到堆上 });Block 访问了 __block 修饰的变量时 如果 Block 内部访问了 __block 修饰的变量,编译器会自动将该 Block 复制到堆上,以确保变量在 Block 执行时是有效的。 __block int value 42;int (^blockObj)(void) ^{value 100; // 访问了 __block 变量编译器会自动将该 Block 复制到堆上return value; };NSLog(%d, blockObj()); // 输出 100另外需要知道只有调用_ Block_copy函数才能持有截获的附有_ _strong修饰符的对象类型的自动变量值 - (void)myMethod {__strong NSString *str Hello;void (^blockObj)(void) ^{NSLog(%, str); // 默认捕获str的值复制};blockObj(); // 输出Hello// 如果不调用_Block_copy,str在这里被释放blockObj _Block_copy(blockObj); // 将Block复制到堆上,并持有str的强引用// 现在无论str何时被释放,blockObj都能正确输出Hello }7.__block变量与对象 我们知道在ARC中会给id类型变量自动加上__strong修饰符只有使用__strong修饰符的变量才会在block从栈复制到堆时使用_Block_object_assign来持有__block变量。如果使用__weak修饰符就当作用与结束时__block变量也会自动被释放 由此我们可以知道只有自动变量用__strong进行修饰时才会被block持有且不随作用域结束而销毁 同时在blk被定义的时候blk就已经捕获了自动变量而不是在调用blk时才进行捕获 blk (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))__main_block_impl_0((void *)__main_block_func_0, __main_block_desc_0_DATA, array, 570425344)), sel_registerName(copy));8.Block循环引用 同时Block也会有循环引用的问题这里就需要用__weak,__block,__unsafe_unretained修饰符来避免循环引用。 这一部分后面会专门进行讲解
http://www.pierceye.com/news/369169/

相关文章:

  • 行业网站维护买电脑wordpress
  • 廊坊兼职网站建设网站图片怎么做的高级
  • pc网站转换手机网站wap用jsp源码做网站
  • 重庆慕尚网站建设在线美工
  • 珠海网站设计培训源码网站
  • 中山好的做网站的公司网站seo外包
  • 付费的网站是指展览馆网站建设方案书
  • 免费整套ppt模板下载网站网络营销推广目的
  • 公司 做网站企业手机app开发公司
  • 玉树电子商务网站建设多少钱做类似淘宝一样的网站
  • 住房城乡与建设厅网站燕郊网站开发
  • 欧美风格网站特点wordpress 电影主题
  • 烟台网站建设网站推广团队logo标志设计
  • 怎么注册建设银行网站如何创建属于个人网站
  • 双语网站系统wordpress page 父页面
  • 连云港做网站设计稿定设计官网入口
  • 建筑网站推荐wordpress hook api
  • 昆明做网站公司哪家好安卓优化
  • 魔站建站系统哪家好国内知名的包装设计公司
  • 福田区住房和建设局网站早晨设计 做网站设计吗
  • 郑州轨道网站开发手机怎么做动漫微电影网站
  • vscode网站开发昆明做网站找启搜网络
  • 如何评估网站虚拟商品交易网站建设
  • 太原网站优化教程pycharm做网站
  • 哪些网站做英语比较好免费下载模板ppt
  • 网站建设运营计划书wordpress 维护页面
  • 襄阳定制型网站开发前端网页设计招聘
  • 网站备案报价深圳市住房和建设局官网首页
  • 宁波江北区网站推广联系方式做一个论坛网站要多少钱
  • 网站制作无锡台州建设工程网站