优化

iOS高性能OC二:property

2020-08-11  本文已影响0人  Trigger_o

1属性的特质:
在Xcode5之前,在头文件@proterty需要在.m搭配一个@synthesize,@proterty负责生成setter和getter,@synthesize负责创建一个示例对象,其中名称可以自定义,一般写成下划线开头;在Xcode5之后,@proterty可以直接完成这两步,这个过程叫做自动合成,并且@synthesize仍然可用,它仍然可以定义实例变量的名称,不写则默认为下划线开头+property名称,不过这个名称也只有在直接访问的时候才用得上,一般使用setter和getter,并不直接访问实例变量

2.关于原子性:
对于atomic,系统库会在属性的setter和getter中添加自旋锁,当一个线程访问的时候,其他线程无法访问,达到线程安全的目的.但是它的局限性非常大.
首先是性能的影响,可能要比nonatomic慢上20倍.
其次atomic保护的是属性本身的内存,如果属性是一个指针,那么指针的这8个字节的内存是线程安全的,一个线程访问的时候不会被其他线程修改,但是指针所指向的内存,这个属性真正的内容,并不是线程安全的.
想要达到真正的线程安全,应该在线程操作中加锁.

3.通过属性访问和直接访问:
a.直接访问不经过消息派发,因此更快
b.直接访问不经过setter,getter,属性的内存管理语义也许不会生效,比如设置为copy,而直接访问进行赋值时可能会写成持有.
b.直接访问也不会触发kvo
因此,如果不需要在getter中做些什么(比如懒加载),在对象内部,写入时通过属性访问,读取时直接访问,这么做是比较合适的
但是在初始化方法中,应该直接访问.例如有这样一种情况,父类的初始化方法将一个字符串属性A赋值为空,子类重写了A的set方法,必须接收一个非空,这样就造成了异常.

4.Equal:
4.1对于可能是同一个对象的情况,NSObject协议的isEuqal方法实现是判断两个指针指向的地址是否相等.或者可以使用hash方法.
isEuqal和hash方法都可以重写,一般来说指向同一个地址就可以认为是同一个对象了.

- (BOOL)isEqualToExample:(Example *)otherExample{
   if(self == otherExample){
       return YES;
   }
//    if(...){
//        return NO;
//    }
   return YES;
}

- (BOOL)isEqual:(id)object{
   if([object class] == [self class]){
       return [self isEqualToExample:(Example *)object];
   }
   return [super isEqual:object];
}

如果不是同一个对象,就需要自定义判断方法了, 自定义equal方法一般会把isEqual也重写,对于一些意向外的情况可以交给基类处理.

- (NSUInteger)hash{
    return [self.title hash];
}

重写hash方法,可能会引起一些性能问题,例如在一个set集合中,set是根据哈希值把元素放在不同的数组中,向set添加对象时,会根据哈希值找到相关数组,依次检查每个元素,如果有和需要添加的新元素相等的,那么说明这个元素已经在set中了.
如果需要重写hash方法,如何考虑在运算复杂度和hash值的范围之间衡量,范围太小就会出现hash碰撞的情况.

4.2判断相等的深度问题:对象的属性可能也是对象,判断到什么程度由业务逻辑决定.
对于集合类型,NSArray的isEqualToArray:是首先看长度是否相等,如果相等,则每个元素都调用isEqual方法进行比较.

在可变集合中,会出现下面这张情况.

NSMutableSet *set = [NSMutableSet new];
    NSMutableArray *arrA = @[@1,@2].mutableCopy;
    [set addObject:arrA];
    NSLog(@"set = %@",set);//set = {((1,2))}
    
    //加不进去
    NSMutableArray *arrB = @[@1,@2].mutableCopy;
    [set addObject:arrB];
    NSLog(@"set = %@",set);//set = {((1,2))}
    
    //能加进去
    NSMutableArray *arrC = @[@1].mutableCopy;
    [set addObject:arrC];
    NSLog(@"set = %@",set);//set = {((1),(1,2))}
    
    [arrC addObject:@2];
    NSLog(@"set = %@",set);//set = {((1,2),(1,2))}
    
    NSSet *set2 = set.copy;
    NSLog(@"set2 = %@",set2);//set = {((1,2))}

5.类簇
OC没有抽象类的语法,但是可以通过创建类簇来实现抽象类的思想,达到和别的语言中的抽象类一样的效果,创建类簇的一个方法是工厂模式,通过调用父类的类方法创建不同情况下需要的子类.并且父类定义了一些统一的接口.这些接口很可能在子类重写,进行各自不同的实现.
cocoa框架中频繁使用类簇,比如集合类型都是类簇.如果真要给NSArray这样的类簇添加子类,必须继承自抽象类,如NSArray或NSMutableArray,并且需要实现它的存储代码.因为抽象类本身没有实现.对于自定义的类簇,可以查看源码或者文档就方便处理了.

6.对象绑定
给一个类添加属性,并不能每次都可以创建子类,比如对象是在某些机制下创建的,并且有时候创建子类也很麻烦,这时候可以使用对象绑定,在运行时为对象新增属性.同样遵守property的内存管理语义

//关联对象,根据key(即property),设置一个值
objc_setAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>, <#id  _Nullable value#>, <#objc_AssociationPolicy policy#>)

//根据key获取值
objc_getAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>)

//移除这个对象上所有绑定的key
objc_removeAssociatedObjects(<#id  _Nonnull object#>)

//创建一个key
static void *ExampleKey = "ExampleKey"

注意key要的是一个const void *,因此这里通常使用静态全局变量,如上面最后一行所示.

7.获取属性列表

unsigned int ivarCount;
    Ivar *ivarList = class_copyIvarList(Example.class, &ivarCount);
    for (int i = 0; i < ivarCount; i++) {
        NSLog(@"%s", ivar_getName(ivarList[i]));
    }

使用class_copyIvarList获取属性列表,得到的是一个Ivar,Ivar有下标方法,可以依次通过ivar_getName函数获取到,得到的是下划线开头的变量名.
如果不通过@property定义变量,并且手动实现setter和getter,那么Ivar就是自定义的变量名.

8.动态添加class_addProperty

void setUserName(id self, SEL _cmd, NSString *name) {
    
    Ivar ivar = class_getInstanceVariable([self class], "_userName");
    
    object_setIvar(self, ivar, name);
    
}

NSString* userName(id self, SEL _cmd) {
    
    // 获取类中指定名称实例成员变量的信息
    Ivar ivar = class_getInstanceVariable([self class], "_userName");
    
    return object_getIvar(self, ivar);
    
}


void registerUserModelClass() {
    
    
    Class MyClass = objc_allocateClassPair([NSObject class], kUserModelClassName , 0);
    
    class_addIvar(MyClass, "_userName" , sizeof(NSString *), log2(sizeof(NSString *)), "@") ;
   
    objc_property_attribute_t type = { "T", "@\"NSString\"" };
    objc_property_attribute_t ownership = { "C", "" }; // C = copy
    objc_property_attribute_t backingivar  = { "V", "_userName" };
    objc_property_attribute_t attrs[] = { type, ownership, backingivar };
    
    
    class_addMethod(MyClass, @selector(setUserName:), (IMP)setUserName, "V@:@");
    class_addMethod(MyClass, @selector(userName), (IMP)userName, "@@:");

    //注册这个类到runtime系统中就可以使用他了
    objc_registerClassPair(MyClass);
    
    if (!class_addProperty(MyClass, "ad_id", attrs, 3)) {
        
        NSLog(@"add property failure");
        
    }
    
}
上一篇下一篇

猜你喜欢

热点阅读