一.属性
1.实例变量与属性的区别之一: 实例变量是在编译期就硬编码到内存中的, 如果修改了类定义之后必须重新编译,否则就会出错! 从这里,我理解了为什么runtime**不能**为类Class新增实例变量, 却可以新增属性(实质只是新增了setter/getter方法,比如NSString增加一个 url的 NSString属性)
2.属性特质
①原子性atomic, 读写安全(本质是加了setter和getter都同步锁), 但不能保证是线程安全的(多线程访问下, 因为线程1可能连续修改set属性值, 而线程2可能在连续get属性值,get出来的值可能是不一样的)
如何手动实现原子性http://www.sohu.com/a/156876090_609543
如何理解automic http://blog.csdn.net/songchunmin_/article/details/53391970
②readonly, 用于保护网络数据的原始性, 如要修改,在.m文件中重新声明为readwrite即可修改.
③assign, 用于基本数据类型的访问, 直接访问
④weak, 访问对象类型,非拥有关系,能自动置nil(delegate)
如何实现weak特质:http://blog.csdn.net/lzhgxiaoxiong/article/details/47954687
⑤strong, 访问对象类型,拥有关系,引用计数+1
⑥copy, 访问对象类型, 分为深拷贝和浅拷贝, 一般用于NSString, NSArray和NSDictionary, block属性
1.方法copy拷贝出来的对象类型总是不可变类型(例如, NSString, NSDictionary, NSArray等等)
2.方法mutableCopy拷贝出来的对象类型总是可变类型(例如NSMutableString,NSMutableDictionary,NSMutableArray等等)
3.注意:属性特质中只有”copy”, 并无”mutableCopy”
参考https://www.jianshu.com/p/5254f1277dba
二.实例变量和属性的使用
1. 在get值时, 使用实例变量
2. 在set值时,使用属性
3. 在init方法里,使用实例变量set/get
4. 必须使用属性访问的情况: KVO监听, 懒加载等.
三.对象等同性
1.”等同性”约定是: 若两对象相等,则其哈希码(hash)一定相等, 但是两个哈希码相同的对象却不一定相等.
(如果不符合这一个准则,则说明没有正确重写isEqual和hash方法)
2.编写hash时,应该在”减少碰撞频度”与”降低运算复杂度”之间做取舍.
代码范例:
// 哈希码生成方法只要能保证两个相同的对象生成的是两个相同的
// 哈希码就行了(相同哈希码的对象不一定相等)
// 下面是一种比较好的哈希码生成方式,
//既能保持高效率,又能使生成的哈希码至少位于一定范围之内,而不会过于频繁地重复。
-(NSUInteger)hash{
NSUInteger firstNameHash = [_firstName hash];
NSUInteger lastNameHash = [_lastName hash];
NSUInteger ageHash = _age;
return firstNameHash ^ lastNameHash ^ ageHash;
}
3.自定义类可以编写属于自己的”等同性判定方法”, 比如Person类可以写一个isEqualToPerson: 并注意需要重写isEqual方法(如果是后台数据,也可以根据数据库给的唯一id来判定等同性).
代码范例:
-(BOOL)isEqualToPerson:(EOCPerson*)otherPerson{
// 如果指针相等,则指向同一对象,所以相等
if(self == object) return YES;
// 只有每个属性都相等,才认为两个对象相等。
if(![_firstName isEqualToString:otherPerson.firstName]) return NO;
if(![_lastName isEqualToString:otherPerson.lastName]) return NO;
if(_age != otherPerson.age) return NO;
return YES
}
-(BOOL)isEqual:(id)object{
// 如果两个对象属于同一个类,则由自己的方法判定,否则交由超类判定
if([self class] != [object class]){
return [self isEqualToPerson:(EOCPerson*)obj];
}else{
return [super isEqual:object];
}
}
四.以”类族模式”隐藏[抽象基类]的实现细节
类族(class cluster)是一种很有用的设计模式,可以隐藏”抽象基类”背后的实现细节,比如大部分的集合collection和UIButton. 还有,类族的”等同性”需要使用isMemberOf和isKindOf来判定,而不是isEqual方法.
范例如下:
1.使用”工厂模式”创建类族
// EOCEmployee.h 抽象基类
typedef NS_ENUM(NSUInteger, EOCEmployeeType){
EOCEmployeeTypeDeveloper,
EOCEmployeeTypeDesigner,
EOCEmployeeTypeFinance,
};
@interface EOCEmployee : NSObject
@property (copy) NSString *name;
@property NSUInteger salary;
+ (EOCEmployee*)employeeWithType:(EOCEmployeeType)type;
- (void)doADayWork;
@end
// EOCEmployee.m
@implementation EOCEmployee
// 使用工厂模式创建类簇
+ (EOCEmployee*)employeeWithType:(EOCEmployeeType)type{
// 根据传入的雇员类型参数返回不同的子类对象
switch (type) {
case EOCEmployeeTypeDeveloper:
return [EOCEmployeeDeveloper new];
break;
case EOCEmployeeTypeDesigner:
return [EOCEmployeeDesigner new];
break;
case EOCEmployeeTypeFinance:
return [EOCEmployeeFinance new];
break;
}
}
-(void)doADayWork{
// 由子类实现, 这里可以抛出exception来警告!
}
@end
// EOCEmployeeTypeDeveloper枚举类型对应的子类,其它两个子类类似
// EOCEmployeeDeveloper.h
@interface EOCEmployeeDeveloper : EOCEmployee
@end
// EOCEmployeeDeveloper.m
@implementation EOCEmployeeDeveloper
- (void)doADayWork{
// 具体实现代码
}
@end
五.使用强关联对象(属性)
有时候类的实例可能是由某种机制所创建的,而开发者无法令这种机制创建出自己所写的子类实例。之中情况就需要用关联对象来解决问题。 可以给某关联对象关联许多其他对象,这些对象通过键来区分。存储对象值的时候可以指明存储策略,用以维护相应的内存管理语义。
关联类型 等效的属性特质
OBJC_ASSOCIATION_ASSIGN assgin
OBJC_ASSOCIATION_RETAIN_NONATOMIC nonatomic,retain
OBJC_ASSOCIATION_COPY_NONATOMIC nonatomic,copy
OBJC_ASSOCIATION_RETAIN retain
OBJC_ASSOCIATION_COPY copy
下列API可以管理关联对象
// 以给定的键和策略为某对象设置关联对象值
void objc_setAssociatedObject(id object, void *key, id value,
objc_AssociationPolicy policy)
// 根据给定的键从某对象中获取关联对象值
id objc_getAssociatedObject(id object, void *key)
// 移除指定对象的全部关联对象
void objc_removeAssociatedObjects(id object)
下面是一个关联对象用法的示例,示例使用的是给UIAlertView类新增一个block类型的属性对象:
// 导入运行时系统框架
#import <objc/runtime.h>
// 定义全局静态变量做键
static void *EOCMyAlertViewKey = "EOCMyAlertViewKey";
- (void)askUserAQuestion{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question" message:@"What do you want to do?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Continue", nil];
// 定义一个处理按钮逻辑的块
void (^block)(NSUInteger) = ^(NSUInteger buttonIndex){
if (buttonIndex == 0) {
// 停止
}
else{
// 继续
}
};
// 将块设置为alert的关联对象
objc_setAssociatedObject(alert, EOCMyAlertViewKey, block, OBJC_ASSOCIATION_COPY);
[alert show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
// 获取关联对象中的块
void (^block)(NSUInteger) = objc_getAssociatedObject(alertView, EOCMyAlertViewKey);
//执行
block(buttonIndex);
}
这样做的好处是,将创建警告视图与处理操作结果的代码放到了一起,这样能增强代码的可读性。需要注意的是,块可能要捕获某些变量,这也许会造成保留环。因为关联对象之间的关系并没有正式的定义,其内存管理语句是在关联的时候才定义的,而不是接口中预先定好的。使用块作关联对象的时候一定要小心,更好的做法是将块保存为子类中的属性。