iOS彻底搞清属性与成员变量

本文主要解决如下问题

  • 成员变量、实例变量、属性变量、局部变量、全局变量的概念
  • @property、@synthesize、@dynamic本质
  • 重写getter和setter方法注意事项
  • 类别(Category)中属性Property
  • 类别(Category)中添加实例变量
  • Objective-C 中的点语法
  • self.和下划线的区别
  • 属性、成员变量、self.var、_var使用经验总结

案例讲解

本节通过实例解释成员变量、实例变量、属性变量、局部变量、全局变量的概念

1
2
3
4
5
6
7
8
9
#import <UIKit/UIKit.h>
@interface MyViewController :UIViewControlle
{
UIButton *yourButton;//实例变量
int count;//成员变量
id data;//实例变量
}
@property (nonatomic, strong) UIButton *myButton;//公有属性
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#import "ViewController.h"
@interface ViewController ()
{
BOOL test;//成员变量
NSString *myString;//实例变量
}
@property(nonatomic,strong,readwrite)NSString *name;//私有属性
@end
static int hu=3;//全局变量
NSString*sttr1=@”S1ViewController”;//全局变量
@implementation ViewController
{
BOOL test1;//成员变量
}
- (void)viewDidLoad {
[super viewDidLoad];
// 局部变量
NSArray *array = [[NSArray alloc] initWithObject:@“123”,nil];
}
@end

成员变量

  • 成员变量是定义在{}号中的变量。(包括.h和.m中,yourButton、count、data、test1都是成员变量)
  • 成员变量用于类内部,无需与外界接触的变量。

实例变量

  • 如果成员变量的数据类型是一个类则称这个变量为实例变量。(yourButton、data、myString是实例变量)
  • 实例变量+基本数据类型变量=成员变量

属性变量

  • 有前缀 @property
  • 编译器会为属性自动添加存取方法(setter、getter)和适当的实例变量(属性前加下划线_var)
  • 可以通过“点语法”访问属性,编译器会把“点语法”转换为对存取方法的调用(使用“点语法”的效果与直接调用存取方法相同)。
  • 写在.m中的属性是私有属性,如上面代码中name属性,只能在本类内部使用
  • 私有属性和公有属性的区别是是否能在类外部访问,私有属性不能继承给子类,其他相同(都会自动生成get/set方法,生成下划线的实例变量)
  • 公有属性变量是用于与其他对象交互的变量。
  • 正因为属性变量要与其他对象交互,也就有了属性修饰符或者叫属性特质(attribute)。如:nonatomic,readwrite,copy等等

局部变量:

局部变量是根据其生存周期定义的,在源文件中的array,其生命周期是在以“{ }”为界限的代码块中,虽然它的名称与成员变量相同,但不是同一个变量。

全局变量:

定义:在@implementation外定义的变量(在@implementation中定义也是可以但是一般不这么干)

@property、@synthesize、@dynamic本质

@property原理

@property = ivar + getter + setter;

实例变量+get方法+set方法,也就是说使用@property 系统会自动生成setter和getter方法;

我们每次在增加一个属性,系统都会在 ivar_list 中添加一个成员变量的描述,在 method_list 中增加 setter 与 getter 方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出 setter 与 getter 方法对应的实现,在 setter 方法中从偏移量的位置开始赋值,在 getter 方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转.

@synthesize

@synthesize的作用:(1)如果属性没有手动实现setter和getter方法,编译器为你自动生成setter与getter方法。(2)可以指定与属性对应的实例变量

@dynamic

@dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成

假如一个属性被声明为 @dynamic var,而且你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。

重写getter和setter方法注意事项

只重写getter(懒加载):默认会自动生成下划线开头的变量,在getter中要使用下划线(return _var)来返回值,不能使用self.否则造成死循环

只重写setter:默认会自动生成下划线开头的变量,在setter中要使用下划线( _var = var)来接收值,不能使用self.否则造成死循环

