---恢复内容开始---
OC 也好,UI 也罢,甚至于swift,几乎都会涉及self,super用法,并且通常来说不太好理解,总会迷迷糊糊的用,所以必须要搞清楚
补充知识:OC中全局变量和局部变量
变量 | 显示 | 存储空间 | 生命周期 | 特点 |
---|---|---|---|---|
成员变量(实例变量、属性) | 在类的声明中定义,在@interface CLASS ( ) 和@end 中间 | 堆 | 创建对象时候有效,对象销毁(释放)结束 | 1.定义的时候不能初始化2.只能通过对象访问,不能离开类单独存在 |
局部变量 | 函数体内或代码块中 | 栈 | 函数体内 | 作用域: 从定义的那一行开始,直到大括号结束或者遇到break return为止 |
全局变量 | 函数外部 | 静态区 | 整个文件(从定义变量的那一行直到文件结束) | 程序一启动就会自动分配内存空间,直到程序释放才结束 |
简言之:全局变量是放在函数体外(或者说方法外),局部变量是放在函数体内(方法内),全局变量作用域是整个文件,(这个整个文件的含义是当我写在.h文件中,在.m文件也是可以访问的,.h和.m文件是一体的,创建的时候就同时会生成)
突然脑子死机冒出了一个问题:引入头文件的目的?还有和继承的区别?
首先:引入头文件,一般是只引入.h文件就可以,引入头文件,引入了这个文件的类,类型,方法的声明以及实现(相当于引入了这个类,但又不是引入这个类),为什么这么说,
加入A文件引入了B文件的头文件,不是说A文件就可以访问B文件的属性和方法了,我们知道,属性和方法只能由A文件中的类或者类对象去掉用,所以我们引入了一个文件
要想在B 文件访问A文件属性和调用方法,只能在B文件实例化A对象,用A对象去访问和调用.
这里又可以延伸一个东西:就是我们狭隘的理解全局变量(也说全局属性)作用域为只适用于当前.h.m文件,那么引入头文件后,是不是同样有作用,这个问题其实很简单,
引入头文件,就是当前文件多了个文件,那么显然,多出的文件在当前文件中同样有作用,也就是说,属性和方法写到哪无所谓,只要是该文件中的类的对象,就可以访问属性,
调用方法.
-----------进入正题:
1.self用法:
1)
谁调用了当前方法,self就指的是谁,
self出现在对象方法,self就代表对象,self出现在类方法中,self就代表类,
这么一说很笼统:理解后也就是:对象方法是由对象去掉用,类方法是由类去掉用,而self是出现在方法内部的,谁调用谁就替代self(也就是说self可以是类也可以是对象)
2)
self修饰变量,表示访问的是一个全局的实例变量(只是这么一说,肯定被读者忽略,那么为啥么要强调这一句话?)
举例如下:
>> 1. 声明的时候age没有下划线,导致在set方法中会出现问题
>> 2. 在Set方法中实现的时候,因为形参跟成员变量同名,形参属于局部变量,所以导致一个结果。局部变量跟全局变量同名的时候,会暂时屏蔽全局变量的作用域。解决这个问题就引入self
>> 3. 所以在main函数中调用时,结果是0.
>> 4. 在set方法中加入self ->age = age, self在对象方法中就代表当前的对象,self –>age 访问的是全局的实例变量。所以问题就解决了。
------------------------------第一步类的声明--------------------------
#import <Foundation/Foundation.h> @interface Person : NSObject { int age; //实例变量没有下划线时 } //封装对实例变量的读写的方法 -(void)setAge:(int)age; -(int)age; @end
------------------------------第二步类的实现--------------------------
age = age
局部变量 和 全局变量同名会屏蔽全局变量的作用域:
所以等号左边的age就是指-(void)setAge:(int)age中的age,注意看:我为什么说这个age是局部变量?冒号右边有个(int)age其实也就是在方法内部定义了一个
局部变量age,所以在方法内部age = age都是指(int)age中的age,此时,才到了self大显神通的时候了,正因为self在修饰变量的时候是访问的全局的实例变量,所以体现了
self的价值和用处.
#import "Person.h" @implementation Person -(void)setAge:(int)age{ //age = 88 //当局部变量和全局变量同名的时候,局部变量会暂时屏蔽全局变量的作用域 age = age; } //局部变量age的生命周期是到此处结束 -(int)age{ return age; } @end
------------------------------第三步main函数中--------------------------
#import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { //创建对象 Person *p = [Person new]; //设置年龄 [p setAge:88]; //打印输出年龄 NSLog(@"_age = %d",[p age]); } return 0; }
--------------------------第四步修改的set方法实现-----------------------
#import "Person.h" @implementation Person -(void)setAge:(int)age{ // 此时就可以赋值了 self == p self->age = age; // self ->age 访问的是全局的实例变量} -(int)age{ return age; } @end
2.类的继承与派生:
子类继承父类便拥有父类所有的属性和方法(注意是拥有不是简单的引入(引入头文件),引入的还是别人的,拥有的才是自己的)
注意:父类的私有属性是不能被子类继承的(私有属性就是写在.m文件中的属性,要和实例变量修饰符private修饰的变量区分开)
提一下:private修饰的实例变量不是完全私有的,是可以被子类继承的,但不能被子类所使用(就像父亲给了儿子一个加了锁的宝箱,儿子虽然拥有但是打不开的)
举个例子:就像抄别人的东西始终是别人的,只有自己想出来的,自己总结出来的才是自己的,就像我的博客是劳资一个字一个字打的,
那就是我的.(怎么有种亲儿子和干儿子的区别)
继承:是单继承,什么意思?就是子类只能继承一个父类,父类可以拥有多个子类(你只能有一个亲爹,但你亲爹可以生好几个儿子)
OC 没有多继承但可以多层继承(就是子类可以继承父类,父类可以继承父父类,但是子类不能继承父父类,不要问我为什么,你爷爷能生出你么)
派生类(其实也就是子类)的概念其实就是说:子类继承了父类,仍然可以添加自己的属性和方法,(儿子继承老子的财产,当然可以自己去创造财富)
2.1继承的注意事项:
只有一条:子类是不能定义和父类相同的实例变量的,例如父类声明 了一个int _age,子类就不能重复声明了(这句话的前提肯定是子类已经继承父类了)
也就是说,属性没有重写这一说(子类继承父类,已经拥有了父类的属性,那么子类如果再定义一个相同名称的属性,就会造成变量的重复定义,会出错),
而方法是可以重写的
3.方法的重写:(还有后面会提到的重写构造方法)
定义:子类重写父类的方法就是方法的重写.
子类重写父类的方法,假如父类Dog有一个方法是 – (void)eat; 子类重写了这个方法,子类对象调用的时候会先调用子类的这个eat方法,
子类有这个方法,就不再调用父类,但是如果想掉就在eat方法的实现中加入[super eat];即可
衍生出方法的调用顺序:当子类对象去掉用一个方法的时候,会先从子类中找是否有这个方法,如果没有就去父类中找,依次往上,
直到找到最顶层的NSObject为止,如果最顶层也没有就会报错.
------(走了会神儿...写到这里突然想到工作中的事儿,工作中做东西目的是实现功能和高效,因为老板要的是结果,所以,有些新人出去总想着
亲力亲为,其实是过于偏执,你会写这很好,但作用不大,提升也不大,你用别人的在别人的基础上优化,既可以学习也可以提高效率,还能提升自己优化代码的能力)
4.实例变量修饰符
@public(公开的)在当前类和任何别的类中,都能被实例变量所属的对象访问,或者使用,(这里所指的任何别的类,是泛指通过头文件的引入,也可以用extern修饰)
@protected(受保护的)只能在本类和子类(OC中实例变量默认是被他修饰的)
@private(私有的)只能在本类,相对私有,能被子类继承,但不能被子类访问
@package (包)用的很少,可以在本类和其他类中使用,但和public是有区别的,package是指当前类中,就是用package修饰的,子类继承后不能访问
5.OC中私有属性和私有方法
1)
私有属性:上文已经多次提到,就是在.m文件中声明一个属性,不能被继承也不能被子类访问
相对私有属性:是指被实例变量修饰符@private修饰的属性,可以被子类继承,但不能被子类访问
2)
私有方法:
什么是私有方法?私有方法是干吗用的?我为啥呢么要用私有方法?
私有方法就是在.m文件中声明的一个方法,在.h文件中没有提供该方法的接口.
没有接口,我在main函数(或者别的文件)中肯定不能通过对象去调用,这就隐藏了这个方法,
如下所示
work方法是不能在main函数中访问的
---------------------------第一步在.h里面声明一个方法------------------------
#import <Foundation/Foundation.h> @interface Person : NSObject //声明一个对象方法 -(void)run; //一个接口 @end
---------------------------第二步在.m里面实现方法并添加一个不在.h文件中声明的方法------------------------
#import "Person.h" @implementation Person -(void)run{ NSLog(@"班长在跑"); } //私有方法(.m中定义的,.h中并没有声明) -(void)work{ NSLog(@"班长工作,月薪28k"); } @end
那么重点来了,有吊用?
举个例子:同样我写一个run方法,但是我的run方法写的很高级,功能很多,他一边跑还一边统计时速,油量,车胎热度等等运行中的指数,如果我都把这些指数提供接口
,那么代码就不好维护,可读性也不高,而且这些参数我只是希望客户能看到就可以,那么我把这些参数方法的实现写到.m文件中的run方法中,我只需要在main函数中,用
对象去调用run方法就会得到了这些参数,这就是私有方法的运用
6.description方法的介绍和重写:
什么是description方法?
description方法其实就是当我以%@方式打印对象时系统会自动调用这个方法(打印类对象调用类方法,打印对象调用对象方法)
打印结果如下图所示:打印类结果是类名,打印对象默认会返回<类名:内存地址>(示例是重写了该方法)
虽然字符串也是对象,但字符串在使用@%打印时情况特殊
注意:如果在-description方法中使用NSLog打印self会造成死循环
- (NSString *)description {
// 下面代码会引发死循环
// NSLog(@"%@",self);
//return @"abc";
}
延伸:
字符串对象的length方法:计算的是字符串的字数,而不是像strlen函数那样,计算的是字符数。如“哈ha123”length得出的结果是6,返回unsigned long类型,而strlen函数得出的结果是8,因为一个汉字占3个字节。
提示:字数也包括空格。
7.多态:
多态:父类指针指向子类(肯定要有继承)
举例:Animal 和 Dog 都有eat的方法
Animal *anl = [Dog new];
[anl eat];
调用的是子类Dog的eat方法
为什么呢?
对象调用方法,实际上是通过isa指针找到代码区的eat方法,而多态就像是把儿子的名字改成了他爹的名字,但方法还是儿子的方法
8.SEL:全称Selector 表示方法的存储位置。
Person *p=[[Person alloc] init];
[p test];
寻找方法的过程:
(1)首先把test这个方法名包装成sel类型的数据;
(2)根据SEL数据找到对应的方法地址;
(3)根据方法地址调用相应的方法。
(4)注意:在这个操作过程中有缓存,第一次找的时候是一个一个的找,非常耗性能,之后再用到的时候就直接使用。
关于_cmd:每个方法的内部都有一个-cmd,代表着当前方法。
注意:SEL其实是对方法的一种包装,将方法包装成一个SEL类型的数据,去寻找对应的方法地址,找到方法地址后就可以调用方法。这些都是运行时特性,发消息就是发送SEL,然后根据SEL找到地址,调用方法.