美文网首页
iOS property的学习

iOS property的学习

作者: jayhe | 来源:发表于2020-06-07 22:26 被阅读0次
1. @property的作用

在类和扩展中
会自动设置@synthesize propertyName = _propertyName
来告诉编译器自动生成getter、setter、指定实例变量名

在分类中
不会自动生成getter、setter方法,不会生成实例变量

在协议中定义,并且类遵循该协议

@interface PropertyUsage : NSObject <PropertyTestProtocol>

@property (nonatomic, strong) NSObject *subTest;

@end

@interface PropertyUsage (TestCategory)

@property (nonatomic, strong) NSObject *categoryTest;

@end
默认不会自动生成getter、setter方法,不会指定实例变量名; 图片.png

编译器会提示需要去手动@synthesize
当手动@synthesize之后,就会自动生成getter、setter、及实例变量

在子类中定义同名的属性

@interface SubPropertyUsage : PropertyUsage

@property (nonatomic, strong) NSObject *subTest;

@end

此时编译器也会警告,说getter、setter方法会在父类中实现,同时也不会生成新的实例


图片.png

加上@synthesize subTest = _subTest_;
这里不能用_subTest编译器会报错跟父类的冲突了Property 'subTest' attempting to use instance variable '_subTest' declared in super class 'PropertyUsage'
我们debug下打印子类的方法列表

(lldb) po [[SubPropertyUsage new] _shortMethodDescription]
<SubPropertyUsage: 0x6000009ae0d0>:
in SubPropertyUsage:
    Properties:
        @property (retain, nonatomic) NSObject* subTest;  (@synthesize subTest = _subTest_;)
    Instance Methods:
        - (void) setSubTest:(id)arg1; (0x10a9824b0)
        - (id) subTest; (0x10a982460)
        - (void) .cxx_destruct; (0x10a982520)
in PropertyUsage:
    Properties:
        @property (retain, nonatomic) NSObject* subTest;  (@synthesize subTest = _subTest;)
        @property (readonly) unsigned long hash;
        @property (readonly) Class superclass;
        @property (readonly, copy) NSString* description;
        @property (readonly, copy) NSString* debugDescription;
        @property (copy, nonatomic) NSString* addressFormate;  (@synthesize addressFormate = _addressFormate;)
    Instance Methods:
        - (void) setAddressFormate:(id)arg1; (0x10a9822d0)
        - (void) setSubTest:(id)arg1; (0x10a982370)
        - (id) addressFormate; (0x10a982280)
        - (void) testPropertyUsage; (0x10a981fc0)
        - (id) subTest; (0x10a982330)
        - (void) .cxx_destruct; (0x10a9823d0)
(NSObject ...)

可以看到子类中也有一套getter、setter,并且生成了新的实例


图片.png

结论:在子类中定义同名的属性,默认是不会生成新的getter、setter、实例;当使用@synthesize指定一个跟父类不一样的实例名的时候,会生成新的getter、setter、实例

2. 修饰符的作用

2.1. readwrite
可读写权限,也是属性的默认修饰符
2.2. readonly
只读权限,编译器会只生成getter方法不生成setter方法
2.3. getter=
指定属性的get方法

小测试以下代码打印tmpName会是什么结果

@interface PropertyUsage ()

@property (nonatomic, setter=_hcSetName:, getter=_hcGetName, copy) NSString *name;

@end

@implementation PropertyUsage

@synthesize name = _hcTestName;

@end
PropertyUsage *tmpPU = [PropertyUsage new];
tmpPU.name = @"HC";
NSString *tmpName = [tmpPU valueForKey:@"name"];

代码执行到tmpName会异常退出;

kvo的查找顺序:

  • 先查询是否有get<Key>, <key>, is<Key>, or _<key>方法,有的话就调用;
  • 看实例的accessInstanceVariablesDirectly方法,返回YES,就去查找实例变量按照这个顺序 _<key>``_is<Key>``<key>``is<Key>,有就直接返回其值
  • 以上2部都没有匹配到,则会调用valueForUndefinedKey:,抛出一个异常