两个都重写 :同时手动重写了一个属性的get和set方法的话,Xcode不会再自动生成带有下划线的私有成员变量了这时如果不加,@synthesize就会报错,解决方法就是添加@syntheszie var = _var**

readonly 和 writeonly情况下重写:这时属性只会生成getter或者setter方法,如果我们重写了该方法,就需要我们重新添加@synthesize

类别(Category)中属性Property

类别中只能添加方法,不能添加实例变量。我们经常看见在类别中这样写:

1
@property (nonatomic, assign) CGFloat x;

在这种情况下是不会自动生成实例变量的。这里添加的属性,其实是添加的setter和getter方法

在Objective-C提供的runtime函数中,确实有一个class_addIvar()函数用于给类添加成员变量,但是文档中特别说明:

1
This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.

意思是说,这个函数只能在“构建一个类的过程中”调用。一旦完成类定义,就不能再添加成员变量了。经过编译的类在程序启动后就runtime加载,没有机会调用addIvar。程序在运行时动态构建的类需要在调用 objc_allocateClassPair 之后,objc_registerClassPair之前才可以被使用,同样没有机会再添加成员变量。那为什么可以在类别中添加方法和属性呢?
因为方法和属性并不“属于”类实例,而成员变量“属于”类实例。我们所说的“类实例”概念,指的是一块内存区域,包含了isa指针和所有的成员变量。所以假如允许动态修改类成员变量布局,已经创建出的类实例就不符合类定义了,变成了无效对象。但方法定义是在objc_class中管理的,不管如何增删类方法,都不影响类实例的内存布局,已经创建出的类实例仍然可正常使用。

在类别中添加实例变量

那我偏偏想要在类别中添加实例变量该怎么办呢?这时候就要用到runtime了,不要忘记了Objective-C是动态语言。一种常见的办法是通过runtime.h中objc_getAssociatedObject / objc_setAssociatedObject来访问和生成关联对象。这两个方法可以让一个对象和另一个对象关联,就是说一个对象可以保持对另一个对象的引用,并获取那个对象。

1
2
3
4
5
//.h文件代码
//NSObject+IndieBandName.h
@interface NSObject (IndieBandName)
@property (nonatomic, strong) NSString *indieBandName;
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.m文件中代码
// NSObject+IndieBandName.m
#import "NSObject+Extension.h"
#import <objc/runtime.h>
static const void *IndieBandNameKey = &IndieBandNameKey;
@implementation NSObject (IndieBandName)
@dynamic indieBandName;
- (NSString *)indieBandName {
return objc_getAssociatedObject(self, IndieBandNameKey);
}
- (void)setIndieBandName:(NSString *)indieBandName {
objc_setAssociatedObject(self, IndieBandNameKey, indieBandName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

通过runtime的两种方法就可以为类别添加一个实例变量了。

Objective-C 中的点语法

  • 点表达式(.)看起来与C语言中的结构体访问以及java语言汇总的对象访问有点类似,如果点表达式出现在等号 左边,调用该属性名称的setter方法。如果点表达式出现在右边,调用该属性名称的getter方法。
  • OC中点表达式(.)其实就是调用对象的settergetter方法的一种快捷方式,self.myString = @"张三";实际就是[self setmyString:@"张三"];

self.var和_var的区别

其中self.var是调用的xx属性的get/set方法,而__var则只是使用成员变量_var,并不会调用get/set方法。两者的更深层次的区别在于,通过存取方法访问比直接访问多做了一些其他的事情(例如内存管理,复制值等),例如如果属性在@property中属性的修饰符有retain,那么当使用self.xx的时候相应的属性的引用计数器由于生成了setter方法而进行加1操作,此时的retaincount为2

属性、成员变量、self.var、_var使用经验总结

  • 需要与外部类交互的都写成属性

  • 所有属性在使用时最好使用self.来调用,其他内部使用的对象尽量用成员变量定义(减少内存占用,调用更快)

  • 需要懒加载的对象定义为属性(或私有属性)

  • 重写getter(懒加载)和setter方法时在内部使用_var来操作,避免造成死锁。