Runtime ----- 带你上道

在IOS开发和学习过程中,我们经常会接触到一个词: Runtime  。很多开发者对之既熟悉又陌生,基本都是浅尝辄止,达不到灵活使用的水平(话说开发中也确实不经常用。。)本文和大家一起研究一下,Runtime到底是什么,还有他的一些应用场景,毕竟Runtime是OC动态特性的核心,熟练掌握它可以帮助我们更好的控制类的属性及方法,编写出更高效的代码。

一、什么是Runtime 

不管你之前如何理解的Runtime,先把他扔一边,我们从头梳理一下:

1、有一种大气而准确的说法 :

Objective-C是C语言的扩展,并加入了面向对象特性和Smalltalk式的消息传递机制。OC中的Runtime实现了将C语言转化为面向对象语言的作用,实际上我们的每一条OC代码的执行都会转换为Runtime的函数调用。Runtime是OC底层的实现,其函数的调用是高效的,基于Runtime的代码编写也是高效的!

2、核心:

是一个用C和汇编语言写的Runtime库(开源),这个库所做的事情就是加载类信息,进行方法的分发和转发,正是这个库赋予了Objective-C动态特性。

3、所谓的“动态特性” :

(1)我们比较下C++ 和OC,C++没有动态特性,编译时直接将代码转换为机器语言,而OC是在运行的时候,通过Runtime把程序转为可令计算机读懂的语言。两者都是对C进行了面向对象的扩展,但是实现机制不同。

(2)虽然RunTime赋予了OC动态特性,使得开发和使用变得相当灵活,但是归根结底OC还是一种编译型的语言,其具有一定的动态性,但是其动态特性也比不上JavaScript这种解释型的语言。

4、Runtime 其实有两个版本(也就听听。。):

(1)“Modern” 和 “Legacy”。我们现在用的 Objective-C 2.0 采用的是现行(Modern)版的 Runtime 系统,只能运行在 iOS 和 OS X 10.5 之后的64位程序中。而OS X较老的32位程序仍采用 Objective-C 1中得(早期) Legacy 版本的 Runtime 系统。这两个版本最大的区别在于当你更改一个类的实例变量的布局时,在早期版本中你需要重新编译它的子类,而现行版本就不需要。

(2)苹果开源了Runtime库的代码,同时GNU也维护着一个开源的版本,这两个版本之间都在努力的保持一致。

5、常见的简单使用场景,后面详细罗列:

(1)动态的创建、改变类

(2)动态的创建、改变、遍历属性

(3)动态的创建、改变、交互、遍历方法

二、OC中的对象模型

真正开始了解Runtime,有个基础工作需要做,就是我们要重温一下OC的对象和类的结构

1、打开<objc/objc.h>看看objc_object的定义 (截图看起来比较清晰,呵呵)

    

总结一下上面的:

(1)常用的id类型实际上是一个指向objc_object(实例对象)结构体的指针,id通常指代一个对象,也就是说OC对象其实就一个指向objc_object结构体的指针

(2)看objc_object结构体定义,得知其结构体内有一个类型为Class的字段isa,这就是常说的isa指针了。

(3)Class声明为一个指向objc_class的指针

关于 SEL、IMP的补充

(1)SEL是selector在Objective-C中的表示类型。selector可以理解为区别“方法”的ID。

    typedef struct objc_selector *SEL;
    struct objc_selector {
        char *name;                       OBJC2_UNAVAILABLE;// 名称
        char *types;                      OBJC2_UNAVAILABLE;// 类型
    };

   name和types都是char类型。

(2)IMP是“implementation”的缩写,它是由编译器生成的一个函数指针。当你发起一个消息后,这个函数指针决定了最终执行哪段代码。

2、接下来打开<objc/runtime.h>,看看objc_class的定义

研究一下objc_class中的几个字段:

(1)isa:这里的isa指针同样是一个指向objc_class(类对象)的指针,表明该Class的类型,这里的isa指针指向的就是常说的meta-class(元类)了。不难看出,类本身也是一个对象。同样的,元类也是一个对象,为了设计上的完整,元类的isa指针都会指向一个root metaclass(根元类)。根元类本身的isa指针指向自己,这样就形成了一个闭环。

(2)super_class:这个指针就是指向该class的super class,即指向父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL。

