本文主要解决如下问题
- 成员变量、实例变量、属性变量、局部变量、全局变量的概念
- @property、@synthesize、@dynamic本质
- 重写getter和setter方法注意事项
- 类别(Category)中属性Property
- 类别(Category)中添加实例变量
- Objective-C 中的点语法
- self.和下划线的区别
- 属性、成员变量、self.var、_var使用经验总结
案例讲解
本节通过实例解释成员变量、实例变量、属性变量、局部变量、全局变量的概念
1 | #import <UIKit/UIKit.h> |
1 | #import "ViewController.h" |
成员变量
- 成员变量是定义在
{}
号中的变量。(包括.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 | //.h文件代码 |
1 | .m文件中代码 |
通过runtime的两种方法就可以为类别添加一个实例变量了。
Objective-C 中的点语法
点表达式(.)
看起来与C语言中的结构体访问以及java语言汇总的对象访问有点类似,如果点表达式出现在等号=
左边,调用该属性名称的setter
方法。如果点表达式出现在=
右边,调用该属性名称的getter
方法。- OC中
点表达式(.)
其实就是调用对象的setter
和getter
方法的一种快捷方式,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来操作,避免造成死锁。