iOS property的学习

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;
  // 省略其他代码
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,717评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,501评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,311评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,417评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,500评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,538评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,557评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,310评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,759评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,065评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,233评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,909评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,548评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,172评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,420评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,103评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,098评论 2 352