iOS-Runtime知识点整理

-1.Runtime简介
回到顶部

1.Runtime简介

因为Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时。也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义,它是整个Objc运行框架的一块基石。

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

Runtime基本是用C和汇编(437版本开始较多使用mm文件,但是仍用C语法)实现的,可见苹果为了动态系统的高效而作出了很多努力,你可以在这里下到苹果维护的开源代码runtime源码。苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。

-2.Runtime相关的头文件
回到顶部

2.Runtime相关的头文件

ios的sdk中 usr/include/objc文件夹下面有这样几个文件

List.h
NSObjCRuntime.h
NSObject.h
Object.h
Protocol.h
a.txt
hashtable.h
hashtable2.h
message.h
module.map
objc-api.h
objc-auto.h
objc-class.h
objc-exception.h
objc-load.h
objc-runtime.h
objc-sync.h
objc.h
runtime.h

都是和运行时相关的头文件,其中主要使用的函数定义在message.h和runtime.h这两个文件中。 在message.h中主要包含了一些向对象发送消息的函数,这是OC对象方法调用的底层实现。 runtime.h是运行时最重要的文件,其中包含了对运行时进行操作的方法。 主要包括:

1.操作对象的类型的定义

/// An opaque type that represents a method in a class definition. 一个类型,代表着类定义中的一个方法
typedef struct objc_method *Method;/// An opaque type that represents an instance variable.代表实例(对象)的变量
typedef struct objc_ivar *Ivar;/// An opaque type that represents a category.代表一个分类
typedef struct objc_category *Category;/// An opaque type that represents an Objective-C declared property.代表OC声明的属性
typedef struct objc_property *objc_property_t;// Class代表一个类,它在objc.h中这样定义的  typedef struct objc_class *Class;
struct objc_class {Class isa  OBJC_ISA_AVAILABILITY;#if !__OBJC2__Class super_class                                        OBJC2_UNAVAILABLE;const char *name                                         OBJC2_UNAVAILABLE;long version                                             OBJC2_UNAVAILABLE;long info                                                OBJC2_UNAVAILABLE;long instance_size                                       OBJC2_UNAVAILABLE;struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;struct objc_cache *cache                                 OBJC2_UNAVAILABLE;struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif} OBJC2_UNAVAILABLE;
这些类型的定义,对一个类进行了完全的分解,将类定义或者对象的每一个部分都抽象为一个类型type,对操作一个类属性和方法非常方便。OBJC2_UNAVAILABLE标记的属性是Ojective-C 2.0不支持的,但实际上可以用响应的函数获取这些属性,例如:如果想要获取Class的name属性,可以按如下方法获取:
Class classPerson = Person.class;
// printf("%s\n", classPerson->name); //用这种方法已经不能获取name了 因为OBJC2_UNAVAILABLE
const char *cname  = class_getName(classPerson);
printf("%s", cname); // 输出:Person

2.函数的定义

对象进行操作的方法一般以object_开头

进行操作的方法一般以class_开头

类或对象的方法进行操作的方法一般以method_开头

成员变量进行操作的方法一般以ivar_开头

属性进行操作的方法一般以property_开头

协议进行操作的方法一般以protocol_开头

根据以上的函数的前缀 可以大致了解到层级关系。对于以objc_开头的方法,则是runtime最终的管家,可以获取内存中类的加载信息,类的列表,关联对象和关联属性等操作。

例如:使用runtime对当前的应用中加载的类进行打印,别被吓一跳。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {unsigned int count = 0;Class *classes = objc_copyClassList(&count);for (int i = 0; i < count; i++) {const char *cname = class_getName(classes[i]);printf("%s\n", cname);}
}
-3.技术点和应用场景
回到顶部

3.技术点和应用场景

在以下的代码中,都用到了Person类,Person类知识简单的定义了一个成员变量和两个属性

@interface Person : NSObject
{@privatefloat _height;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@end

3_1.获取属性\成员变量列表

回到顶部
对于获取成员变量的列表可以使用class_copyIvarList函数,如果想要获取属性列表可以使用class_copyPropertyList函数,使用的示例如下:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {Class classPerson = NSClassFromString(@"Person"); // 与下面一句效果一样,可以不用导入头文件
//    Class clazz = Person.class;unsigned int count = 0;Ivar *ivarList = class_copyIvarList(classPerson, &count); // 获取成员变量数组for (int i = 0; i < count; i++) {const char *cname = ivar_getName(ivarList[i]); // 获取成员变量的名字NSString *name = [NSString stringWithUTF8String:cname];NSLog(@"%@", name);}NSLog(@"-------------------分割线------------------");objc_property_t *propertyList = class_copyPropertyList(classPerson, &count); // 获取属性数组for (int i = 0; i < count; i++) {const char *cname = property_getName(propertyList[i]);NSString *name = [NSString stringWithUTF8String:cname];NSLog(@"%@", name);}}
以上代码的输出为:
2015-06-05 22:28:16.194 runtime终极[4192:195757] _height
2015-06-05 22:28:16.195 runtime终极[4192:195757] _age
2015-06-05 22:28:16.195 runtime终极[4192:195757] _name
2015-06-05 22:28:16.195 runtime终极[4192:195757] -------------------分割线------------------
2015-06-05 22:28:16.195 runtime终极[4192:195757] name
2015-06-05 22:28:16.195 runtime终极[4192:195757] age

为什么会有上面的输出结果,因为@property会做三份工作:
1.生成一个带下划线的成员变量
2.生成这个成员变量的get方法
3.生成这个成员变量的set方法
因此会输出三个成员变量_height、_age和_name。需要注意的是属性名是不带下划线的,和定义时的名字一样。因此可以说:ivarList可以获取到@property关键字定义的属性 ,而propertyList不可以获取到成员变量。也就是:使用ivarList是可以将所有的成员变量和属性都获取的

当属性是readonly的而且重写了getter时,这种情况还是会遇见的,比如一个属性是计算型属性,需要依赖其他属性的值计算而来。此时生成的带下划线的成员变量就不在了, 通过ivarList不能获取该属性了。因此当有这种值的时候,无论使用ivarList还是使用propertyList都无法获取全部的属性或变量

在进行下一个话题之前:先需要弄清楚另一个问题:对于一个readonly的属性,到底是didSet+set好,还是重写getter好?

大部分的readonly的属性是计算型的,依旧是依赖于其他属性,因此可以使用didSet+set,也就是在其他属性的set方法内,将本属性set。 但是didSet+set有时候完全没有必要,不符合懒加载的规则,浪费了计算能力,用重写getter的方法好一些。 因此重写getter总是会好一点。

回归正题:在KVC时,想要获取全部的成员变量和属性, 怎么办呢?

首先要了解setValue: forKeyPath:方法的底层实现:以name属性为例
1.首先先去类的方法列表去寻找有木有setName:,如果有,就直接调用[person setName:value]
2.找找有没有带下划线的成员变量_name,如果有 _name = value;
3.找有没有成员变量name,如果有 name = value;
4.如果都没有找到,就直接报错。
因此对于readonly的又重写了getter的属性而言:如果对propertyList的属性一次使用kvc,就会报错,因此为保证代码正常,不能使用propertyList的属性进行kvc;
另外:这种属性本来就是计算型的了,为什么还有为它赋值呢,因此对它进行kvc也不合情理。
当使用ivaList时,直接就无法获取到这种属性,因此是kvc的最佳方案。再者,使用propertyList无法获取成员变量(_height),无法对成员变量进行赋值。而使用ivaList是可以将该赋值的成员变量都获取的。

以上就是使用ivar还是使用property进行kvc的论证。

话题外: 很多类 有些成员变量 既没有暴露给外部调用的getter又没有setter,只是用@private声明了一下:为什么??
猜测是:是方法调用时使用的中间变量,因为是跟随对象产生,不适合使用静态static,又因为外部不会使用,所以没必要给外部提供接口,但是可能有好几个方法都需要这个量,不适合做局部变量,所以就这样定义了。

对于这种情况,要想不对这种成员变量赋值,在KVC时又可以这样改进一下,通过ivarList获取,去掉propertyList中没有的成员变量,这样就过滤掉了上面的那种成员变量了。

3_1_1.应用1:KVC字典转模型

回到顶部

获取属性\成员列表一个重要的应用就是,一次取出模型中的属性\成员变量,根据它的名字获取字典中的key然后取出字典中这个key对应的value,使用setValue: forKeyPath:方法设置值。为什么要这样,而不再使用方法setValuesForKeysWithDictionary:。因为在setValuesForKeysWithDictionary:方法内部会执行这样一个过程
遍历字典里面的所有key,一个一个取出来,遍历每个key按照以下过程
1.取出key,
2.取出key的value,即dict[key],直接给模型的属性\成员变量赋值
3.怎么给模型的属性赋值,使用方法setValue:value forKeyPath:key进行赋值,这个方法的执行过程在前面已经提到。

因此,开发中经常遇到的字典中的key比模型中多时,会出现的 this class is not key-value compliant for ‘xxx’这个bug就很好解释了,通常是因为字典中的key,比模型中的属性\成员变量多。那么当模型中的属性比字典中多时,使用setValuesForKeysWithDictionary:会不不会有bug呢?经测试:当多出来的属性是对象数据类型时,为null,当属性是基本数据类型时,会有一个系统默认值(如int为0)。

因此使用逐一为属性赋值的方法进行KVC:

Class clazz = Person.class;
unsigned int count = 0;Person *person = [[Person alloc]init];
NSDictionary *dict = @{@"name":@"zhangsan",@"age":@19, @"height": @1.75};Ivar *ivars = class_copyIvarList(clazz, &count);
// NSLog(@"%tu", count); // 3
for (int i = 0; i < count; i++) {const char *cname = ivar_getName(ivars[i]);NSString *name = [NSString stringWithUTF8String:cname];NSString *key = [name substringFromIndex:1]; // 去掉'_'[person setValue:dict[key] forKey:key];
}
NSLog(@"%@", person); // 已经重写了description方法
输出是:
<Person, 0x7ff15b80f230>{ name = zhangsan, height = 1.750000, age = 19}

使用这种方式进行kvc,即使字典中的key多的时候也不会有bug,但是新的问题出现了,如果模型中的属性比字典中的key多便会出现bug而且:如果多的是对象类型不会有bug,该属性的值为null,如果是基本数据类型就会出错could not set nil as the value for the key ‘xxx’。例如,将上面的字典改为:

NSDictionary *dict = @{@"age":@19, @"height": @1.75}; // 去掉了name NSString类型
修改之后输出为:
<Person, 0x7f996263fbd0>{ name = (null), height = 1.750000, age = 19}
如果将字典改为:
NSDictionary *dict = @{@"name":@"zhangsan",@"age":@19}; // 去掉了height float类型
程序直接崩溃。

如何解决上面的bug:可以在setValue:value forKeyPath:key方法调用之前进行如下处理:取出属性对应的类型,如果类型是基本数据类型,value替换为默认值(如int对应默认值为0)。

runtime提供的ivar_getTypeEncoding函数可以获取到属性的类型,返回值代表的含义如下:


height是float类型对应的TypeCode是"f"因此可以进行过滤一下,代码改动如下:
Class clazz = Person.class;
unsigned int count = 0;Person *person = [[Person alloc]init];
NSDictionary *dict = @{@"name":@"zhangsan",@"age":@19, @"height": @1.75};Ivar *ivars = class_copyIvarList(clazz, &count);
for (int i = 0; i < count; i++) {const char *cname = ivar_getName(ivars[i]);NSString *name = [NSString stringWithUTF8String:cname];NSString *key = [name substringFromIndex:1];const char *coding = ivar_getTypeEncoding(ivars[i]); // 获取类型NSString *strCode = [NSString stringWithUTF8String:coding];id value = dict[key];if ([strCode isEqualToString:@"f"]) {// 判断类型是否是floatvalue = @(0.0);}[person setValue:value forKey:key];
}
NSLog(@"%@", person);
这样就可以正常执行了,输出为:
<Person, 0x7fc75d004a00>{ name = zhangsan, height = 0.000000, age = 19}

3_1_2.应用2:NSCoding归档和解档

回到顶部
获取属性\成员列表另外一个重要的应用就是进行归档和解档,其原理和上面的kvc基本上一样,这里只是展示一些代码:
- (void)encodeWithCoder:(NSCoder *)aCoder {unsigned int count = 0;Ivar *ivars = class_copyIvarList(self.class, &count);for (int i = 0; i < count; i++) {const char *cname = ivar_getName(ivars[i]);NSString *name = [NSString stringWithUTF8String:cname];NSString *key = [name substringFromIndex:1];id value = [self valueForKey:key]; // 取出key对应的value[aCoder encodeObject:value forKey:key]; // 编码}
}
- (id)initWithCoder:(NSCoder *)aDecoder {if (self = [super init]) {unsigned int count = 0;Ivar *ivars = class_copyIvarList(self.class, &count);for (int i = 0; i < count; i++) {const char *cname = ivar_getName(ivars[i]);NSString *name = [NSString stringWithUTF8String:cname];NSString *key = [name substringFromIndex:1];id value = [aDecoder decodeObjectForKey:key]; // 解码[self setValue:value forKey:key]; // 设置key对应的value}}return self;    
}

3_2.交换方法实现

回到顶部

交换方法实现的需求场景:自己创建了一个功能性的方法,在项目中多次被引用,当项目的需求发生改变时,要使用另一种功能代替这个功能,要求是不改变旧的项目(也就是不改变原来方法的实现)。

可以在类的分类中,再写一个新的方法(是符合新的需求的),然后交换两个方法的实现。这样,在不改变项目的代码,而只是增加了新的代码 的情况下,就完成了项目的改进。

交换两个方法的实现一般写在类的load方法里面,因为load方法会在程序运行前加载一次,而initialize方法会在类或者子类在 第一次使用的时候调用,当有分类的时候会调用多次。

// 程序一运行的时候调用
+ (void)load
{// 如果是类方法,使用的是class_getClassMethod,如果是对象方法使用的是class_getInstanceMethodMethod methodOne = class_getInstanceMethod(self, @selector(methodOne:));Method methodTwo = class_getInstanceMethod(self, @selector(methodTwo:));// 交换两个方法的实现method_exchangeImplementations(methodOne, methodTwo);
}
注意的是
1.可以交换的两个方法的参数必须是匹配的,参数的类型一致。
2.如果在方法one的内部想要调用方法two,此时在方法one的内部应该用one调用,而实际上是在调用two,否则会造成死循环。
例如:
// 交换前
- (NSString *) methodOne:(NSString *)str{NSLog(@"%@", [self methodTwo:str]);return "suc";
}
// 交换后 在方法的实现中,要注意将调用two的地方,换成自己的名字
- (NSString *) methodOne:(NSString *)str{NSLog(@"%@", [self methodOne:str]);return "suc";
}
任何一个方法都有两个重要的属性:SEL是方法的编号 ,IMP是方法的实现,方法的调用过程实际上去根据SEL去寻找IMP。
在这个例子中,假设在交换之前SEL为methodOne:的方法指向着IMP1,SEL为methodTwo的方法指向着IMP2。
交换实现实际上是在底层是交换了方法编号的指向,也就是让methodOne:指向了IMP2,methodTwo指向了IMP1。

3_3.类\对象的关联对象

回到顶部
关联对象不是为类\对象添加属性或者成员变量(因为在设置关联后也无法通过ivarList或者propertyList取得) ,而是为类添加一个相关的对象,通常用于存储类信息,例如存储类的属性列表数组,为将来字典转模型的方便。 例如,将属性的名称存到数组中设置关联
const char* propertiesKey = "propertiesKey";
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Person class], &count);NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:count];for (unsigned int i = 0; i < count; ++i) {Ivar pty = ivars[i];const char *cname = ivar_getName(ivars[i]);NSString *name = [NSString stringWithUTF8String:cname];NSString *key = [name substringFromIndex:1]; // 去掉_[arrayM addObject:key];
}
free(ivars);
objc_setAssociatedObject(self, propertiesKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
NSLog(@"%@", arrayM);
输出是
(age,height,name
)

objc_setAssociatedObject方法的参数解释:

第一个参数id object, 当前对象
第二个参数const void *key, 关联的key,可以是任意类型
第三个参数id value, 被关联的对象
第四个参数objc_AssociationPolicy policy关联引用的规则,取值有以下几种:
enum {OBJC_ASSOCIATION_ASSIGN = 0,OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,OBJC_ASSOCIATION_COPY_NONATOMIC = 3,OBJC_ASSOCIATION_RETAIN = 01401,OBJC_ASSOCIATION_COPY = 01403
};
如果想要获取已经关联的对象,通过key取得即可
NSArray *pList = objc_getAssociatedObject(Person, propertiesKey);
可以将以上两种操作封装起来,为Person类增加一个properties类方法,封装上面的操作,用于方便获取类的属性列表。
const char* propertiesKey = "propertiesKey";@implementation Person
+ (NSArray *)properties {// 如果已经关联了,就依据key取出被关联的对象并返回NSArray *pList = objc_getAssociatedObject(self, propertiesKey);if (pList != nil) {return pList;}// 如果没有关联,则设置关联对象,并将对象返回unsigned int count = 0;Ivar *ivars = class_copyIvarList([self class], &count);NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:count];for (unsigned int i = 0; i < count; ++i) {Ivar pty = ivars[i];const char *cname = ivar_getName(ivars[i]);NSString *name = [NSString stringWithUTF8String:cname];NSString *key = [name substringFromIndex:1];[arrayM addObject:key];}free(ivars);objc_setAssociatedObject(self, propertiesKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);return arrayM.copy;
}
@end

3_4.动态添加方法,拦截未实现的方法

回到顶部
每个类都有都有一下两个类方法(来自NSObject)
+ (BOOL)resolveClassMethod:(SEL)sel
+ (BOOL)resolveInstanceMethod:(SEL)sel
以上两个一个使用于类方法,一个适用于对象方法。在代码中调用没有实现的方法时,也就是sel标识的方法没有实现 都会现调用这两个方法中的一个(如果是类方法就调用第一个,如果是对象方法就调用第二个)拦截。 通常的做法是在resolve的内部指定sel对应的IMP,从而完成方法的动态创建和调用两个过程,也可以不指定IMP打印错误信息后直接返回。

假如在Person类中没有-sayHi这个方法,如果对象p使用[p performSelector:@selector(sayHi) withObject:nil];那么就会必须经过Person类的resolveInstanceMethod:(SEL)sel方法,在这里为-sayHi指定实现。

void abc(id self, SEL _cmd){NSLog(@"%@说了hello", [self name]);
}@implementation Person//动态添加方法:在resolve中添加相应的方法,注意是类方法还是对象方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel
{if ([NSStringFromSelector(sel) isEqualToString:@"sayHi"]) {class_addMethod(self, sel, abc, "v@:"); // 为sel指定实现为abc}return YES;
}
@end

对实现(abc)的前两个参数的说明

每个方法的内部都默认包含两个参数,被称为隐式参数
id类型self(代表类或对象)和SEL类型的_cmd(方法编号)

class_addMethod函数参数的含义:

第一个参数Class cls, 类型
第二个参数SEL name, 被解析的方法
第三个参数 IMP imp, 指定的实现
第四个参数const char *types,方法的类型,具体参照类型的codeType那张图,但是要注意一点:Since the function must take at least two arguments—self and _cmd, the second and third characters must be “@:” (the first character is the return type).译为:因为函数必须至少有两个参数self和_cmd,第二个和第三个字符必须是“@:”。如果想要再增加参数,就可以从实现的第三个参数算起,看下面的例子就明白。
返回值:YES if the method was found and added to the receiver, otherwise NO.

为-sayHi方法的实现增加参数

调用时:
Person *p = [[Person alloc] init];p.name = @"zhangsan";
p.age = 10;
[p performSelector:@selector(sayHi:) withObject:@"world"]; // 增加了一个参数,多了冒号
对Person类中的代码做了修改
void abc(id self, SEL _cmd, NSString *content){ // 增加了一个参数contentNSLog(@"%@说了hello%@", [self name], content);
}@implementation Person//动态添加方法:在resolve中添加相应的方法,注意是类方法还是对象方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel
{if ([NSStringFromSelector(sel) isEqualToString:@"sayHi:"]) {class_addMethod(self, sel, abc, "v@:@"); // 增加了一个对象类型参数 增加了@}return YES;
}
@end
输出为:
zhangsan说了helloworld

3_5.动态创建一个类

回到顶部
动态创建一个类,为这个类添加成员变量和方法,并创建这个类型的对象:
#import "ViewController.h"
#import <objc/runtime.h>
#import <objc/message.h>
#import "Person.h"static void printSchool(id self, SEL _cmd) {NSLog(@"我的学校是%@", [self valueForKey:@"schoolName"]);
}@implementation ViewController- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {Class classStudent = objc_allocateClassPair(Person.class, "Student", 0);// 添加一个NSString的变量,第四个参数是对其方式,第五个参数是参数类型if (class_addIvar(classStudent, "schoolName", sizeof(NSString *), 0, "@")) {NSLog(@"添加成员变量schoolName成功");}// 为Student类添加方法 "v@:"这种写法见参数类型连接if (class_addMethod(classStudent, @selector(printSchool), (IMP)printSchool, "v@:")) {NSLog(@"添加方法printSchool:成功");}// 注册这个类到runtime系统中就可以使用他了objc_registerClassPair(classStudent); // 返回void// 使用创建的类id student = [[classStudent alloc] init];NSString *schoolName = @"清华大学";// 给刚刚添加的变量赋值// object_setInstanceVariable(student, "schoolName", (void *)&str);在ARC下不允许使用[student setValue:schoolName forKey:@"schoolName"];// 调用printSchool方法,也就是给student这个接受者发送printSchool:这个消息
//    objc_msgSend(student, "printSchool"); // 我尝试用这种方法调用但是没有成功[student performSelector:@selector(printSchool) withObject:nil]; // 动态调用未显式在类中声明的方法}
@end
输出的结果是:
添加成员变量schoolName成功
添加方法printSchool成功
我的学校是清华大学
-4.面试题
回到顶部

面试题

说说什么是runtime

1>OC 是一个全动态语言,OC 的一切都是基于 Runtime 实现的
平时编写的OC代码, 在程序运行过程中, 其实最终都是转成了runtime的C语言代码, runtime算是OC的幕后工作者
比如:
OC :
[[Person alloc] init]
runtime :
objc_msgSend(objc_msgSend("Person" , "alloc"), "init")
2>runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API
3>runtimeAPI的实现是用C和汇编,是一套苹果开源的框架

使用过runtime吗,用它来做什么。

本文二、三部分。

 

 

 

 

 

转载于:https://www.cnblogs.com/Mike-zh/p/4557014.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/297203.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

人人年薪百万?醒醒!

今天刷知乎&#xff0c;看见一个很有趣的帖子&#xff1a;中青校媒的一份调查结果显示&#xff1a;67.65%的大学生评估自己10年内会年薪百万。这个帖子下面一堆答主在那疯狂的讽刺挖苦&#xff0c;觉得年轻人什么都不懂就会幻想。我倒是觉得年轻人对未来充满憧憬和希望反而是好…

sql not exists用法_SQL Server 2012 高级用法(一)

最近用到了SQL Server 相关数据库等问题&#xff0c;于是整理了如下SQL Server高级用法系列。该系列不再介绍基础用法(select ,insert, update, delete, and or ,order by 等)。本文将介绍高级用法&#xff1a;Top, Like,通配符。三种高级用法。TOP Top 子句用于规定要返回的记…

谈谈女友卸妆后的感受?

1 2 医生和她的实习助手3 那个&#xff0c;打针的话能不能轻点呀4 ok&#xff0c;学会了 图自青鲤5 有点甜~6 一个人可以无聊到什么程度&#xff1f;7 所谓的前翻乾坤无敌轮回抓鱼大法。。8 《请回答1988》中的珍珠饰演者金雪演唱插曲《惠化洞》&#xff08;来源于weibo&#x…

隐形材料你见过吗?

1 提前锻炼&#xff0c;为春运做准备2 隐形的聚合物3 黄山西海地轨电车好像驶入了画中.....4 大猪蹄子&#xff0c;就是喜欢年轻的5 现在的小学生太会了吧&#xff01;6 当狗穿上了鞋&#xff0c;脚就不是自己的脚了~7 你仿佛在说我是王八&#xff1f;&#xff1f;&#xff1f;…

java中可以用浮点作为循环变量吗_Java千问:Java循环语句的几个冷门知识点你都知道吗?...

我们在进行Java开发的时候&#xff0c;经常会用到循环结构&#xff0c;但是有几个关于循环的“冷门”知识点&#xff0c;不知道你是不是都已经掌握了呢&#xff1f;我们一起来看看一、大括号可以不写吗&#xff1f;我们都知道&#xff0c;如果每次循环过程中要执行的语句只有一…

各个大学的录取通知书,哪个颜值最高?

近段时间&#xff0c;各大高校纷纷开始晒出自家的录取通知书&#xff0c;快来和小天一起评评哪家的颜值最高&#xff01;北京工业大学北京科技大学东南大学复旦大学哈尔滨工业大学南京农业大学南开大学清华大学天津大学西安交通大学中国传媒大学北京航空航天大学这不仅仅是一纸…

有关堆栈溢出(in vs 2005)的读书笔记--堆栈中 申请大数组

部分原创 66下一步 Vs 堆栈 理论修改的上限是多少 &#xff1f; 问题2&#xff1a; 全局变量去 最大的上限是多少&#xff1f; 堆栈&#xff08;休养版本测试&#xff09; 2012年3月31日17:31:27 3月 从 10^5到 10^6数组&#xff0c;导致vs… 当前&#xff1a;搭好脚手架…

烧脑又过瘾!这个被严重低估的学习方法,看完瑟瑟发抖!

▲ 点击查看科学家曾在基于脑科学和认知科学后得出一个“学习的金字塔”理论。就是下面这张图&#xff1a;最顶端的就是我们所谓的“知识”&#xff0c;但为了可以达到这个顶端&#xff0c;下面有一系列的支撑能力。而引申出来的核心概念&#xff0c;就叫“玩中学”。不要想当然…

为什么手机最后 10% 的电量很不耐,最后1%的电量最耐用?

全世界只有3.14 % 的人关注了爆炸吧知识手机电量这玩意一直都是个玄学问题。为什么我的小手机&#xff0c;最后 1% 的电量最耐用&#xff0c;每次都够我看完一个十分钟的视频。又或者是为什么我的小手机&#xff0c;最后 10% 的电量很不耐用。。。每次电池报警还有 10% 的电量&…

使用identity+jwt保护你的webapi(三)——refresh token

前言上一篇已经介绍了identity的注册&#xff0c;登录&#xff0c;获取jwt token&#xff0c;本篇来完成refresh token。开始开始之前先说明一下为什么需要refresh token。虽然jwt token有很多优点&#xff0c;但是它的缺点也是非常明显。由于jwt无状态的特性&#xff0c;所以j…

一个10*10的正方形里,最多可以放多少个直径为1的圆?为啥不是100个?

祝大家元宵节快乐知识君曾经给大家出了一道题。↓Q&#xff1a;在一个10*10的正方形里&#xff0c;最多可以放多少个直径为1的圆&#xff1f;为啥不是100个&#xff1f;关于这道题&#xff0c;后台就收到了许多留言&#xff0c;大家纷纷给出了不同的答案。五花八门的答案&#…

【开源框架】:解决方案级别的代码生成器 WebFirst

框架描述WebFirst 是一新代的 代码生成器&#xff0c;用法简单&#xff0c;功能强大&#xff0c;支持多种数据库 &#xff0c;具体功能如下&#xff1a;一、 建库、CodeFirst方式在线建表&#xff0c;没用到CodeFirst的用户可以用工具轻松体验&#xff0c;支持公共字段二、导出…

在netbeans下使用调试PHP的插件XdeBug

本人的开发环境: wamp最新官网wampserver2.2d-x32版。 下载点&#xff1a;http://nchc.dl.sourceforge.net/project/wampserver/WampServer%202/WampServer%202.2/wampserver2.2d-x32.exe PHP&#xff1a;5.3.10 apache &#xff1a;2.2.21 要使用到NetBeans的调试功能&#xf…

老娘不就是没化妆吗?你几个意思?

1 可能是环境的原因唤醒了雪橇犬的祖传技能▼2 这是花仙子吧&#xff01;怎么能生的出这么好看的崽▼3 你什么表情&#xff1f;老娘不就是没化妆吗▼4 我们是专业的&#xff0c;一般不会笑除非忍不住▼5 你知道小兔子会游泳吗▼6 狗子的快乐你体会不了▼7 谁能告诉我这到…

790页微软官方《.Net核心编程》高清版PDF,提供下载

本文档由微软技术专家编写&#xff0c;从1.1版本持续维护升级至今&#xff0c;开篇就详细对比了各版本中的差异&#xff0c;内容包含了ASP.NET Core、Blazor、WebAPI、SignalR、gRPC、EF Core、云原生&#xff0c;既是最权威的&#xff0c;也是最全面的&#xff0c;是学习跨平台…

真烧脑!60种小学奥数分类题型大汇编!做了几道题,感觉身体被掏空……

全世界只有3.14 % 的人关注了爆炸吧知识很多的家长都在问&#xff1a;小学奥数怎么学&#xff1f;有哪些重点&#xff1f;需要大量练习吗&#xff1f;其实&#xff0c;数学的题型万变不离其宗&#xff0c;了解题型&#xff0c;领悟题目的思路方法&#xff0c;一定胜过盲目狂练5…

保护我方Id | ASP.NET Core Web API使用加密Id

前言上次&#xff0c;我们介绍了hashids.net&#xff0c;可以将数值型Id加密成无意义的字符串&#xff0c;但是通过这些字符串又可以反向映射出真实的Id以供内部使用。比如B站的播放链接https://www.bilibili.com/video/BV1xK4y1VXXX应该就是这种实现方式。但是&#xff0c;我们…

用java写注册表单_利用HTML表单标签编写一个注册页面

今天我们来写一个注册页面form表单先来利用表单标签制作一个简单的注册页面,给大家说说标签的结构:页面结构大体就是这样子的~利用HTML表单标签编写一个注册页面表单标签:所有需要提交到服务器端的表单项必须使用包起来~form标签的属性:action:整个表单提交的位置(可以是一个页…

我被Dapr点燃

Dapr的热度个人认为才刚刚热起来&#xff0c;9月份我写了Dapr .NET Core实战一共10篇&#xff0c;从基础概念到简单的实战&#xff0c;但是有很多人感兴趣&#xff0c;具体表现在我个人维护的QQ群&#xff0c;人数从80人左右&#xff0c;增加到了155&#xff0c;将近增加了1倍…

Http Only Cookie保护AccessToken

【导读】JWT认证方式目前已被广泛使用&#xff0c;一直以来我们将token放在请求头中的Authorization中&#xff0c;若通过此种方式&#xff0c;一旦token被恶意窃取&#xff0c;攻击者可肆意对用户可访问资源进行任意索取我们大多都是通过登录成功后&#xff0c;响应AccessToke…