官方kvo文档
由于这里我们自定义了getter方法不是按照kvo查找规则的,并且实例变量的命名(name = _hcTestName)也不是按照kvo查找规则内的,所以找不到最终就异常了

2.4. setter=
指定属性的set方法
2.5. strong
强引用修饰变量,内部调用了objc_storeStrong,先判断对象本身的值是否是跟传入的值一致,一致就返回,否则就retain新值,将新值赋给对象,释放旧值

void
objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}

2.6. retain
手动管理内存是使用的强引用修饰符;内部会调用void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)赋值的方式跟strong一样

void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

2.7. copy
拷贝;内部会调用objc_setProperty_nonatomic_copy

void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, false, true, false);
}

可以看到跟retain的差异就是调用reallySetProperty的时候有个参数copy传了true;内部则对传入的newValue进行了copy操作newValue = [newValue copyWithZone:nil];也就是说用copy修饰那么得对象实现了协议NSCopying,否则就会找不到copyWithZone方法导致崩溃

小测试:一个NSArray类型的属性使用copy修饰,当对属性赋值以不可变数组时会产生副本吗

@interface PropertyUsage : NSObject

@property (nonatomic, copy) NSArray *testCopyProperty;

@end
PropertyUsage *testPU = [PropertyUsage new];
NSArray *tmpArray = [NSArray array];
testPU.testCopyProperty = tmpArray;

答案是不会;对于不可变的集合,它的copy实现就是直接返回self;由于本身是不可变的所以没有必要再开辟一块空间去存储浪费空间,只需要指向同一快内存就可以了。

2.8. assign
直接赋值不产生强引用,一般是用于非OC的基础数据类型,如果是对OC对象使用assign,则对象会在被赋值之后就释放;但是指针还指向那块被释放的对象的空间;如果后续这块空间被分配给了其他的对象,那么就会产生野指针异常

2.9. unsafe_unretained
直接赋值不产生强引用,效果跟assign类似,用来修饰OC的对象

2.10. weak
弱引用;当赋值的时候内部会调用objc_storeWeak,将变量的指针存储在以变量为key的weak表中,并将变量的isa的weakly_referenced标记位置位1,当变量释放的时候发现标记为是1就去弱引用表中找到对象的entry将其清除,以达到自动置为nil

2.11. noatomic or atomic
非原子和原子,我们看到在属性赋值的函数reallySetProperty以及获取属性值的函数objc_getProperty中会根据是否是原子操作,而判断是否加上一个os_unfair_lock,保证了多线程读写的操作是安全的,但是同时也因为加锁带来了额外的开销;这个也不能完全保证多线程的安全,比如数组属性申明原子操作,读写是安全的,那么我多线程去修改数组的元素了;所以我们一般都是用非原子操作,如果要线程安全,就自己对数据进行加锁处理保证读写改的安全
如果不显示指定,默认是atomic的

// reallySetProperty代码片段
if (!atomic) {
   oldValue = *slot;
   *slot = newValue;
} else {
   spinlock_t& slotlock = PropertyLocks[slot];
   slotlock.lock();
   oldValue = *slot;
   *slot = newValue;        
   slotlock.unlock();
}

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    if (offset == 0) {
        return object_getClass(self);
    }

    // Retain release world
    id *slot = (id*) ((char*)self + offset);
    if (!atomic) return *slot;
        
    // Atomic retain release world
    spinlock_t& slotlock = PropertyLocks[slot];
    slotlock.lock();
    id value = objc_retain(*slot);
    slotlock.unlock();
    
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value);
}

spinlock_t名字虽然是自旋锁,但是内部使用的是os_unfair_lock;由于自旋锁的优先级反转问题,苹果从iOS10开始底层的实现已经用os_unfair_lock替换了OSSpinLock
不再安全的 OSSpinLock


StripedMap<spinlock_t> PropertyLocks;

using spinlock_t = mutex_tt<LOCKDEBUG>;

class mutex_tt : nocopy_t {
    os_unfair_lock mLock;
  // 省略其他代码
}

相关文章

网友评论

      本文标题:iOS property的学习

      本文链接:https://www.haomeiwen.com/subject/bpnhtktx.html