(3)cache:用于缓存最近使用的方法。消息发送时,系统会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,如果每次消息来时,都是在methodLists中遍历一遍,性能一定很差。这时,在每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,提高了调用的效率。

(4)version:我们可以使用这个字段来提供类的版本信息。这对于对象的序列化非常有用,它可以让我们识别出不同类定义版本中实例变量布局的改变(不太明白的作用。。。)

(5)objc_method_list:方法链表中存放的是该类的成员方法(-方法),类方法(+方法)存在meta-class的objc_method_list链表中(就是元类的“实例方法”)。

关于结构体指针 Method、Ivar的补充

(1)Method代表类中的某个方法的类型

 声明: typedef struct objc_method *Method;

 objc_method的定义如下:

    struct objc_method {
        SEL method_name                   OBJC2_UNAVAILABLE; // 方法名
        char *method_types                OBJC2_UNAVAILABLE; // 方法类型
        IMP method_imp                    OBJC2_UNAVAILABLE; // 方法实现
    }

    方法名method_name类型为SEL。

     方法类型method_types是一个char指针,存储着方法的参数类型和返回值类型。

方法实现method_imp的类型为IMP

(2)Ivar代表类中实例变量的类型

声明:typedef struct objc_ivar *Ivar

   objc_ivar的定义如下:  

    struct objc_ivar {
        char *ivar_name                   OBJC2_UNAVAILABLE; // 变量名
        char *ivar_type                   OBJC2_UNAVAILABLE; // 变量类型
        int ivar_offset                   OBJC2_UNAVAILABLE; // 基地址偏移字节
  #ifdef __LP64__
   	int space                         OBJC2_UNAVAILABLE; // 占用空间
  #endif
    }

  

3、经典配图展示:oc对象继承模型

    

(1)上图的几个注意点

  • 注意竖直方向和虚线方向代表的不同意义
  • meta-class的isa指针指向的是root meta-class
  • root meta-class的isa指针指向的是其本身
  • root meta-class的super class指向的是root class(这里也是不明白意义所在。。)
  • root class的super class 指向的是nil

(2)举个方法查找过程的例子:

    调用respondsToSelector: 的时候,实例对象只需要根据其isa指针,找到其所属的class,然后遍历其methodLists,如果没有,那么根据这个类的super_class找到其父类,再看其父类是否能相应这个方法就可以了,直到super_class为nil时,就无法响应这个方法了,return NO。

(3)调用类方法的不同:

  当我们使用类名调用类方法(+方法)时,只需要根据class的isa指针,找到其meta-class,然后通过meta-class的methodLists找到相应的方法既可(“类”是“元类”的对象)。

三、消息机制

1、OC中调用一个方法的本质就是在给对象发送消息,比如初始化一个NSObject对象:

  NSObject *object = [[NSObject alloc] init];

事实上,在编译时这句话会翻译成一个C的函数调用,即:

  objc_msgSend(objc_msgSend([NSObject?class],@selector(alloc)),@selector(init));

看看官方文档:

2、关于消息执行的时机问题:

(1)思考:就如上文所述,OC的代码翻译成C的函数调用之后,就是把OC代码转换成C代码了,那OC的动态特性体现在哪里?不就和C的静态特性一样了么?

(2)回答:对于C语言,函数的调用在编译的时候就会去决定调用哪个函数。而OC是一种动态语言,它会尽可能的把代码执行的决策从编译和链接的时候,推迟到运行时。给一个对象发送的一个消息并不会立即执行,而是在运行的时候再去寻找他对应的实现。这样就可以把消息转发给你想要的对象,或者随意交换一个方法的实现之类的。

3、所有使用objc_msgSend函数,会执行以下步骤(也体现了objc_cache的作用)

(1)通过对象(类)的isa指针去找到他的class

(2)在class的method list 找到该消息的实现

(3)如果class中没有该消息的实现,就继续到它的super_class中去找

(4)一旦找到这个这个消息的实现,那么就去执行他的IMP(函数指针,代码所在空间)

4、常见的函数、头文件

