Objective-C中与Properties有关的哪些事

2018-06-16  本文已影响12人  一半晴天

此文成于 2015/04/13

属性有两种,一种是基于实例变量,另一种是基于访问器
如果属性是连接其他对象的属性,则需要注意避免强循环引用.(虽然大部分情况下ARC已经替我们处理好大部分情况了.)

属性封装了对象的数据值

属性,就好比是类定义的给使用都的其封装的数据的接口
一个类可以封装一个或者多个数据对象,例如一个NSNumber只封装了一个数值
如下的一个类接口声明:

@interface Person:NSObject
@property NSString *firstName;
@property NSString *lastName;
@end

使用访问器来获取或者设置属性值

NSString *firstName = [somePerson firstName];
[somePerson setFirstName:@”Johnny”];

编译器为自动为属性合成成访问器方法.
合成的方法遵守下面的命名约定:

@property (readonly) NSString *fullName;

如果不声明的话,属性默认有一个(readwrite)的特性

 @property(getter=isFinished) BOOL finished;
NSString *firstName = somePerson.firstName;
somePerson.firstName = @”Johnny”;

其实这只是一个编译器提供的语法糖,实际上还是调用的访问器方法
somePerson.firstName 相当于[somePerson firstName]
somePerson.firstName = @”Johnny”相当于 [somePerson setFirstName:@”Johnny”]

@implementation Person
@synthesize firstName = instanceFirstName;
@end

注意如果使用了@synthesize指令,但是没有指定新的实例变量名,
会导致编译器生成一个跟属性名同名的实例变量名,
如下代码

@synthesize firstName;

这样相应的实例变量名会成为firstName而不是默认的_firstName

  1. 在类声明中加一对花括号并在其中声明实例变量
@interface Person:NSObject{
    NSString *_myNonPropertyInstanceVariable;
}
  1. 在类实现中加一个花括号区域,并在其中声明:
@implementation Person{
    NSString *_anotherCustomInstanceVariable;
}
  1. 创建类扩展并在其中声明:
    类扩展有一点类似于Category,但是类扩展只适用于你拥有对应类的源代码的情况
    类与类扩展同时编译.
    因为类扩展中声明中的方法必须在原始的类的实现块中实现
    声明类扩展的语法如下(类似一个Category,但是不具名,所以有时也被称之为匿名Category)
@interface ClassName (){
   id _extraCustomInstanceVariable;
}
@property NSObject *extraProperty;
@end

注意到,类扩展也可以声明属性,类扩展一般用来隐藏一些封装的数据
参考 :https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html#//apple_ref/doc/uid/TP40011210-CH6-SW1
参考中还有一点有意思的是: 可以将在一个框架中可用的方法,放在类扩展中,但是放在一个单独的头文件中,
发布框架时不会将此头文件也发布.

  1. 声明 @property (readonly) NSString *fullName;
  2. 实现 以一个computed property 的方法来实现
- (NSString *) fullName{
 return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
}

如下:

- (XYZObject *)someImportantObject{
 if(!_someImportantObject){
   _someImportantObject = [[XYZObject alloc] init];
 }
 return _someImportantObject;
}

这也实现了一种 "lazy accessor"的模式。

Property 的线程安全性问题

默认情况下 Objective-C 的属性都是 atomic

@interface XYZObject: NSObject
@property NSObject *implicitAtomicObject;
@property (atomic) NSObject *explicitAtomicObject;
@end

因此上面两个属性声明是的修饰上是等价的。
atomic 也意味着生成的getter,setter也是线程安全的。

如果确定某一个属性是不需要线程安全的保护,可以将其声明为 nonatomic
因为nonaotomic属性是直接访问,所以其速度会更快。

不过需要注意的是:

属性的atomic 并不意味着 对象的线程安全性。

通过从属关系及责任管理对象图

Objective-C 的对象是在堆上动态分配的,
这意味着需要一个指针来跟踪对象的地址。跟标量不一样,并不到通过一个指针变量的范围来决定一个对象的生命周期。只要有其他对象需要它,它就在内存中存活。
因此,相对于考虑单独的考虑每一个对象的生命周期,更应该思考他们之间的关系。
例如对于两个字符串属性 firstNamelastName 它们属于 XYZPerson,
只要XYZPerson对象还存活在内存中,这两个属性也就应该一直存活在内存中。

在 Objective-C 中,只要有一个来自于其他对象的强引用,当 XYZPerson在内存中被释放之后,
firstName,lastName 对应的字符串对象也会立即被释放。

默认情况下,Objective-C 中声明的属性之前的关系都是 强引用 strong 的关系。
有时这会自动布局循环引用的问题。
为此 Objective-C 提供了 弱引用类型 weak

对于变量来说,如果不想一个变量维护一个强引用可以将其声明为 __weak,如下:
NSObject * __weak weekVariable;

为了安全,将所变量所引用的对象被释放之后,其值将设置成为nil
不过下面的__week修饰不合适的:
NSObject * __weak someObject = [[NSObject alloc] init];
因为新创建的对象没有强引用,它被马上释放掉了,someObject马上被设置为nil

