重新回过头看这些基础知识,对许多知识点都有新的认识,拥有坚实的基础才能更快的成长。
OC程序的源文件的后缀名是.m m代表message表示消息机制。main 仍然是OC程序的入口和出口,main函数有一个int类型的返回值,代表程序的结束状态。
#import
预处理指令,是#inlcude
指令的增强版,作用是将文件的内容在预编译的时候拷贝到写指令的地方。 #import
做了优化,同一个文件无论#import多少次,都只会包含一次。
简要原理:#import指令在包含文件的时候,底层会先判断这个文件是否被包含,如果包含过就会略过。
因此#import
#include
主要区别在于使用#include
需要处理重复引用,而#import
能防止同一个文件被多次包含,不需要处理重复引用。
面向对象后期维护和修改十分方便。当我们遇到一个需求的时候,不要亲自去实现。
类是模板,类的对象是根据这个模板创建出来的,类模板中有什么,对象中就有什么,绝不可能多,也绝不可能少。
设计类的三要素
在程序运行期间,当某个类第一次被访问到的时候,会将这个类存储到内存中的代码段区域,这个过程叫做类加载。
只有类在第一次被访问的时候,才会做类加载,并且一旦类被加载到代码段以后,直到程序结束的时候才会被释放。
例:Person *p1 = [Person new];
指针名->属性名
根据指针,找到指针指向的对象,在找到对象中的属性来访问。
3). 如何调用方法。[指针名 方法名];
先根据指针名找到对象,对象发现要调用方法,在根据对象的isa指针找到类。然后调用类里的方法。
4). 为什么不把方法存储到对象之中。
因为每一个对象的方法的代码实现都是一模一样的,没有必要为每一个对象都保存一个方法,这样的话就太浪费空间了,既然都一样,那么就只保存一份在代码段中。
5). 对象属性是有默认值的。NULL 可以作为指针变量的值,如果一个指针变量的值是NULL值代表这个指针不指向内存中的任何一块空间,其实等价于0。NULL其实是一个宏,就是0。
nil 只能作为指针变量的值,代表这个指针变量不指向内存中任何空间。nil其实也等价于0,也是一个宏,就是0。
所以NULL和nil其实是一样的,虽然使用NULL的地方可以使用nil,但是不建议随意使用。C指针用NULL OC的类指针用nil。
Person *p1 = nil;
表示p1指针不指向任何对象。
如果一个指针的值为nil代表这个指针不指向任何对象,此时如果通过p1指针去访问p1指针指向的对象的属性,运行就会报错。如果通过p1指针去调用对象的方法,运行不会报错,但是方法不会执行。
同类型的指针变量之间是可以相互赋值的。p1,p2指向同一个对象,无论谁修改对象的属性都会修改。因为他们指向同一块内存空间。
对象可以作为方法的参数也可以作为方法的返回值。 类的本质是我们自定义的一种数据类型,并且对象在内存中的大小是由我们自己决定的,数据类型是在内存中开辟空间的一个模板
当对象作为方法的参数传递的时候,是地址传递。所以,在方法内部通过形参去修改形参指向的对象的时候,会影响实参变量指向的对象的值。对象作为方法的返回值,返回的是对象的地址
属性的本质是变量,在创建对象的时候,对象当中的属性是按照类模板中的规定逐个创建出来的。类模板中属性是什么类型,那么对象中的属性就是什么类型。 如果对象的属性是另外一个类的对象,这个属性仅仅是一个指针变量而已,并没有对象产生。这个时候还要为这个属性赋值一个对象的地址,才可以正常使用。 类模板中属性是什么类型,对象当中的属性就是什么类型。
类方法的调用不依赖对象,如果要调用类方法不需要去创建对象。而是直接使用类名就可以调用类方法。
类方法和对象方法的调用过程
类方法节约空间且效率高,因为调用类方法不需要创建对象。但是在类方法中不能直接访问属性。因为属性只有在对象创建的时候才会创建在对象之中,而类方法在执行的时候有可能还没有类对象,所以不能访问属性。但是我们可以在类方法中创建类对象。
同理,在类方法中也不能通过self直接调用当前类的其他的对象方法,因为对象方法只能通过对象来调用,在对象方法中可以直接调用类方法。
因此如果方法不需要直接访问属性,也不需要直接调用其他的对象方法,那么我们就可以直接将这个方法定义为类方法。
如果不是所有的子类都拥有的方法,那么这个方法就不应该定义在父类之中,因为一旦定义在父类之中,那么所有的子类都拥有该方法
NSObject类是所有类的父类,NSObject类中包含了创建对象的方法,所以我们自己创建的类必须直接或者间接的继承自NSObject。 NSObject中有一个isa指针的属性,所以每一个子类对象中都有一个叫做isa的指针。
用来修饰属性,可以限定对象的属性在那一段范围之中访问。
@private : 私有,被其修饰的属性只能在本类的内部访问。 @protected: 受保护的 被其修饰的属性只能在本类以及本类的子类中访问。只能在本类和子类的方法实现中访问。 @package: 被其修饰的属性,可以在当前框架中访问。 @public: 公共的,被其修饰的属性可以在任意地方访问。
如果不为属性指定访问修饰符 默认:protected 子类仍然可以继承父类的私有属性。就算父类的属性是private,只不过在子类当中无法直接访问从父类继承过来的私有属性,可以通过set get方法来访问。
访问修饰符的作用域 从写访问修饰符的地方开始往下,直到遇到另外一个访问修饰符的或者结束大括弧为止,中间的所有的属性都应用这个访问修饰符。
使用建议 @public 无论什么情况下都不要使用,属性不要直接暴漏给外界。 @private 如果属性只想在本类中使用,不想再子类中使用。 @protected 如果你希望属性只在本类和本类的子类中使用。
访问修饰符只能用来修饰属性,不能用来修饰方法。
子类可以替换父类的位置,并且程序的功能不受影响。 即一个父类指针指向一个子类对象,可正常调用子类的方法和属性。
LSP 里氏替换原则: 一个指针中不仅可以存储本类对象的地址,还可以存储子类对象的地址 如果一个指针的类型是NSObject类型的,那么这个指针中可以存储任意的OC对象的地址。 如果一个数组的元素的类型是一个OC指针类型的,那么这个数组中不仅可以存储本类对象还可以存储子类对象。 如果一个数组元素是NSObject指针类型,那就意味着任意类型的对象都可以存在数组中。 如果一个方法的参数是一个对象,我们可以传本类对象也可以传子类对象 当一个父类指针指向1个子类对象的时候,通过父类指针就只能去调用子类对象中的父类成员。
当子类拥有父类的行为,但是子类的行为与父类不同。这个时候就可以通过重写父类的方法来实现。
当一个父类指针指向一个子类对象的时候,通过这个父类指针调用的方法,如果子类对象中重写了这个方法,调用的就是子类重写的方法。
指的是同一个行为,对于不同的事物具有完全不同的表现形式。同一个行为具备多种形态。子类重写父类的方法就是多态。
description方法是定义在NSObject之中的。我们通过重写description方法来修改NSLog的输出形式。NSLog的底层就是description方法。
相同点: 都可以将多个数据封装为一个数据
不同点:
如果表示的实体没有行为,只有属性。 那么如果属性比较少,只有几个,那么这个时候就定义为结构体,分配在栈,提高效率。如果属性比较多,不要定义成结构体,因为这样结构体变量会在栈中占据比较大的空间,导致访问效率降低。
类是以Class对象存储在代码段中的
内存中的五大区域 栈 存储局部变量 堆 允许程序员自己申请的空间,需要程序员自己控制 BSS段 存储没有初始化的全局变量和静态变量 数据段 用来存储已经初始化的全局变量,静态变量还有常量 代码段 用来存储程序的代码。
类加载:当类第一次被访问的时候,这个类就会被加载到代码段存储起来。
Class c1 = [Person class];
c1对象就是Person类,c1完全等价于Person,可以使用类对象来调用类的类方法。
注意:声明Class指针的时候,不需要加*
因为在typedef的时候已经加了*
了SEL其实是一个类,要在内存中申请空间存储数据,SEL对象是用来存储一个方法的。 类是以Class对象的形式存储在代码段之中。那么如何将方法存储在类对象之中?
*
所以我们声明SEL指针的时候不需要加*
。[p1 sayHi];
1). 先拿到存储sayHi方法的SEL对象,也就是拿到存储sayHi方法的SEL数据,SEL消息。
2). 将SEL消息发送给p1对象。
3). p1对象接收到SEL消息以后,就知道要调用方法
4). 根据对象的isa指针找到存储类的类对象。
5). 找到这个类对象以后,在这个类对象中去搜寻是否有和传入的SEL数据相匹配的方法。如果有就执行,如果没有再找父类,直到NSObject。OC最重要的1个机制:消息机制,调用方法的本质其实就是为对象发送SEL消息,[p1 sayHi];
表示为p1对象发送1条sayHi消息。
- (id)performSelector:(SEL)aSelector;
手动发送消息
Person *p1 = [Person new];
SEL s1 = @selector(sayHi);
[p1 performSelector:s1]; 与 [p1 sayHi]效果是完全一样的.// 带一个参数和两个参数的方法
// 如果有多个参数可以把参数封装在一个对象里面,传递对象
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
使用点语法访问对象的属性。
语法:对象名.去掉下划线的属性名
p.name = @“jack”;
这个时候就会将@“jack”赋值给p对象的_name属性。
原理: 本质并不是把@"jack"直接赋值给p对象的_name属性,实质上是调用set方法。
当使用点语法赋值的时候,编译器会将点语法转换为调用setter方法的代码。 当使用点语法取值的时候,编译器会将点语法转换为调用getter方法的代码。
在getter setter方法中慎用点语法,可能会造成无限递归而程序崩溃。 主要是看情况,get方法中如果点语法是调用set方法是可以使用的。 例如懒加载中我们是可以使用点语法为赋值的,因为懒加载是get方法,而赋值是调用set方法,所部不会递归调用。 如果属性没有封装setter getter 是无法使用点语法的。
@property 自动生成getter setter 方法的声明。 原理:由编译器在编译的时候自动生成。
@synthesize
自动生成getter setter 方法的实现。
@synthesize age // age一定要是前面@property声明过的。
希望@synthesize 不要生成私有属性,setter getter 的实现中操作我们已经写好的属性就可以了。
@synthesize @property名称 = 已经存在的属性名;
@synthesize age = _age;
Xcode 4.4之后,只要写一个@property 编译器会自动生成私有属性,并且自动生成getter setter 声明和实现。
@property NSString *name;
@property生成set get方法和成员属性,但是如果同时重写了setter getter方法,那么就不会自动生成私有属性了。需要自己写。 父类的property一样可以被子类继承,但是生成的属性是私有的,可以通过setter getter方法来访问
OC是一门弱语言,编译器在编译的时候,语法检查的时候没有那么严格。 强类型的语言:编译器在编译的时候,做语法检查的时候,就非常严格,行就是行,不行就是不行。
静态类型 指的是一个指针指向的对象是一个本类对象。 动态类型 一个指针指向的对象不是本类对象。
编译器在编译的时候,检查能否通过一个指针去调用指针指向的对象的方法。
判断原则:看指针所属的类型之中有没有这个方法,如果有就认为可以调用,编译通过,如果这个类中没有,那么编译报错。这就叫做编译检查,在编译的时候,能不能调用对象的方法主要是看指针的类型,我们可以将指针的类型做转换,来达到骗过编译器的目的。
编译检查只是骗过了编译器,但是这个方法究竟能不能执行,所以在运行的时候运行检查会去检查对象中是否真的有这个方法,如果有就执行,如果没有就报错
编译器,用编译器将C代码OC代码转化成二进制,编译器是个软件,苹果自己写的叫做LLVM,可以编译C OC Swift语言。
我们可以通过以下方法先来先判断以下对象中是否有这个方法,如果有再去执行,如果没有就别去执行,避免程序在没有方法的时候报错。
1). 判断对象中是否有这个方法可以执行.
- (BOOL)respondsToSelector:(SEL)aSelector; (最常用)
2). 判断类中是否有指定的类方法.
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
3). 判断指定的对象是否为 指定类的对象或者子类对象.
- (BOOL)isKindOfClass:(Class)aClass;
4). 判断对象是否为指定类的对象 不包括子类.
- (BOOL)isMemberOfClass:(Class)aClass;
5). 判断类是否为另外1个类的子类.
+ (BOOL)isSubclassOfClass:(Class)aClass;
OC中所有类的基类,根据LSP NSObject指针就可以指向任意的OC对象,所有NSObject指针是一个万能指针,可以指向任意的OC对象 缺点:如果要调用指向的子类对象的独有的方法,就必须要做类型转换。
是一个万能指针,可以指向任意的OC对象
*
所以,声明id指针的时候不需要再加*
了。
2). id指针是1个万能指针,任意的OC对象都可以指.id和instancetype的区别.
创建对象,我们之前通过new方法
类名 *指针名 = [类名 new];
new实际上是1个类方法,其作用为:
new方法的内部,其实是先调用的alloc方法,再调用的init方法。 alloc方法是1个类方法。作用: 那1个类调用这个方法就创建那个类的对象,并把对象返回,分配内存空间。 init方法是1个对象方法。作用: 初始化对象。
init方法 作用: 初始化对象,为对象的属性赋初始值,这个init方法我们叫做构造方法。 init方法做的事情:初始化对象,并为对象的属性赋默认值。 如果属性的类型是基本数据类型就赋值为0,C指针赋值NULL,OC指针赋值nil。 所以,我们创建1个对象如果没有为这个对象的属性赋值这个对象的属性是有默认值的。
重写init方法的规范: 1). 必须要先调用父类的init方法,然后将方法的返回值赋值给self。 2). 调用init方法初始化对象有可能会失败,如果初始化失败,返回的就是nil。 3). 判断父类是否初始化成功,判断self的值是否为nil,如果不为nil说明初始化成功。 4). 如果初始化成功就初始化当前对象的属性。 5). 最后 返回self的值。
自定义构造方法: 1). 自定义构造方法的返回值必须是instancetype。 2). 自定义构造方法的名称必须以initWith开头。 3). 方法的实现和init的要求一样。
文中如果有不对的地方欢迎指出。我是xx_cc,一只长大很久但还没有二够的家伙。