(1) #import <objc/runtime.h> : 主要包括 成员变量、类、方法

  • class_copyIvarList :        获得某个类内部的所有成员变量
  • class_copyMethodList :  获得某个类内部的所有方法
  • class_getInstanceMethod : 获得某个实例方法(对象方法,减号-开头)
  • class_getClassMethod :  获得某个类方法(加号+开头)
  • class_addMethod  :   添加方法
  • class_addProperty :   为类添加属性
  • class_respondsToSelector:   类实例是否响应指定的selector
  • class_setSuperclass:  调整一个类的继承关系
  • class_replaceProperty:替换类的属性
  • class_replaceMethod:替换类的方法
  • ivar_getTypeEncoding : 获得成员变量的类型
  • ivar_getName :  获得成员变量名
  • method_exchangeImplementations :   交换2个方法的具体实现

(2) #import <objc/message.h> : 消息机制

  • objc_msgSend(....)

四、RunTime的使用实例

RunTime可以很灵活的实现改变系统的方法及属性的效果,灵活度之大以至于也造成了一些隐患 ——— 破坏了系统的封装及代码的可读性,所以大家还是谨慎使用,也不要在开发过程中给队友挖坑(不要问我怎么知道的。。。)

下面搜罗了一些常用的场景:

1、动态创建一个类

#import <objc/runtime.h>
// 自定义一个方法
void reportFunction (id self, SEL _cmd) {
 NSLog(@"This object is %p", self);
}
int main(int argc, const char * argv[]) {
 @autoreleasepool {?? ? ? ?
 // 1.动态创建对象 创建一个Person 继承自 NSObject类
 Class newClass = objc_allocateClassPair([NSObject class], “Person”, 0);?
 // 为该类增加名为Report的方法
 class_addMethod(newClass, @selector(report), (IMP)reportFunction, @"[email protected]:");?? ? ? ?
 // 注册该类
 objc_registerClassPair(newClass);
 // 创建一个 Student 类的实例
 instantOfNewClass = [[newClass alloc] init];
 // 调用方法
 [instantOfNewClass report];
 }
 return 0;
}

2、关联对象(分类中动态添加成员变量)

(1)对象在内存中的排布可以看成是一个结构体,该结构体的大小并不能动态的变化,所以无法在运行时动态的给对象增加成员变量,但是我们可以通过关联对象的方法变相的给对象增加一个成员变量。

(2)比如,我们想给NSObject新增一个关联对象(就是添加成员变量):

创建一个NSObject的分类AssociatedObject,并声明一个新属性

@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;?
@end
在NSObject+AssociatedObject.m文件里面进行关联
#import "NSObject+AssociatedObject.h"
#import <objc/runtime.h>
@implementation NSObject (AssociatedObject)
@dynamic associatedObject;
- (void)setAssociatedObject:(id)object {
 // 设置关联对象
 objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
 // 得到关联对象
 return objc_getAssociatedObject(self, @selector(associatedObject));
}
@end

(3)我们可以通过上面的方法,给类动态的添加属性,不过我们更常用的是给一个类动态的添加block回调(想让这个类的实例实现什么业务逻辑,都可以通过赋值block,然后调用,来灵活的实现,使用起来也很方便,和添加属性类似)。

3、交换方法(可更改系统方法)

我们在开发过程中会遇到一种常见的错误:给数组元素赋值nil,系统会崩溃。下面我们参照这个案例,解释下runtime交换方法的实现

(1)先创建一个数组

    NSMutableArray *arrayM = [NSMutableArray array];
    [arrayM addObject:@"1111"];
    [arrayM addObject:@"2222"];
    [arrayM addObject:nil];  //这里会造成程序崩溃
    [arrayM addObject:@"33333"];

(2)交换方法,将系统的addObject和自定义的方法进行交换,我们先写一个NSMutableArray的分类,给其添加新的方法,然后在其中实现与系统方法交换。注意:“交换”不等同于“替换”

(3)这里有一个坑,addObject 实际上是 调用 insertObject :atIndex:方法, 并且在运行过程中,可以看到这个方法是_NSArrayM的方法(不是NSMutableArray的方法,这里有点像KVO的情况),所以我们要拿到_NSArrayM类,然后和它交换方法。可以参照下面的代码:NSClassFromString(@"__NSArrayM”)就是动态过程中获取这个类。

(4)代码实现

@implementation NSMutableArray (XL)

+ (void)initialize //补充下这个方法只执行一次,类比load方法都是执行一次,但是后者编译完就会执行
{
    //当前类被初始化的时候调用
    Method m1 = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(addObject:));
    Method m2 = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(new_AddObject:));

	//下面这样写也可以
	//Method m2 = class_getInstanceMethod([NSMutableArray class], @selector(new_AddObject:));

    method_exchangeImplementations(m1, m2);

}
- (void)new_AddObject:(id)objc
{
    if (objc == nil) {
        //这里的方法已经通过交换变成 addObject:
       [self new_AddObject:@"此处为空"];

    }else{
        [self new_AddObject:objc];
    }
}
@end

