最近比较清闲,就把以前学习的过程记录下吧,多少年后如果能在互联网上找到自己的痕迹,想想还是一件蛮值得高兴的事情
涉及到的知识点
@dynamic
objc/runtime.h
@dynamic
说起dynamic 就要提跟它相关的@property 和 @synthesize
@property 是iOS 6以后出现的,使用它生成的变量,编译器会自动帮我们生成setter 和 getter方法,还有一个系统默认生成的以 _开头的变量,它相当于在.m中帮我们自动@synthesize varName = _varName 。@dynamic 就是告诉编译器,这两个方法我自己去实现,让编译器去先通过编译,但是如果运行的时候不去动态绑定它的setter 和getter方法,而去调用它的话,程序就会崩溃.
NSUserDefaults
我相信项目中大家都有用到NSUserDefaults ,而且非常普遍,用起来也顺手。最常用的就是我们用它来保存用户的登录状态,然后一些登录之后才能做的操作,就从本地去取这个状态的值,从而去判断用户是否登录
比方说有一个需求是保存用户的手机号码,下次进入登录页面的时候,免除用户填写手机号码的操作
[[NSUserDefaults standardUserDefaults] setObject:@"18888888888" forKey:@"phoneAccount"];
[[NSUserDefaults standardUserDefaults] synchronize];
其实代码很简单,但是每一次都要去写两行代码才能实现这个操作。当然我们可以把它写成宏,比方说这样
#define SET_OBJECT(object, key) \
({ \
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; \
[defaults setObject:object forKey:key]; \
[defaults synchronize]; \
})
这样的话,又简单了一些。但是往往我们的项目里面并不只是登录这块需要用到NSUserDefaults ,需要记录的数据不止这些。那么我们想把代码写的更规范些,更清晰,更具可读性的话,我们最好还是建立一个.h和.m文件专门记录我们所需要存储数据的key值。
当然你可以只建立一个.h文件 使用宏去记录,但是苹果推荐我们使用这种方式去记录一个常量
有了上面这些操作是不是就变的很方便了,确实方便。但是有一个第三方的轮子,能让我们更方便的去替代上面这些操作
GVUserDefaults GitHub
到现在为止已经800多star了,还是蛮好用的。直接看源码,它的源码只有一个.h 和一个.m文件。.h中只有一个创建单例的类方法
+ (instancetype)standardUserDefaults;
我们一步一步看,点进去看它的init方法
- (instancetype)init {
self = [super init];
if (self) {
// 获取一个SEL 类型的 选择器
SEL setupDefaultSEL = NSSelectorFromString([NSString stringWithFormat:@"%@pDefaults", @"setu"]);
// 这个if 里面的操作后面会说到
if ([self respondsToSelector:setupDefaultSEL]) {
NSDictionary *defaults = [self performSelector:setupDefaultSEL];
NSMutableDictionary *mutableDefaults = [NSMutableDictionary dictionaryWithCapacity:[defaults count]];
for (NSString *key in defaults) {
id value = [defaults objectForKey:key];
NSString *transformedKey = [self _transformKey:key];
[mutableDefaults setObject:value forKey:transformedKey];
}
// 相当于把mutableDefaults 里面的数据 配置为本地的默认值
[self.userDefaults registerDefaults:mutableDefaults];
}
[self generateAccessorMethods];
}
return self;
}
我们再点进去 方法 generateAccessorMethods ,核心代码也都在这里
- (void)generateAccessorMethods {
unsigned int count = 0;
objc_property_t *properties = class_copyPropertyList([self class], &count);
self.mapping = [NSMutableDictionary dictionary];
for (int i = 0; i < count; ++i) {
objc_property_t property = properties[i];
const char *name = property_getName(property);
const char *attributes = property_getAttributes(property);
char *getter = strstr(attributes, ",G");
if (getter) {
getter = strdup(getter + 2);
getter = strsep(&getter, ",");
} else {
getter = strdup(name);
}
SEL getterSel = sel_registerName(getter);
free(getter);
char *setter = strstr(attributes, ",S");
if (setter) {
setter = strdup(setter + 2);
setter = strsep(&setter, ",");
} else {
asprintf(&setter, "set%c%s:", toupper(name[0]), name + 1);
}
SEL setterSel = sel_registerName(setter);
free(setter);
NSString *key = [self defaultsKeyForPropertyNamed:name];
[self.mapping setValue:key forKey:NSStringFromSelector(getterSel)];
[self.mapping setValue:key forKey:NSStringFromSelector(setterSel)];
IMP getterImp = NULL;
IMP setterImp = NULL;
char type = attributes[1];
switch (type) {
case Short:
case Long:
case LongLong:
case UnsignedChar:
case UnsignedShort:
case UnsignedInt:
case UnsignedLong:
case UnsignedLongLong:
getterImp = (IMP)longLongGetter;
setterImp = (IMP)longLongSetter;
break;
case Bool:
case Char:
getterImp = (IMP)boolGetter;
setterImp = (IMP)boolSetter;
break;
case Int:
getterImp = (IMP)integerGetter;
setterImp = (IMP)integerSetter;
break;
case Float:
getterImp = (IMP)floatGetter;
setterImp = (IMP)floatSetter;
break;
case Double:
getterImp = (IMP)doubleGetter;
setterImp = (IMP)doubleSetter;
break;
case Object:
getterImp = (IMP)objectGetter;
setterImp = (IMP)objectSetter;
break;
default:
free(properties);
[NSException raise:NSInternalInconsistencyException format:@"Unsupported type of property \"%s\" in class %@", name, self];
break;
}
char types[5];
snprintf(types, 4, "%c@:", type);
class_addMethod([self class], getterSel, getterImp, types);
snprintf(types, 5, "v@:%c", type);
class_addMethod([self class], setterSel, setterImp, types);
}
free(properties);
}
我们都知道OC是一门动态语言,严重依赖于runtime库。
类Class 在runtime库中由结构体组成,它在runtime中的结构体我们也不会陌生.
typedef struct objc_class *Class;
/*
这是由编译器为每个类产生的数据结构,这个结构定义了一个类.这个结构是通过编译器在执行时产生,在运行时发送消息时使用.因此,一些成员改变了类型.编译器产生"char* const"类型的字符串指针替代了下面的成员变量"super_class"
*/
struct objc_class {
struct objc_class* class_pointer; /* 指向元类的指针. */
struct objc_class* super_class; /* 指向父类的指针. 对于NSObject来说是NULL.*/
const char* name; /* 类的名称. */
long version; /* 未知. */
unsigned long info; /* 比特蒙板. 参考下面类的蒙板定义. */
long instance_size; /* 类的字节数.包含类的定义和所有父类的定义 */
#ifdef _WIN64
long pad;
#endif
struct objc_ivar_list* ivars; /* 指向类中定义的实例变量的列表结构. NULL代表没有实例变量.不包括父类的变量. */
struct objc_method_list* methods; /* 链接类中定义的实例方法. */
struct sarray * dtable; /* 指向实例方法分配表. */
struct objc_class* subclass_list; /* 父类列表 */
struct objc_class* sibling_class;
struct objc_protocol_list *protocols; /* 要实现的原型列表 */
void* gc_object_type;
};
先看第一个方法
objc_property_t *properties = class_copyPropertyList([self class], &count);
获取当前Class的所有属性
然后for循环 获取每一个属性的name 和 属性特性描述字符串
const char *attributes = property_getAttributes(property);
我们也可以利用函数
unsigned int attrCount = 0;
objc_property_attribute_t * attrs = property_copyAttributeList(property, &attrCount);
for (unsigned int j = 0; j < attrCount; j ++) {
objc_property_attribute_t attr = attrs[j];
const char * name = attr.name;
const char * value = attr.value;
}
free(attrs);
获取变量的属性的特性,objc_property_attribute_t结构体包含name和value 常用的属性:
属性类型 name值:T value:变化
编码类型 name值:C(copy) &(strong) W(weak) 空(assign) G(getter=method)S(setter=method)D(dynamic)等 value:无
非/原子性 name值:空(atomic) N(Nonatomic) value:无
变量名称 name值:V value:变化
比方说有一个属性 @property (nonatomic, copy) NSString *teacher;
那么使用property_copyAttributeList 获取到的objc_property_attribute_t结构体列表中的描述就是 T@"NSString",C,N,V_teacher
接着往下看,源码中这一段
char *getter = strstr(attributes, ",G");
if (getter) {
getter = strdup(getter + 2);
getter = strsep(&getter, ",");
} else {
getter = strdup(name);
}
SEL getterSel = sel_registerName(getter);
free(getter);
char *getter = strstr(attributes, ",G"); 这个方法是什么意思呢?
从代码上来看 应该是用来判断它的getter方法存不存在
我们可以建立一个测试demo来测试一下,还是teacher来举例
我在if 和 else 里面分别打上断点,并且运行
发现它并没有走到if 里面去 ,而且getter为null 根据我们对@property 所知道的,编译器会帮助我们自动生成setter方法 和 getter 方法啊,按理说getter应该是有值的啊。再接着往下看,如果我在声明属性的时候这样来写
然后再运行来看
发现它就走到if 里面去了,由此来看 char *getter = strstr(propertyAttr, ",G"); 这个函数就用来判断通过描述字符串获取的objc_property_attribute_t()结构体里面有没有"G"的属性,如果有就说明指定了getter方法,就由此描述字符串生成getter的SEL 如果没有就用变量名称生成
同样的setter方法,跟getter方法差不多。紧接着这两句方法
NSString *key = [self defaultsKeyForPropertyNamed:name];
[self.mapping setValue:key forKey:NSStringFromSelector(getterSel)];
[self.mapping setValue:key forKey:NSStringFromSelector(setterSel)];我们点进去看下
- (NSString *)defaultsKeyForPropertyNamed:(char const *)propertyName {
NSString *key = [NSString stringWithFormat:@"%s", propertyName];
return [self _transformKey:key];
}
- (NSString *)_transformKey:(NSString *)key {
if ([self respondsToSelector:@selector(transformKey:)]) {
return [self performSelector:@selector(transformKey:) withObject:key];
}
return key;
}
以变量名为key值 ,SEL 对应的字符串为value 保存在mapping 里面。
下面的
IMP getterImp = NULL;
IMP setterImp = NULL;
char type = attributes[1];
switch (type) {
case Short:
case Long:
case LongLong:
case UnsignedChar:
case UnsignedShort:
case UnsignedInt:
case UnsignedLong:
case UnsignedLongLong:
getterImp = (IMP)longLongGetter;
setterImp = (IMP)longLongSetter;
break;
这里的代码都是取描述字符串里第二个字节字符串去判断变量所属类型,分别生成setter和getter的IMP地址.
static long long longLongGetter(GVUserDefaults *self, SEL _cmd) {
NSString *key = [self defaultsKeyForSelector:_cmd];
return [[self.userDefaults objectForKey:key] longLongValue];
}
static void longLongSetter(GVUserDefaults *self, SEL _cmd, long long value) {
NSString *key = [self defaultsKeyForSelector:_cmd];
NSNumber *object = [NSNumber numberWithLongLong:value];
[self.userDefaults setObject:object forKey:key];
}
最后就是动态添加setter方法 和 getter方法
class_addMethod([self class], getterSel, getterImp, types);
class_addMethod([self class], setterSel, setterImp, types);
我是这样使用他们
创建GVUserDefaults 的分类,.h文件
#define UserInfo [GVUserDefaults standardUserDefaults]
@property(nonatomic,weak)NSString* nickName;
@property(nonatomic,weak)NSString* account;
@property(nonatomic,weak)NSString* password;
// 恢复到初始状态
- (void) restore;
.m文件
@dynamic nickName;
@dynamic password;
@dynamic account;
- (void)restore{
[self setValuesForKeysWithDictionary:[self setupDefaults]];
}
// 这个方法就是在init方法里面if 里面设置默认数据的方法
- (NSDictionary *)setupDefaults {
static NSDictionary* dic = nil;
if (dic) {
return dic;
}else{
dic = @{
@"nickName":@"",
@"password":@"",
@"account":@"",
};
return dic;
}
}
这样的话如果我们想使用UserDefaults 存储一个值,就变的非常简单,只需要在需要存储的地方写上 UserInfo.变量名 = 想要存储的值就可以了。
当然github上GVUserDefaults 还提供了更改key值的方法,这里我就不写了,大家可以自己学习下。
总的来说,就是利用runtime库去动态添加setter和getter方法。runtime其实在我们的平常工作中用处非常多,动态添加方法,交换方法,包括kvo的实现原理也是去动态监听setter和getter方法。
学无止境啊,让我们一起努力吧。。。。