1.get与set方法
set方法
作用:设置成员变量的值
-
命名规范:
- 必须是对象方法
- 返回值类型为void
- 方法名必须以set开头,而且后面跟上成员变量名去掉”_” 首字母必须大写
- 必须提供一个参数,参数类型必须与所对应的成员变量的类型一致
- 形参名称和成员变量去掉下划线相同
set方法的好处
- 不让数据暴露在外,保证了数据的安全性
- 对设置的数据进行过滤
get方法
作用:获取成员变量的值
-
命名规范:
- 必须是对象方法
- 必须有返回值,返回值的类型和成员变量的类型一致
- 方法名必须是成员变量去掉下划线
- 一定是没有参数的
-
getter方法的优点:
- 可以让我们在使用getter方法获取数据之前,对数据进行加工;
注意
只允许外界读取,而不允许外界修改,采用get方 如果某一个属性只提供了getter方法, 没有提供setter方法, 我们称这个属性为只读属性
如果某一个属性只提供了setter方法, 没有提供getter方法, 我们称这个属性为只写属性
如果某一个属性同时提供了setter方法和getter方法, 我们称这个属性为可读可写的属性
如果某一个属性没有提供了getter和setter, 我们称这个属性为私有属性
2.点语法
本质
:点语法的本质是方法的调用,当使用点语法时,编译器会自动展开成相应的方法
注意
:
- 本质是方法的调用,而不是访问成员变量,当使用点语法时,编译器会自动展开成相应的方法调用
- 转换成相应的对setter和getter方法调用,如果没有set和get方法,则不能使用点语法。
造成死循环的例子
- (void) setAge:(int)age {
// 下面的代码会引发死循环
self.age = age;
//编译器展开后 [self setAge:age]
}
- (int) age {
// 下面的代码会引发死循环
return self.age;
// 编译器展开后 [self age]
}
- 当点语法使用在 “=“赋值符号左侧的时候,点语法会被展开为setter方法的调用,其他情况(等号右侧、直接使用)为点语法展开为getter方法的调用
3.继承
方法重写
- 在子类中实现与父类中同名的方法,称之为方法重写;
- 重写以后当给子类发送这个消息的时候,执行的是在子类中重写的那个方法,而不是父类中的方法。
- 如果想在子类中调用被子类重写的父类的方法,可以通过super关键字
- 使用场景:当从父类继承的某个方法不适合子类,可以在子类中重写父类的这个方法。
继承中方法调用的顺序
- 1、在自己类中找
- 2、如果没有,去父类中找
- 3、如果父类中没有,就去父类的父类中
- 4、如果父类的父类也没有,就还往上找,直到找到基类(NSObject)
- 5、如果NSObject都没有就报错了,如果找到了就执行这个方法,就不再往后查找了
注意事项
子类不能定义和父类同名的成员变量,私有成员变量也不可以;因为子类继承父类,子类将会拥有父类的所有成员变量,若在子类中定义父类同名成员变量 属于重复定义。
OC类支持单一继承,不支持
多继承
;也就是说一个类只能有一个直接父类OC类支持
多层继承
4.实例变量修饰符
作用域
- @public (公开的)在有对象的前提下,任何地方都可直接访问。在框架外部相当于@private
- @protected (受保护的)只能在当前类和子类的对象方法中访问
- @private (私有的)只能在当前类的对象方法中才能直接访问
- @package (框架级别的)作用域介于私有和公开之间,只要处于同一个框架中相当于
访问区域
- @private私有成员是能被继承,也不能被外部方法访问。
- @public 公有成员能被继承,也能被外部方法访问。
- @protected 保护成员能够被继承,不能够被外部方法访问。
注意事项
- 在@interface @end之间声明的成员变量如果不做特别的说明,其默认是protected 的。
- 一个类继承了另一个类,那么就拥有了父类的所有成员变量和方法,注意所有的成员变量它都拥有,只是有的它不能直接访问。例如@private
5.实例变量
- 实例变量(成员变量)既可以在@interface中定义, 也可以在@implementation中定义
- 写在@implementation中的成员变量, 默认就是私有的成员变量, 并且和利用@private修饰的不太一样, 在@implementation中定义的成员变量在其它类中无法查看, 也无法访问
- 在@implementation中定义的私有变量只能在本类中访问
6.@property和@synthesize基本概念
-
@property
和@synthesize
相同点:- 是编译器的指令(用来告诉编译器要做什么!)
- 免去手工书写getterr和setter方法繁琐的代码
- 异同
- @property 用在
声明
文件中告诉编译器声明成员变量的的访问器(getter/setter)方法 -
@synthesize
用在实现
文件中告诉编译器实现成员变量的的访问器(getter/setter)方法
- @property 用在
- @synthesize编写步骤
1.在@implementation和@end之间写上@synthesize
2.在@synthesize后面写上和@property中一样的属性名称, 这样@synthesize就会将@property生成的什么拷贝到@implementation中
3.由于getter/setter方法实现是要将传入的形参 给属性和获取属性的值,所以在@synthesize的属性后面写上要将传入的值赋值给谁和要返回哪个属性的值, 并用等号连接
@synthesize注意点
-
@synthesize age = _age;
- setter和getter实现中会访问成员变量_age
- 如果成员变量_age不存在,就会自动生成一个@private的成员变量_age
-
@synthesize age;
- setter和getter实现中会访问@synthesize后同名成员变量age
- 如果成员变量age不存在,就会自动生成一个@private的成员变量age
多个属性可以通过一行@synthesize搞定,多个属性之间用逗号连接
@synthesize age = _age, name = _name;
7.new方法实现原理
new方法实现原理
- 完整的创建一个可用的对象:
Dog *dog = [Dog new]
- new方法的内部会分别调用两个方法来完成3件事情:
- ( 1 )使用alloc方法来分配存储空间(返回分配的对象);
- ( 2 )使用init方法来对对象进行初始化。
- ( 3 )返回对象的首地址
- 可以把new方法拆开如下:
- (1)调用类方法+alloc分配存储空间,返回未经初始化的对象
Person *p1=[person alloc];
- (2)调用对象方法-init进行初始化,返回对象本身
Person *p2=[p1 init]
;
- (1)调用类方法+alloc分配存储空间,返回未经初始化的对象
- (3)以上两个过程整合为一句:
Person *p=[[Person alloc] init];
- 说明:
- alloc 与 init合起来称为构造方法,表示构造一个对象
- alloc 方法为对象分配存储空间,并将所分配这一块区域全部清0.
- init方法是初始化方法(构造方法),用来对象成员变量进行初始化,默认实现是一个空方法。
注意
- 作用是等价的
Person *p1 = [Person new];
Person *p = [[Person alloc] init];
- iOS 程序通常使用
[[类名 alloc] init]
的方式创建对象,因为这个可以与其他initWithXX:...
的初始化方法,统一来。代码更加统一
8.构造方法
1.系统自带的构造方法
-
重写init方法
- 想在对象创建完毕后,成员变量马上就有一些默认的值就可以重写init方法
-
super init
的作用:面向对象的体现,先利用父类的init方法为子类实例的父类部分属性初始化
- self为什么要赋值给
super init
:为了防止父类的初始化方法release掉了self指向的空间并重新alloc了一块空间。还有[super init]可能alloc失败,这时就不再执行if中的语句。
-
- 想在对象创建完毕后,成员变量马上就有一些默认的值就可以重写init方法
- (id)init {
if (self = [super init]) {
// Initialize self.
}
return self;
}
-
使用注意
- 子类拥有的成员变量包括自己的成员变量以及从父类继承而来的成员变量,在重写构造方法的时候应该首先对从父类继承而来的成员变量先进行初始化。
- 原则:先初始化父类的,再初始化子类的。
- 先调用父类的构造方法[super init];
- 再进行子类内部成员变量的初始化。
- 原则:先初始化父类的,再初始化子类的。
- 子类拥有的成员变量包括自己的成员变量以及从父类继承而来的成员变量,在重写构造方法的时候应该首先对从父类继承而来的成员变量先进行初始化。
千万不要把
self = [super init]
写成self==
[super init]重写构造方法的目的:为了让对象方法一创建出来,成员变量就会有一些固定的值。
2.自定义构造方法
规范
- ( 1 )一定是对象方法,以减号开头
- ( 2 )返回值一般是instancetype类型
- ( 3 )方法名必须以initWith开头
示例
@interface Person : NSObject
@property int age;
@property NSString *name;
// 当想让对象一创建就拥有一些指定的值,就可以使用自定义构造方法
- (id)initWithName:(NSString *)name;
- (id)initWithAge:(int)age andName:(NSString *)name;
@end
3.自定义类工厂方法
概念
- 类工厂方法是一种用于分配、初始化实例并返回一个它自己的实例的类方法。类工厂方法很方便,因为它们允许您只使用一个步骤(而不是两个步骤)就能创建对象. 例如new
规范
- (1)一定是+号开头
- (2)返回值一般是instancetype类型
- (3)方法名称以类名开头,首字母小写
子父类中的类工厂方法
- 由于子类默认会继承父类所有的方法和属性, 所以类工厂方法也会被继承
- 由于父类的类工厂方法创建实例对象时是使用父类的类创建的, 所以如果子类调用父类的类工厂方法创建实例对象,创建出来的还是父类的实例对象
- 为了解决这个问题, 以后在自定义类工厂时候不要利用父类创建实例对象, 改为使用self创建, 因为self谁调用当前方法self就是谁
示例
@interface Person : NSObject
+ (id)person;
@end
@implementation Person
+ (id)person
{
// return [[Person alloc]init];
// 谁调用这个方法,self就代表谁
// 注意:以后写类方法创建初始化对象,写self不要直接写类名
return [[self alloc]init];
}
@end
@interface Student : Person
@property NSString *name;
@end
@implementation Student
@end
int main(int argc, const char * argv[])
{
Student *stu = [Student person];// [[Student alloc] init]
[stu setName:@"flower"];
}
9.类的本质
1.类的本质
- 类的本质其实也是一个对象(类对象)
- 程序中第一次使用该类的时候被创建,在整个程序中只有一份。
- 此后每次使用都是这个类对象,它在程序运行时一直存在。
- 类对象是一种数据结构,存储类的基本信息:类大小,类名称,类的版本,继承层次,以及消息与函数的映射表等
- 类对象代表类,Class类型,对象方法属于类对象
- 如果消息的接收者是类名,则类名代表类对象
- 所有类的实例都由类对象生成,类对象会把实例的isa的值修改成自己的地址,每个实例的isa都指向该实例的类对象
2.如何获取类对象
- 通过实例对象
格式:[实例对象 class ];
如: [dog class];
- 通过类名获取(类名其实就是类对象)
格式:[类名 class];
如:[Dog class]
3.类对象的用法
- 用来调用类方法
[Dog test];
Class c = [Dog class];
[c test];
- 用来创建实例对象
Dog *g = [Dog new];
Class c = [Dog class];
Dog *g1 = [c new];
4.OC实例对象 类对象 元对象之间关系
-
Objective-C是一门面向对象的编程语言。
- 每一个对象 都是一个类的实例。
- 每一个对象 都有一个名为isa的指针,指向该对象的类。
- 每一个类述了一系列它的实例的特点,包括成员变量的列表,成员函数的列表等。
- 每一个对象都可以接受消息,而对象能够接收的消息列表是保存在它所对应的类中。
在Xcode中按Shift + Command + O打开文件搜索框,然后输入NSObject.h和objc.h,可以打开 NSObject的定义头文件,通过头文件我们可以看到,NSObject就是一个包含isa指针的结构体.
-
因为类也是一个对象,那它也必须是另一个类的实例,这个类就是元类 (metaclass)。
- 元类保存了
类方法
的列表。当一个类方法
被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有则该元类会向它的父类查找该方法,直到一直找到继承链的头。 - 元类(metaclass)也是一个对象,那么元类的isa指针又指向哪里呢?为了设计上的完整,所有的元类的isa指针都会指向一个根元类(root metaclass)。
- 根元类(root metaclass)本身的isa指针指向自己,这样就行成了一个闭环。上面说到,一个对象能够接收的消息列表是保存在它所对应的类中的。在实际编程中,我们几乎不会遇到向元类发消息的情况,那它的isa 指针在实际上很少用到。不过这么设计保证了面向对象的干净,即所有事物都是对象,都有isa指针。
- 由于
类方法
的定义是保存在元类(metaclass)中,而方法调用的规则是,如果该类没有一个方法的实现,则向它的父类继续查找。所以为了保证父类的类方法可以在子类中可以被调用,所以子类的元类会继承父类的元类,换而言之,类对象和元类对象有着同样的继承关系。
- 元类保存了
5.load方法和initialize方法
-
load方法
- 在程序启动的时候会加载所有的类和分类,并调用所有类和分类的+load方法(只会调用一次)
- 先加载父类,再加载子类;也就是先调用父类的+load,再调用子类的+load
- 先加载元原始类,再加载分类
- 不管程序运行过程有没有用到这个类,都会调用+load加载
-
initialize方法
- 在第一次使用某个类时(比如创建对象等),只会调用一次+initialize方法
- 一个类只会调用一次+initialize方法,先调用父类的,再调用子类的
10.SEL类型
概念
SEL类型代表着方法的签名,在类对象的方法列表中存储着该签名与方法代码的对应关系
每个类的方法列表都存储在类对象中
每个方法都有一个与之对应的SEL类型的对象
根据一个SEL对象就可以找到方法的地址,进而调用方法
-
SEL类型的定义
- typedef struct objc_selector *SEL;
首先把test这个方法名包装成sel类型的数据
根据SEL数据到该类的类对象中,去找对应的方法的代码,如果找到了就执行该代码
如果没有找到根据类对象上的父类的类对象指针,去父类的类对象中查找,如果找到了,则执行父类的代码
如果没有找到,一直像上找,直到基类(NSObject)
如果都没有找到就报错。
注意
:在这个操作过程中有缓存,第一次找的时候是一个一个的找,非常耗性能,之后再用到的时候就直接使用。
使用
-
定义普通的变量
- 如:SEL sel = @selector(show);
作为方法实参与NSObject配合使用
-
检验对象是否实现了某个方法
- - (BOOL) respondsToSelector: (SEL)selector 判断实例是否实现这样方法
- + (BOOL)instancesRespondToSelector:(SEL)aSelector;
示例
BOOL flag;
Person *obj = [[Person alloc] init];
// [对象 respondsToSelector]用于判断是否包含某个对象方法
flag = [obj respondsToSelector:@selector(objectFun)]; //YES
flag = [obj respondsToSelector:@selector(classFun)]; //NO
// [类名 instancesRespondToSelector]用于判断是否包含某个对象方法
// instancesRespondToSelectorr只能写在类名后面, 等价于 [对象 respondsToSelector]
flag = [Person instancesRespondToSelector:@selector(objectFun)]; //YES
flag = [Person instancesRespondToSelector:@selector(classFun)]; //NO
- 让对象执行某个方法
- - (id)performSelector:(SEL)aSelector;
- - (id)performSelector:(SEL)aSelector withObject:(id)object;
- - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
Person *p = [Person new];
SEL s1 = @selector(objectFun);
[p performSelector:s1];
SEL s2 = @selector(objectFun:);
[p performSelector:s2 withObject:@"XX"];
SEL s3 = @selector(objectFun:value2:);
[p performSelector:s3 withObject:@"XX" withObject:@"XXX"];
SEL s4 = @selector(classFun);
[Person performSelector:s4];
SEL s5 = @selector(classFun:);
[Person performSelector:s5 withObject:@"XX"];
SEL s6 = @selector(classFun:value2:);
[Person performSelector:s6 withObject:@"XX" withObject:@"XXX"];
OC方法查找顺序
-
1.给实例对象发送消息的过程(调用对象方法)
- 根据对象的isA指针去该对象的类方法中查找,如果找到了就执行
- 如果没有找到,就去该类的父类类对象中查找
- 如果没有找到就一直往上找,直到跟类(NSObject)
- 如果都没有找到就报错
-
2.给类对象发送消息(调用类方法)
- 根据类对象的isA指针去元对象中查找,如果找到了就执行
- 如果没有找到就去父元对象中查找
- 如果如果没有找到就一直往上查找,直到根类(NSOject)
- 如果都没有找到就报错
一个控件看不见有哪些可能?
- 宽度或者高度其实为0
- 位置不对(比如是个负数或者超大的数,已经超出屏幕)
- hidden == YES
- alpha <= 0.01
- 没有设置背景色、没有设置内容
- 可能是文字颜色和背景色一样
懒加载
概念
:也称为是延迟加载,即用到时再去加载,而且也只加载一次(占用内存小)。所有的懒加载,其实采用的就是get方法,先判断是否已经创建,如果没有再去实例化。
目的
:
- 避免创建出多余的对象,重复创建会消耗性能
- 判断是否为空才去创建,否则直接用已有对象
好处
:
- 不必将创建对象的代码全部放viewDidLoad方法中,使代码的可读性强
- 每个控件的get方法中分别负责各自的实例化处理,代码彼此之间的独立性强,松耦合。