4、KVO的底层实现(自定义KVO)

(1)KVO的底层实现也是利用了RunTime机制,简单点说,KVO机制就是在运行时,动态派生出被检测对象的子类(NSKVONotifying_XXX),将被观察对象的isa指向该子类,然后在新子类中重写观察属性的set方法,接着在set方法里调用观察者的observeValueForKeyPath方法实现的监听机制(晕了吧,这就对了。。。看例子)

(2)给出示例(Person类就不写了,就一个属性age),在控制器中进行监测,下面代码执行结束,会显示监听到的age的变化

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    Person *p = [[HMPerson alloc] init];
    p.age = 20;
    self.p = p;

   //添加观察者
    [p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];

    //属性改变了!
    p.age = 30;

}

- (void)dealloc
{
    [self.p removeObserver:self forKeyPath:@"age"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"%@对象的%@属性改变了:%@", object, keyPath, change);
}

@end

(3)分析上面的例子,系统在运行时动态的进行了四项操作:

<1> 生成了Person的子类 NSKVONotifying_Person

<2> 在该子类中重写了age的set方法

<3> 调用了观察者的 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 方法

<4> 在p.age = 30 的时候,修改了对象中的isa指针,指向子类NSKVONotifying_Person,这样,在调用set方法的时候就会去子类的空间中寻找方法的地址并调用(很关键的一步,大家可以打断点验证)

(4)模拟KVO过程,我们手动创建一个子类:NSKVONotifying_Person,注意,此时运行会报出“坏内存”的错误,因为你的创建的类和系统自动生成的子类重名了(这也是一种验证KVO原理的方式)

#import "NSKVONotifying_Person.h"

    @implementation NSKVONotifying_HMPerson

    -(void)setAge:(int)age
    {
        //必须先调用父类的setAge方法,保证父类的set方法正常运行
        [super setAge:age];

        //伪代码,调用监听者的方法,实现监听到属性改变后的逻辑操作,并且传递参数
        [监听者  observeValueForKeyPath:@"age" ofObject:super change:@{ 监听属性的键值 } context:nil];

    }
    @end

5、模型归档(遍历成员属性的应用)

(1)Runtime可以动态获取成员属性名列表。

(2)下面代码中,Ivar表示的就是成员属性,ivars是指向属性的指针或者说地址(也可以理解为数组,但不是数组)。

(3)归档的实现可能会遇到对象中有很多属性,逐个手动去匹配归档哪些属性很麻烦,所以使用运行时,通过循环实现。

(4)代码示例:对于一个有很多属性的Person类,遵守了NSCoding协议之后,我们可以利用RunTime遍历模型对象的所有属性进行归档,关键代码如下:

// 利用runtime机制进行属性的归档接档
- (void)encodeWithCoder:(NSCoder *)aCoder {
 unsigned int count = 0;
 Ivar *ivars = class_copyIvarList([Person class], &count);
 for (int i = 0; i<count; i++) {
 // 取出i位置对应的成员变量
 Ivar ivar = ivars[i];
 // 查看成员变量
 const char *name = ivar_getName(ivar);
 // 归档
 NSString *key = [NSString stringWithUTF8String:name]; 

  //KVC 获得对象属性值
id value = [self valueForKey:key];
 [aCoder encodeObject:value forKey:key];
 }

 // 如果函数名中包含了copy\new\retain\create等字眼,那么这个函数返回的数据就需要手动释放
 free(ivars);
}
 
- (id)initWithCoder:(NSCoder *)aDecoder {
 self = [super init];
 if (self) {
 unsigned int count = 0;
 Ivar *ivars = class_copyIvarList([Person class], &count);
 for (int i = 0; i<count; i++) {
 // 取出i位置对应的成员变量
 Ivar ivar = ivars[i];
 // 查看成员变量
 const char *name = ivar_getName(ivar);
 // 归档
 NSString *key = [NSString stringWithUTF8String:name];
 id value = [aDecoder decodeObjectForKey:key];
 // 设置到成员变量身上
 [self setValue:value forKey:key];
 }
 free(ivars);
 }
 return self;
}