__weak 相对的是 __strong 不过我们不需要指定 __strong修饰,因为它是默认启用的。

- (void)someMethod{
   [self.weekProperty doSomething];
   // ...
   [self.weekProperty doSomethingElse];
}

在上面的方法中,无法保存方法执行到后面self.weekProperty还不为空。所以执行到后面可能导致问题。
一个简单的解决方法时,使用一个局部强引用类型变量来在使用范围内对属性引用加强。
如下:

- (void)someMethod{
   NSObject *cachedObject = self.weekProperty;
   
   [cachedProperty doSomething];
   // ...
   [cachedObject doSomethingElse];
}

局部变量的强引用可以保证在此需要此弱引用 属性的方法中,其一直不为被释放。

适用于某一些类的 不安全弱引用。

对于 weak__weak 属性一般为认为是比较安全的,因为在引用对象释放之后,
其属性或者变量会变成 nil 而不是一个危险的野指针。
但是 Cocoa及 Cocoa Touch中的一些类不能使用 weak,及 __weak修饰。
如: NSTextView,NSFont,NSColorSpace等。
这些对象的弱引用可以使用 unsafe_unretained来修饰属性。
使用 __unsafe_unretianed来修饰变量。

属性复制

如果某一个对象需要维护自己的一份属性,可以将其属性声明为
copy 这样就不会造成一个对象不修改了某一个属性,同时也影响了另一个属性。
值得注意的是:

  1. 声明为 copy 的属性的对象需要实现 NSCopying 协议。

  2. 在初始化方法(init)中,应该设置为其对象的一个拷贝,如下所示:

- (instancetype)initWithSomeOriginalString:(NSString*)aString{
   self = [super init];
   if(self){
     _instanceVariableForCopyProperty = [aString copy];
   }
}

小结

@property 的修饰属性主要有:
readonly,readwrite,strong,weak,unsafe_unretained,copy,atomic,nonatomic,getter,setter

上面没有提到两个属性,一个是 assign,一个是retain
这两个属性是在没有ARC时比较常用。但是有ARC一般就不用了。
assign是直接赋值,比较适用于标题,retian在赋值时引用计数会加1.

@property 的其他编译期修饰 IBAction / IBOutlet / IBOutlet Collection

参考: http://nshipster.com/ibaction-iboutlet-iboutletcollection/
对于编程而言,一些指令开始是必须的后来就退化成线索了。
#pragma,method type encoding, 及之前非常必须的 storage classes 随着编译器的越来越强大。
都不再是必须的了。

IB 两个字母是 Interface Builder 的缩写。Xcode 4 之前,Interface Builder 还是独立于 Xcode 而存在的。
在当时这是两个独立的应用时,为了表明哪一部分的代码 是对 Interface Builder 可见的。引入了 IBOutletIBAction
UINibDeclarations.h 中如下表示:

#ifndef IBOutlet
#define IBOutlet
#endif

#ifndef IBAction
#define IBAction void
#endif

#ifndef IBInspectable
#define IBInspectable
#endif

#ifndef IB_DESIGNABLE
#define IB_DESIGNABLE
#endif

不过 Clang 最终对其进行了一定的特殊处理。其编译成了如下的基于 attribute 的 attribute

#define IBOutlet __attribute__((iboutlet))
#define IBAction __attribute__((ibaction))

IBAction

很早之前 IBAction 就不再是必须的了。有如下签名的:-(void){name}:(id)sender 的方法在 outlets 面板就是可见的了。
但是IBAction 作为一种标识 target/action 方法也是一个不错的选择。

IBOutlet

IBOutlet 修饰现在还是必须的,

  1. 对于有 IBOutlet 修饰的也一并,properti 优于 ivar
  2. 总是将 IBOutlet 属性声明为 weak,除非实现在有必要才声明为 strong,
    因为一般一个 View 被其父 View 所拥有 ,而 top-level object 一般为 File's Owner 所拥有。
    如果不声明为 weak 有可能造成循环引用的问题。

IBOutletCollection

UINib 它是这样定义的 : #define IBOutletCollection(ClassName)
然后在 Clang 相关代码是这样的:#define IBOutletCollection(ClassName) __attribute__((iboutletcollection(ClassName)))

@property (nonatomic, strong) IBOutletCollection(UIButton) NSArray *buttons;
一般声明为 strong
值得注意的几点:

  1. 它的顺序是没有保证的
  2. 无论如何, IBOutletCollection 对应生成的是 NSArray.

由于数组字面量的出现,其变得不再那么有用了。

常见问题

  1. [※※]@synthesize和@dynamic分别有什么作用?
    参考: @synthesize vs @dynamic, what are the differences?

参考文档

https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/EncapsulatingData/EncapsulatingData.html#//apple_ref/doc/uid/TP40011210-CH5-SW6

上一篇 下一篇

猜你喜欢

热点阅读