6、字典和对象模型之间的转换,例如MJExtension

(1)现在流行的很多字典转模型的框架,基本上都是利用Runtime原理实现(效率高):遍历模型属性列表—>根据获得的属性名作为key去字典中取出value —>然后用KVC给模型对象属性赋值

(2)这只是简单原理,框架中还包含了很多情况的判断,比如模型套模型的情况,这就需要检测成员属性的类型是否是对象类型,那就继续转模型。

(3)一个简单的示例代码

-(void)modelToolWithDict:(NSDictionary *)dict andModel:(Model*)model
{
    // Ivar : 成员变量
    unsigned int count = 0;
    // 获得所有的成员变量
    Ivar *ivars = class_copyIvarList([HMPerson class], &count);
    for (int i = 0; i<count; i++) {
        // 取得i位置的成员变量
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(ivar);
        // 获得成员变量的类型,如果需要根据类型判断是否有模型嵌套,可以通过这个变量
        const char *type = ivar_getTypeEncoding(ivar);

        //获得字典中的值
        id value = dict[[NSString stringWithUTF8String:name]];

        //使用KVC给模型赋值(KVC底层也是Runtime)
        [model setValue:value forKeyPath:[NSString stringWithUTF8String:name]];

        NSLog(@"%d %s %s", i, name, type);
    }
}

7、避免数组越界

开发中,数组在访问时如果越界会造成崩溃,为了避免这种潜在的崩溃风险,我们可以采用多种方法“强制”它不越界,比如重写get方法进行内部判断,这里我们用Runtime来做一个比较彻底的解决。

(1)越界的情况:names数组有10个元素, 调用 self.names[10] ,崩溃 ;

这行代码的本质是: [self.names objectAtIndex:10],所以,我们用运行时进行这个方法的交换。

(2)添加如下分类,后面出现数组访问越界的情况,将返回nil;

(3)因为是在load方法中实现的交换,所以,程序启动内存中加载了这个分类后,自动执行交换,不需要导入任何头文件了。

(4)核心代码

@implementation NSArray(Extension)
+ (void)load
{
    Method otherMehtod = class_getInstanceMethod(class, otherSelector);
    Method originMehtod = class_getInstanceMethod(class, originSelector);
    // 交换2个方法的实现
    method_exchangeImplementations(otherMehtod, originMehtod);
}

- (id)new_ObjectAtIndex:(NSUInteger)index
{
    if (index < self.count) {
        return [self new_ObjectAtIndex:index];
    } else {
        return nil;
    }
}
@end

五、总结

1、Runtime是OC代码可以编译运行的关键,本身是纯C的函数库,它的存在赋予了OC动态特性。

2、Runtime提供的一系列方法,可以在程序运行时操作类以及它的方法和属性,使用Runtime进行代码构建,效率较高。

3、Runtime是底层运行机制,实际开发中我们使用Runtime的时候并不多,但是关键时刻,还是能很好的解决很多问题。

4、熟悉Runtime需要了解几项东西:

(1)OC的对象模型

(2)isa指针

(3)消息机制

5、Runtime水很深,各位请根据自己的水性选择区域。。。。。

参考:http://honglu.me/2014/12/29/浅谈OC运行时-RunTime/

   https://www.ianisme.com/ios/2019.html?sukey=ecafc0a7cc4a741bfade6848774c88b7eeefdecd843c4a6c6f7da7fc65d2ed4ef4a188f7f68ab13e9ee80007d7ffb919

时间: 2024-11-08 09:13:44

Runtime ----- 带你上道的相关文章

SpringMVC使用MultipartFile文件上传,多文件上传,带参数上传

一.配置SpringMVC 在spring.xml中配置: <!-- springmvc文件上传需要配置的节点--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="-1&

AJAX提交form表单带文件上传

过了三天才想要写博客,这样不好,要改正 在做毕设的时候,用户发帖涉及到了文件上传的问题,在这里记录一下 背景: 在用户发帖的时候,用户只想发表文字postText,还有些用户想在发表postText的同时还发表一些图片,如何做? 上代码 不写的太细了,和流水账似的,挑重点记录一下. 1.前台的文件上传 本来想用form表单直接上传了,但是form提交时会刷新整个页面,但这不是我想要的,所以使用了ajax提交form表单. 利用ajax提交表单需要用到jquery.form.js这个包,网上有很多

至集地带太它交电会府造上道风越解治wang

改青勞能以查件達程革保工子器就與她無石這由建導引王海事張用色積目場你所西題門記滿驗月細觀品代六導裡或派化只那說理層看滿立片兒識過力系會解位路流己界常生身關何存西論型究周它體口除江業平眼隊調團方場實立之之京個少勞調群至十織增處把和不切制上般頭樣兒國識看備分一關化分聲最面置義置更界機先因理民老她們真根元識發立什爭知資親經 統毛區快明比半火新系及特持位較數圖共張細重知領較周轉維立情以變火平價機的並傳心由中音開話信毛稱溫利民強式展金立二組研命題切際邊委事並式力況非縣新工維快引觀統計濟金科便器參光可象國本

十年生死两茫茫,AG视讯带你上天堂

带你上天堂的AG视讯正规网址[7 7 6 6 8 2 2.c o m]一.首先文明游戏态度好.正确认识AG视讯,当是游戏.不要输了就生气而埋怨,大家一起文明游戏.二.陶冶情操心情好.不要在没有足够的时间和心情时玩AG视讯游戏,情绪可能会影响你的判断力.三.识清时务少要牌.盲目拿牌者,肯定是输多赢少,因为毕竟是两人斗一人,赢的时候是少数.俗话说识时务者为俊杰就是这个理.四.切忌恶意逃跑.有些人喜欢恶意逃跑,一逃跑双倍扣分,划不来的是自己. 五.聊天不可太多.打牌时尽量少聊天,要聊就不斗,要斗就不聊

c# 模拟提交带文件上传的表单

------WebKitFormBoundary8gsfyexbIoO5OYAa Content-Disposition: form-data; name="username" admin ------WebKitFormBoundary8gsfyexbIoO5OYAa Content-Disposition: form-data; name="submit" 登录 ------WebKitFormBoundary8gsfyexbIoO5OYAa Content-D

Java中request请求之 - 带文件上传的form表单

常用系统开发中总免不了显示图片,保存一些文件资料等操作. 这些操作的背后,就是程序员最熟悉的 enctype="multipart/form-data"类型的表单. 说起file类型的input,我们往往会遇到问题: 最为常见的就是编码格式-中文乱码.解决中文乱码最好的方式就是我们的项目全部使用 utf-8 格式的国际编码,因为它是可变字节长度的编码,对于占用2字节的中文绰绰有余了.另外就是使用 String s2 = new String(s1.getBytes(“ISO-8859-

微软终于上道儿了:操作系统不买帐,拿更新的扩展功能最低版本来限制

太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 以下是独立于 VS2015 运行的 Android 模拟器,使在 PC 上运行安卓应用成功很自然的事情,贡献! 然而,下面的提示略有不爽,可是一想

十年生死两茫茫 AG视讯最新技巧带你上天堂

AG视讯网站:7 7 6 6 8 2 2 . C O M 金???沙国际直营,a,g视,讯不懂点技巧经验还真不行.现在视,讯类的玩法越来越来.刚玩还好,玩得越久越心里没谱.看过各种打,法 技,巧 运气,背起来玩什么都死,很容易上头,长,龙有多,背的时候追又不敢追,不追又一直出.之前都搞怕了,什么倍投啊,反,龙 顺,龙 单,点 追三止一. 玩起来就不管用了,书的越多越心慌,畏首畏尾.建议下海的老哥们别一根筋的想着碰运气了.多了解熟悉,看看一些老玩家上,岸的技,巧,心得,打,法.总比一个人砖牛角尖好

IIS7设置将域名不带www跳转到带www上

很多朋友在IIS环境中搭建好网站后,习惯性将带www和不带www的域名都绑定到一个网站上,这样做虽然两个域名都能访问,但容易造成权重分散,从而导致网站权重降低.其实我们可以将访问不带www的域名自动跳转到带www的域名,例如访问taotaozaixian.com时浏览器自动跳转到www.taotaozaixian.com,浏览器地址显示的也是www.taotaozaixian.com.总结网上的方法,有两种方法比较常用:第一种是设置网站301重定向.优点是设置快速简单:缺点是要建立两个站点,一个