iOS开发--OC基础

对象方法的声明和实现

1.对象方法声明

iOS开发--OC基础_第1张图片
xxffsm.png

2.对象方法实现

  • 必须写在以@implementation开头,@end之间
  • 在声明的后面加上{}即表示实现
  • 将需要实现的代码写在{}中

类方法的声明和实现

类方法声明

  • 格式
    • 将对象方法-号变为+号
  • 特征
    • 类方法以+开头 如+(void)put;
    • 类方法只能由类来调用
    • 类方法中不能访问实例(成员)变量,因为类方法由类来调用,并没有创建存储空间来存储类中的成员变量。
  • 类方法的好处:
    • 节省内存空间
    • 不依赖于对象,执行效率更高;
    • 能用类方法解决的问题,尽量使用类方法;
  • 类方法的场合:
    • 当方法内部不需要使用到成员变量时,可以改为类方法
    • 类方法一般用于编写工具方法

3.对象方法和类方法区别

  • 对象方法
    • 对象方法是属于对象的
    • 以减号-开头
    • 只能让对象调用,没有对象,这个方法根本不可能被执行
    • 对象方法能直接访问实例变量(成员变量)
    • 对象方法中可以调用当前对象的对象方法(self关键字)
    • 对象方法中可以调用其他对象的对象方法
    • 对象方法中不可以调用类方法
  • 类方法
    • 类方法是属于类的
    • 以加号+开头
    • 只能用类名调用,对象不能调用
    • 类方法中不能直接访问实例变量(成员变量)
    • 类方法中不能直接调用对象方法,要想调用对象方法,必须创建或传入对象。
  • 使用场合:
    • 当不需要访问成员变量的时候,尽量用类方法
  • 类方法和对象方法可以同名

new关键字

  • 1.在堆中分配新对象的存储空间
  • 2.初始化对象属性
  • 3.返回对象的指针

堆和栈的区别:

  • 堆:内存需要手动释放
  • 栈:内存系统自动释放

对象的存储细节

  • 类创建对象,每个对象在内存中都占据一定的存储空间,每个对象都有一份属于自己的单独的成员变量,所有的对象公用类的成员方法,方法在整个内存中只有一份,类本身在内存中占据一份存储空间,类的方法存储于此。
iOS开发--OC基础_第2张图片
memory.png

isa指针

  • 每一个对象都包含一个isa指针.这个指针指向当前对象所属的类。
  • [p eat];表示给p所指向的对象发送一条eat消息,调用对象的eat方法,此时对象会顺着内部的isa指针找到存 储于类中的方法,执行。
  • isa是对象中的隐藏指针,指向创建这个对象的类。
  • 通过isa指针我们可以在运行的时候知道当前对象是属于那个Class(类)的


    iOS开发--OC基础_第3张图片
    isazz.png

局部变量和全局变量以及成员变量的区别

@interface Person : NSObject{
    // 写在类声明的大括号中的变量, 我们称之为 成员变量(属性, 实例变量)
    // 成员变量只能通过对象来访问
    // 注意: 成员变量不能离开类, 离开类之后就不是成员变量 \
            成员变量不能在定义的同时进行初始化
    // 存储: 堆(当前对象对应的堆的存储空间中)
    // 存储在堆中的数据, 不会被自动释放, 只能程序员手动释放
    int age;
}
@end

@implementation Person{
}
@end
int a;
int b = 10;
// 写在函数和大括号外部的变量, 我们称之为全局变量
// 作用域: 从定义的那一行开始, 一直到文件末尾
// 全局变量可以先定义在初始化, 也可以定义的同时初始化
// 存储: 静态区
// 程序一启动就会分配存储空间, 直到程序结束才会释放

int main(int argc, const char * argv[]) {
    // 写在函数或者代码块中的变量, 我们称之为局部变量
    // 作用域: 从定义的那一行开始, 一直到遇到大括号或者return
    // 局部变量可以先定义再初始化, 也可以定义的同时初始化
    // 存储 : 栈
    // 存储在栈中的数据有一个特点, 系统会自动给我们释放
    int num = 10;
    {
        int value;
    }
    return 0;
}

函数与方法对比

  • 对象方法:

    • 对象方法的实现只能写在@implementation...@end中,

      对象方法的声明只能写在 @interface...@end中.或者不写方法的声明

    • (2)对象方法都以 - 号开头,类方法都以 + 号开头

    • (3)对象方法只能由对象来调用,类方法只能由类来调用,不能当做函数一样调用

    • (4)函数属于整个文件,可以写在文件中的任何位置,包括@implementation...@end中,但写在 @interface...@end会无法识别 (写在类声明中的函数实现会被丢弃),函数的声明可以在main函数内部也可以在main函数外部。

    • (5)对象方法归类\对象所有

  • 函数:

    • (1)所有的函数都是平行的
    • (2)函数不存在隶属关系
    • (3)使用的时候可以直接调用
    • (4)不可以访问对象中的成员变量
     方法的注意点:
     >方法可以没有声明只有实现
     >方法可以只有声明没有实现, 编译不会报错, 但是运行会报错
     如果方法只有声明没有实现, 那么运行时会报: 
     reason: '+[Person demo]: unrecognized selector sent to class 0x100001140'
     发送了一个不能识别的消息, 在Person类中没有+开头的demo方法
     reason: '-[Person test]: unrecognized selector sent to instance 0x100400000'
    

常见错误

  • 1)@interface @end和@implementation @end不能嵌套包含
    1. OC是弱语法,可以只有@implementation,但实际开发中千万不要这样。
  • 4)漏写@end
  • 5)两个类的对象声明顺序(可以把顺序打乱)
  • 6)成员变量没有写在{}里
  • 7)方法的声明写在了{}里面
  • 8)在声明时不能对类的成员变量进行初始化,请注意成员变量不能脱离对象而独立存在
  • 9)方法无法像函数那样的调用
  • 10)成员变量和方法不能用static等关键字修饰,不要和c语言混淆
  • 11)类的实现可以写在mian函数后面,在使用之前只要有声明就可以

NSString 类介绍及用法

1.NSString常见方法

  • NSString是 Objective-C 中核心处理字符串的类之一
  • 创建常量字符串,注意使用“@“符号。
NSString *astring = @"This is a String!";
  • 创建空字符串,给予赋值。
NSString *string = [NSString new];
string = @"GX";
  • 创建格式化字符串:占位符(由一个%加一个字符组成)
[NSString stringWithFormat:@"哥%i岁了", 30];

2.NSString字符串长度计算

  • 通过调用NSString类的对象方法 length 可以获得字符串的长度
  • 字符串长度是指该字符串中一共有多个字符(无论是中文还是英文)
  • 纯英文字符
    NSString *str = @"gx";
    NSLog(@"length = %i", [str length]);
    输出结果:3
  • 中英文混合
    NSString *str = @"高Caesar";
    NSLog(@"length = %i", [str length]);
    输出结果:4
  • 纯中文
    NSString *str = @"高雄";
    NSLog(@"length = %i", [str length]);
    输出结果:3
//NSUInteger 就是 unsigned long

        源码:
        typedef unsigned long NSUInteger;

结构体成员变量

typedef struct{
    int year;
    int month;
    int day;
} Sdate;


@interface GXStudent : NSObject
{
@public
    NSString *_name;
    Sdate _birthday;
}
- (void) sayMyNameWithBirthday;
@end
@implementation GXStudent
- (void) sayMyNameWithBirthday
{
    NSLog(@"My name is %@ , my birthday is %i.%i.%i", _name, _birthday.year, _birthday.month, _birthday.day );
}
@end

#import 

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        GXStudent *gx = [GXStudent new];
        //结构体只能在定义的时候初始化\
        系统并不清楚它是数组还是结构体
        gx->_name = @"Xiong";
//        1.先赋值给一个临时变量
//        Sdate gxdate = { 1991, 8, 11};
//        gx->_birthday = gxdate;
//        2.逐个赋值
//        gx->_birthday.year = 2014;
//        gx->_birthday.month = 05;
//        gx->_birthday.day = 12;
//        3.强制类型转换 == 方法一
        gx->_birthday = (Sdate){ 1991, 02, 11};
        [gx sayMyNameWithBirthday];
    }
    return 0;
}

修改模版

如何修改项目模板

  • 1.应用程序中,找到Xcode, 右键"显示包内容"
  • 打开"/Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/Project Templates/Mac/Application" 文件夹
    • 在/Application文件夹中能够找到所有和OS X Application界面对应的文件夹
  • 注意:
    • 修改最好重启Xcode
    • 如果发现不能修改, 可以将文件拖到桌面后再修改, 或者修改文件的权限后再修改

修改类的头部信息

  • 找到对应类对应的类文件模板. (因为类是创建项目之后手动创建的, 而不是随着项目的创建自动创建的, 所以修改类文件模板和项目模板并不是修改同一个文件)
    • 打开"/Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates File Templates/Source/Cocoa Class.xctemplate"文件夹

自定义代码段

  • 快捷键 : Command + Shift + L

自定义 Snippets

iOS开发--OC基础_第4张图片
Snip20150529_27.png

导入Snippets

  • 将下载好的代码片段拷贝到:/Users/gaoxiong/Library/Developer/Xcode/UserData/CodeSnippets/
    iOS开发--OC基础_第5张图片
    Snip20150529_33.png

self关键字

self总结:

  • 谁调用self所在的方法,那么self就是谁
  • self在类方法中,就是这个类的类对象,全局只有一个,可通过self调用本类中的其他类方法,但是不能通过self来调用对象方法或访问成员变量
  • self在对象方法中,就是调用这个方法的那个对象, 可以通过self调用本类中其他的对象方法,访问成员变量,但不能通过self调用本类的类方法。
  • 通过self调用方法的格式:[self 方法名];
  • 通过self访问成员变量格式:self->成员变量名

self注意

  • 同时有对象方法和类方法存在的时候,self不会调错
  • self只能在方法中使用;不要使用self来调用函数,也不可以在函数内部使用self;
  • 使用self调用本方法,导致死循环调用。

继承

继承的概念

iOS开发--OC基础_第6张图片
jcj.png
  • 注意:

    • 基类的私有属性能被继承,不能在子类中访问。
    • OC中的继承是单继承:也就是说一个类只能一个父类,不能继承多个父类
    • 子类与父类的关系也称为isA(是一个)关系,我们说 子类isA父类,也就是子类是一个父类,比如狗类继承动物类,那么我们说狗isA动物,也就是狗是一个动物。在如汽车继承交通工具,那么们说汽车isA交工工具,也就是汽车是一个交通工具
    • 继承的合理性:引用《大话西游》里的一句话来描述继承的。“人是人他妈生的,妖是妖他妈生的!”
  • 格式:

@interface 子类名称 : 父类名称

@end

继承的相关特性

1.方法的重写

  • 在子类中实现与父类中同名的方法,称之为方法重写;
  • 重写以后当给子类发送这个消息的时候,执行的是在子类中重写的那个方法,而不是父类中的方法。
  • 如果想在子类中调用被子类重写的父类的方法,可以通过super关键字
  • 使用场景:当从父类继承的某个方法不适合子类,可以在子类中重写父类的这个方法。

2.继承中方法调用的顺序

  • 1、在自己类中找

  • 2、如果没有,去父类中找

  • 3、如果父类中没有,就去父类的父类中

  • 4、如果父类的父类也没有,就还往上找,直到找到基类(NSObject)

  • 5、如果NSObject都没有就报错了

    如果找到了就执行这个方法,就不再往后查找了

3.继承的注意事项

  • 子类不能定义和父类同名的成员变量,私有成员变量也不可以;因为子类继承父类,子类将会拥有父类的所有成员变量,若在子类中定义父类同名成员变量 属于重复定义
  • OC类支持单一继承,不支持多继承;也就是说一个类只能有一个直接父类
  • OC类支持多层继承 ,如下图所示
iOS开发--OC基础_第7张图片
djc.png

4.继承的使用场合

  • 优点:提高代码的复用性;可以让类与类之间产生关系,正是因为继承让类与类之间产生了关系才有了多态
  • 注意:不能为了提高代码的复用性就使用继承,只有满足 “XXXX is a XXXX” 我们才能使用继承

super"关键字"

1.super基本概念

  • super是个编译器的指令符号,只是告诉编译器在执行的时候,去调谁的方法.

    • self是一个隐私参数;
    self refers to the object receiving a message in objective-C programming.
    
    • super 并不是隐藏的参数,它只是一个“编译器指示符”,它和 self 指向的是相同的消息接收者
    super is a flag that tells the compiler to search for the method implementation in a very different place. It begins in the superclass of the class that defines the method where super appears.
    

2.super的作用

  • 1).直接调用父类中的某个方法

    • super在对象方法中,那么就会调用父类的对象方法
    • super在类方法中,那么就会调用父类的类方法
  • 2).使用场合:子类重写父类的方法时想保留父类的一些行为

多态

1.什么是多态?

  • 程序中的多态:父类指针指向子类对象

2.多态的条件

  • 有继承关系

  • 子类重写父类方法

  • 父类指针指向子类对象

    狗 *g = [狗 new];
    动物 *a = [狗 new];
    猫 *c = [猫 new];
    动物 *a = [猫 new];
    
  • 表现:当父类指针指向不同的对象的时候,通过父类指针调用被重写的方法的时候,会执行该指针所指向的那个对象的方法

3.多态的优点

  • 多态的主要好处就是简化了编程接口。它允许在类和类之间重用一些习惯性的命名,而不用为每一个新的方法命名一个新名字。这样,编程接口就是一些抽象的行为的集合,从而和实现接口的类的区分开来。
  • 多态也使得代码可以分散在不同的对象中而不用试图在一个方法中考虑到所有可能的对象。 这样使得您的代码扩展性和复用性更好一些。当一个新的情景出现时,您无须对现有的代码进行 改动,而只需要增加一个新的类和新的同名方法。

4.如何实现多态

  • Animal是父类,子类有Cat 和 Dog,子类分别重写了父类中的eat方法;实例化对象的时候可以用下面的方法:
Animal *animal = nil;

//实例化猫的对象
animal = [Cat new];
[animal eat];

//实例化狗的对象
animal = [Dog new];
[animal eat];

5.多态的原理

  • 动态绑定:
    • 动态类型能使程序直到执行时才确定对象的真实类型
    • 动态类型绑定能使程序直到执行时才确定要对那个对象调用的方法
  • OC不同于传统程序设计语言,它可以在运行时加入新的数据类型和新的程序模块:动态类型识别,动态绑定,动态加载
  • id类型:通用对象指针类型,弱类型,编译时不进行具体类型检查

6.多态的注意点

  • 1)如果存在多态,父类是可以访问子类特有的方法
假设 子类 Dog 有一个特有的方法bark
[dog bark];
Animal *an2 = [Dog new];
[(Dog*)an2 bark]; //把父类的指针,强制类型转换
  • 2)如果不存在多态,父类是不可以访问子类特有的方法的
Animal *an3 = [Animal new];
[(Dog*)an3 bark]; //错误的,不能强制转换

实例变量修饰符

1 .实例变量的作用域

iOS开发--OC基础_第8张图片
slblxsf.png
  • 1)@public (公开的)在有对象的前下,任何地方都可以直接访问
  • 2)@protected (受保护的)只能在当前类和子类的对象方法中访问
  • 3)@private (私有的)只能在当前类的对象方法中才能直接访问
  • 4)@package (框架级别的)作用域介于私有和公开之间,只要处于同一个框架中相当于@public,在框架外部相当于@private

2.变量修饰符在子类中的访问

  • 1)@private私有成员是能被继承,但不能被外部方法访问。
  • 2)@public 公有成员能被继承,也能被外部方法访问。
  • 3)@protected 保护成员能够被继承,不能够被外部方法访问。

3.实例变量作用域使用注意事项

  • (1)在@interface @end之间声明的成员变量如果不做特别的说明,那么其默认是protected 的
  • (2)一个类继承了另一个类,那么就拥有了父类的所有成员变量和方法,注意所有的成员变量它都拥有,只是有的它不能直接访问。例如@private的

description方法

1.description基本概念

  • NSLog(@"%@", objectA);这会自动调用objectA的description方法来输出ObjectA的描述信息.
  • description方法默认返回对象的描述信息(默认实现是返回类名和对象的内存地址)
  • description方法是基类NSObject 所带的方法,因为其默认实现是返回类名和对象的内存地址, 这样的话,使用NSLog输出OC对象,意义就不是很大,因为我们并不关心对象的内存地址,比较关心的是对象内部的一些成变量的值。因此,会经常重写description方法,覆盖description方法 的默认实现

2.description重写的方法

  • 对象方法
/**对象方法:当使用NSLog输出该类的实例对象的时候调用*/
-(NSString *) description
{
    return [NSString stringWithFormat:@"狗腿数:%d,狗眼数%d\n",_legNum,_eyeNum];
}
  • 类方法
/**类方法:当使用NSLog输出该类的类对象的时候调用*/
+(NSString *) description
{
    return @"+开头的description方法";
}

3.description陷阱

  • 千万不要在description方法中同时使用%@和self,下面的写法是错误的
- (NSString *)description {
    return [NSString stringWithFormat:@"%@", self];
}
  • 同时使用了%@和self,代表要调用self的description方法,因此最终会导致程序陷入死循环,循环调用description方法
  • [NSString stringWithFormat:@“%@”, self]; 使用它时,循环调用,导致系统会发生运行时错误
  • 当该方法使用NSLog(“%@”,self) 时候, 系统做了相关的优化,循坏调用3次后就会自动退出

OC中的私有方法

1.OC中的私有变量

  • 在类的实现即.m文件中也可以声明成员变量,但是因为在其他文件中通常都只是包含头文件而不会包含实现文件,所以在.m文件中声明的成员变量是@private的。在.m中定义的成员变量不能和它的头文件.h中的成员变量同名,在这期间使用@public等关键字也是徒劳的。
@implementation Dog
{
    @public
    int _age;
}
@end

补充:私有变量的访问及修改方式

  • kvc
  • runtime
#import 
#import "Person.h"
#import "Person+GX.h"
#import //runtime 头文件

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        
            //访问私有方法
        SEL sel = @selector(hello);
        [p performSelector:sel];
        
            //kvc访问私有变量
        NSLog(@"-------%@--------%@",[p valueForKey:@"name"] ,[p valueForKey:@"age"]);
            //kvc修改私有变量
        [p setValue:@"猪八戒" forKey:@"name"];
        [p setValue:@"8888" forKey:@"age"];
        NSLog(@"-------%@--------%@",[p valueForKey:@"name"] ,[p valueForKey:@"age"]);
        
            //runtime 修改私有变量
            //记录变量的数量
        unsigned int count = 0;
        
            //获取变量所有属性变量
        Ivar *members = class_copyIvarList([Person class], &count);
        for (int i = 0; i < count; i++) {
            Ivar ivar = members[I];
                //取得属性名字并专程字符串类型
            const char *memberName =ivar_getName(ivar);
            NSLog(@"%s",memberName);
            Ivar name = members[0];
                //修改属性值
            object_setIvar(p, name, @"齐天大圣");
        }
        NSLog(@"%@", [p valueForKey:@"name"]);
        
    }
    return 0;
}


2.OC中的私有方法

  • 私有方法:只有实现没有声明的方法

  • 原则上:私有方法只能在本类的方法中才能调用。

    注意: OC中没有真正的私有方法

@interface Dog : NSObject

@end
@implementation Dog
- (void)eat
{
    NSLog(@"啃骨头");
}
@end
int main(int argc, const char * argv[]) {
    Dog *d = [Dog new];
    SEL s1 = @selector(eat);
    [d performSelector:s1];
    return 0;
}

@property基本概念

@property基本使用

  • 在@inteface中,用来自动生成setter和getter的声明
用@property int age;就可以代替下面的两行
- (int)age;   // getter
- (void)setAge:(int)age;  // setter
  • @property编写步骤
1.在@inteface和@end之间写上@property
2.在@property后面写上需要生成getter/setter方法声明的属性名称, 注意因为getter/setter方法名称中得属性不需要_, 所以@property后的属性也不需要_.并且@property和属性名称之间要用空格隔开
3.在@property和属性名字之间告诉需要生成的属性的数据类型, 注意两边都需要加上空格隔开

@property增强注意点

  • 默认情况下,setter和getter方法中的实现,会去访问下划线 _ 开头的成员变量。
@interface Person : NSObject
{
    @public
    int _age;//可以省略
    int age;//可以省略
}
@property int age;

@end

int main(int argc, const char * argv[]) {

    Person *p = [Person new];
    [p setAge:30];
    NSLog(@"age = %i, _age = %i", p->age, p->_age);

    return 0;
}
  • 如果没有会自动生成一个_开头的成员变量,自动生成的成员变量是私有变量, 声明在.m中,在其它文件中无法查看,但当可以在本类中查看
  • @property只会生成最简单的getter/setter方法,而不会进行数据判断
Person *p = [Person new];
[p setAge:-10];
NSLog(@"age = %i", [p age]);
  • 如果需要对数据进行判断需要我们之间重写getter/setter方法
    • 若手动实现了setter方法,编译器就只会自动生成getter方法
    • 若手动实现了getter方法,编译器就只会自动生成setter方法
    • 若同时手动实现了setter和getter方法,编译器就不会自动生成不存在的成员变量

@property修饰符

@property修饰符

  • 修饰是否生成getter方法的
    • readonly 只生成setter方法,不生成getter方法
    • readwrite 既生成getter 又生成setter方法(默认)
@property (readonly) int age;
  • 指定所生成的方法的方法名称
    • getter=你定制的getter方法名称
    • setter=你定义的setter方法名称(注意setter方法必须要有 :)
@property (getter=isMarried)  BOOL  married;
说明,(程序员约定)通常BOOL类型的属性的getter方法要以is开头

id类型

1.静态类型和动态类型

  • 静态类型
    • 将一个指针变量定义为特定类的对象时,使用的是静态类型,在编译的时候就知道这个指针变量所属的类,这个变量总是存储特定类的对象。
Person *p = [Person new];
  • 动态类型
    • 这一特性是程序直到执行时才确定对象所属的类
id obj = [Person new];

2.为什么要有动态类型?

  • 我们知道NSObject是OC中的基类
  • 那么任何对象的NSObject类型的指针可以指向任意对象,都没有问题
  • 但是NSObject是静态类型,如果通过它直接调用NSObject上面不存在的方法,编译器会报错。
  • 你如果想通过NSObject的指针调用特定对象的方法,就必须把NSObject * 这种类型强转成特定类型。然后调用。如下
//定义NSObject * 类型
 NSObject* obj = [Cat new];
 Cat *c = (Cat*)obj;
 [c eat];
  • id 是一种通用的对象类型,它可以指向属于任何类的对象,也可以理解为万能指针 ,相当于C语言的 void *
  • 因为id是动态类型,所以可以通过id类型直接调用指向对象中的方法, 编译器不会报错
/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
 id obj = [C at new];
 [obj eat]; // 不用强制类型转换

 [obj test]; //可以调用私有方法
  • 注意:
    • 在id的定义中,已经包好了*号。id指针只能指向OC中的对象
    • 为了尽可能的减少编程中出错,Xcode做了一个检查,当使用id 类型的调用本项目中所有类中都没有的方法,编译器会报错
    • id类型不能使用.语法, 因为.语法是编译器特性, 而id是运行时特性

3.id数据类型与静态类型

  • 虽然说id数据类型可以存储任何类型的对象,但是不要养成滥用这种通用类型
    • 如没有使用到多态尽量使用静态类型
    • 静态类型可以更早的发现错误(在编译阶段而不是运行阶段)
    • 静态类型能够提高程序的可读性
    • 使用动态类型前最好判断其真实类型
  • 动态类型判断类型
    • - (BOOL)isKindOfClass:classObj 判断实例对象是否是这个类或者这个类的子类的实例
    Person *p = [Person new];
    Student *stu = [Student new];

    BOOL res = [p isKindOfClass:[Person class]];
    NSLog(@"res = %i", res); // YES
    res = [stu isKindOfClass:[Person class]];
    NSLog(@"res = %i", res); // YES
- (BOOL) isMemberOfClass: classObj 判断是否是这个类的实例
    Person *p = [Person new];
    Student *stu = [Student new];

    BOOL res = [p isMemberOfClass:[Person class]];
    NSLog(@"res = %i", res); // YES
    res = [stu isMemberOfClass:[Person class]];
    NSLog(@"res = %i", res); // NO
+ (BOOL) isSubclassOfClass:classObj 判断类是否是指定类的子类
    BOOL res = [Person isSubclassOfClass:[Student class]];
    NSLog(@"res = %i", res); // NO

    res = [Student isSubclassOfClass:[Person class]];
    NSLog(@"res = %i", res); // YES

new方法实现原理

1.new方法实现原理

  • 完整的创建一个可用的对象:Person *p=[Person new];
  • new方法的内部会分别调用两个方法来完成3件事情:
    • (1)使用alloc方法来分配存储空间(返回分配的对象);
    • (2)使用init方法来对对象进行初始化。
    • (3)返回对象的首地址
This method is a combination of alloc and init. Like alloc, it initializes the isa instance variable of the new object so it points to the class data structure. It then invokes the init method to complete the initialization process.
  • 可以把new方法拆开如下:

    • (1)调用类方法+alloc分配存储空间,返回未经初始化的对象Person *p1=[person alloc];
    • (2)调用对象方法-init进行初始化,返回对象本身 Person *p2=[p1 init];
    • (3)以上两个过程整合为一句:Person *p=[[Person alloc] init];
  • 说明:

    • alloc 与 init合起来称为构造方法,表示构造一个对象

    • alloc 方法为对象分配存储空间,并将所分配这一块区域全部清0.

      The isa instance variable of the new instance is initialized to a data structure that describes the class; memory for all other instance variables is set to 0.
      
    • init方法是初始化方法(构造方法),用来对象成员变量进行初始化,默认实现是一个空方法。

      An object isn’t ready to be used until it has been initialized. The init method defined in the NSObject class does no initialization; it simply returns self.
      
    • 所以下面两句的作用是等价的

      Person *p1 = [Person new];
      Person *p = [[Person alloc] init];
      
  • iOS 程序通常使用[[类名 alloc] init] 的方式创建对象,因为这个可以与其他initWithXX:...的初始化方法,统一来。代码更加统一

构造方法

1.重写init方法

  • 想在对象创建完毕后,成员变量马上就有一些默认的值就可以重写init方法
  • 重写init方法格式:
- (id)init {
    self = [super init];
    if (self) {
        // Initialize self.
    }
    return self;
}
+ [super init]的作用:

面向对象的体现,先利用父类的init方法为子类实例的父类部分属性初始化。

+ self 为什么要赋值为[super init]:

简单来说是为了防止父类的初始化方法release掉了self指向的空间并重新alloc了一块空间。还有[super init]可能alloc失败,这时就不再执行if中的语句。

  • 重写init方法其它格式
- (id)init {
    if (self = [super init]) {
        // Initialize self.
    }
    return self;
}

2.构造方法使用注意

  • 子类拥有的成员变量包括自己的成员变量以及从父类继承而来的成员变量,在重写构造方法的时候应该首先对从父类继承而来的成员变量先进行初始化
    • 原则:先初始化父类的,再初始化子类的。
      • 先调用父类的构造方法[super init];
      • 再进行子类内部成员变量的初始化。
  • 千万不要把self = [super init]写成self == [super init]
  • 重写构造方法的目的:为了让对象方法一创建出来,成员变量就会有一些固定的值。

3.instancetype的作用

  • instancetype与id相似,不过instancetype只能作为方法返回值,它会进行类型检查,如果创建出来的对象,赋值了不相干的对象就会有一个警告信息,防止出错。
// init此时返回值是id
NSString *str = [[Person alloc] init];
// Person并没有length方法, 但是id是动态类型, 所以编译时不会报错
NSLog(@"length = %i", str.length);
// init此时返回值是instancetype
// 由于instancetype它会进行类型检查, 所以会报警告
NSString *str = [[Person alloc] init];
NSLog(@"length = %i", str.length);
instancetype *p = [[person alloc] init];
// 错误写法instancetype只能作为返回值

自定义构造方法

1.自定义构造方法

  • 有时候仅仅靠重写构造方法(初始化方法),不能满足需求。比如一个班级中不可能所有学生的年龄都一样,这时候我们需要在创建某个学生的时候能够传入这个学生的年龄。这时候就需要来自定义构造函数(初始化函数)
  • 自定义构造方法的规范
    • (1)一定是对象方法,以减号开头
    • (2)返回值一般是instancetype类型
    • (3)方法名必须以initWith开头
  • 示例
@interface Person : NSObject

@property int age;

@property NSString *name;

// 当想让对象一创建就拥有一些指定的值,就可以使用自定义构造方法
- (id)initWithAge:(int)age;

- (id)initWithName:(NSString *)name;

- (id)initWithAge:(int)age andName:(NSString *)name;

@end

继承中的自定义构造方法

1.继承中的自定义构造方法

  • 不能在子类访问父类私有变量
  • 父类的属性交给父类的方法来处理
iOS开发--OC基础_第9张图片
initsuper.png

2.自定义构造方法的使用注意

  • (1)自己做自己的事情
  • (2)父类的属性交给父类的方法来处理,子类的方法只处理子类自己独有的属性
  • (3)自定义构造方法必须以intiWith开头,并且’W’必须大写

自定义类工厂方法

1.自定义工厂方法

  • 什么是工厂方法(快速创建方法)
    • 类工厂方法是一种用于分配、初始化实例并返回一个它自己的实例的类方法。类工厂方法很方便,因为它们允许您只使用一个步骤(而不是两个步骤)就能创建对象. 例如new
  • 自定义类工厂方法的规范
    • (1)一定是+号开头
    • (2)返回值一般是instancetype类型
    • (3)方法名称以类名开头,首字母小写
  • 示例
+ (id)person;
+ (id)person
{
    return  [[Person alloc]init];
}

+ (id)personWithAge:(int)age;
+ (id)personWithAge:(int)age
{
    Person *p = [[self alloc] init];
    [p setAge:age];
    return p;
}
  • apple中的类工厂方法
其实苹果在书写工厂方法时也是按照这样的规划书写
    [NSArray array];
    [NSArray arrayWithArray:<#(NSArray *)#>];
    [NSDictionary dictionary];
    [NSDictionary dictionaryWithObject:<#(id)#> forKey:<#(id)#>];
    [NSSet set];
    [NSSet setWithObject:<#(id)#>];

2.子父类中的类工厂方法

  • 由于子类默认会继承父类所有的方法和属性, 所以类工厂方法也会被继承
  • 由于父类的类工厂方法创建实例对象时是使用父类的类创建的, 所以如果子类调用父类的类工厂方法创建实例对象,创建出来的还是父类的实例对象
  • 为了解决这个问题, 以后在自定义类工厂时候不要利用父类创建实例对象, 改为使用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:@"gx"];
}

类的本质

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.类对象的存储

iOS开发--OC基础_第10张图片
ldxcc.png

5.OC实例对象 类对象 元对象之间关系

  • Objective-C是一门面向对象的编程语言。
    • 每一个对象 都是一个类的实例。
    • 每一个对象 都有一个名为isa的指针,指向该对象的类。
    • 每一个类述了一系列它的实例的特点,包括成员变量的列表,成员函数的列表等。
    • 每一个对象都可以接受消息,而对象能够接收的消息列表是保存在它所对应的类中。
  • 在Xcode中按Shift + Command + O打开文件搜索框,然后输入NSObject.h和objc.h,可以打开 NSObject的定义头文件,通过头文件我们可以看到,NSObject就是一个包含isa指针的结构体,如下图所示:
NSObject.h
@interface NSObject  {
    Class isa  OBJC_ISA_AVAILABILITY;
}
objc.h
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};
  • 按照面向对象语言的设计原则,所有事物都应该是对象(严格来说 Objective-C并没有完全做到这一点,因为它有int,double这样的简单变量类型)
    • 在Objective-C语言中,每一个类实际上也是一个对象。每一个类也有一个名为isa的指针。每一个类都可以接受消息,例如[NSObject new],就是向NSObject这个类发送名为new的消息。
    • 在Xcode中按Shift + Command + O,然后输入runtime.h,可以打开Class的定义头文件,通过头文件我们可以看到,Class也是一个包含isa指针的结构体,如下图所示。(图中除了isa外还有其它成员变量,但那是为了兼容非2.0版的Objective-C的遗留逻辑,大家可以忽略它。)
runtime.h
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
  • 因为类也是一个对象,那它也必须是另一个类的实例,这个类就是元类 (metaclass)。

    • 元类保存了类方法的列表。当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有则该元类会向它的父类查找该方法,直到一直找到继承链的头。
    • 元类(metaclass)也是一个对象,那么元类的isa指针又指向哪里呢?为了设计上的完整,所有的元类的isa指针都会指向一个根元类(root metaclass)。
    • 根元类(root metaclass)本身的isa指针指向自己,这样就行成了一个闭环。上面说到,一个对象能够接收的消息列表是保存在它所对应的类中的。在实际编程中,我们几乎不会遇到向元类发消息的情况,那它的isa 指针在实际上很少用到。不过这么设计保证了面向对象的干净,即所有事物都是对象,都有isa指针。
    • 由于类方法的定义是保存在元类(metaclass)中,而方法调用的规则是,如果该类没有一个方法的实现,则向它的父类继续查找。所以为了保证父类的类方法可以在子类中可以被调用,所以子类的元类会继承父类的元类,换而言之,类对象和元类对象有着同样的继承关系。
  • 下面这张图或许能够 让大家对isa和继承的关系清楚一些


    iOS开发--OC基础_第11张图片
    sldxldxydx.png
  • 上图中,最让人困惑的莫过于Root Class了。在实现中,Root Class是指 NSObject,我们可以从图中看出:

  • NSObject类对象包括它的对象实例方法。

  • NSObject的元对象包括它的类方法,例如new方法。

  • NSObject的元对象继承自NSObject类。

  • 一个NSObject的类中的方法同时也会被NSObject的子类在查找方法时找到。

类的启动过程

1.+load方法

  • 在程序启动的时候会加载所有的类和分类,并调用所有类和分类的+load方法(只会调用一次)
  • 先加载父类,再加载子类;也就是先调用父类的+load,再调用子类的+load
  • 先加载元原始类,再加载分类
  • 不管程序运行过程有没有用到这个类,都会调用+load加载
@implementation Person

+ (void)load
{
    NSLog(@"%s", __func__);
}
@end

@implementation Student : Person

+ (void)load
{
    NSLog(@"%s", __func__);
}
@end

输出结果:
+[Person load]
+[Student load]

2.+initialize

  • 在第一次使用某个类时(比如创建对象等),只会调用一次+initialize方法
  • 一个类只会调用一次+initialize方法,先调用父类的,再调用子类的
@implementation Person
+ (void)initialize
{
    NSLog(@"%s", __func__);
}
@end

@implementation Student : Person
+ (void)initialize
{
    NSLog(@"%s", __func__);
}
@end
int main(int argc, const char * argv[]) {
    Student *stu = [Student new];
    return 0;
}
输出结果:
+[Person initialize]
+[Student initialize]

SEL类型

1.什么是SEL类型

  • SEL类型代表着方法的签名,在类对象的方法列表中存储着该签名与方法代码的对应关系
  • 每个类的方法列表都存储在类对象中
  • 每个方法都有一个与之对应的SEL类型的对象
  • 根据一个SEL对象就可以找到方法的地址,进而调用方法
  • SEL类型的定义
    • typedef struct objc_selector *SEL;
  • 首先把test这个方法名包装成sel类型的数据
  • 根据SEL数据到该类的类对象中,去找对应的方法的代码,如果找到了就执行该代码
  • 如果没有找到根据类对象上的父类的类对象指针,去父类的类对象中查找,如果找到了,则执行父类的代码
  • 如果没有找到,一直像上找,直到基类(NSObject)
  • 如果都没有找到就报错。
  • 注意:
    • 在这个操作过程中有缓存,第一次找的时候是一个一个的找,非常耗性能,(缓存)之后就直接使用。
Dog *dog=[[Dog alloc] init];
[dog eat];
iOS开发--OC基础_第12张图片
sel.png

2.SEL使用

  • 定义普通的变量
    • 如:SEL sel = @selector(show);
  • 作为方法实参与NSObject配合使用
  • 检验对象是否实现了某个方法
    • - (BOOL) respondsToSelector: (SEL)selector 判断实例是否实现这样方法
    • + (BOOL)instancesRespondToSelector:(SEL)aSelector;
    BOOL flag;
    // [类 respondsToSelector]用于判断是否包含某个类方法
    flag = [Person respondsToSelector:@selector(objectFun)]; //NO
    flag = [Person respondsToSelector:@selector(classFun)]; //YES

    Person *obj = [[Person alloc] init];

    // [对象 respondsToSelector]用于判断是否包含某个对象方法
    flag = [obj respondsToSelector:@selector(objectFun)]; //YES
    flag = [obj respondsToSelector:@selector(classFun)]; //NO

    // [类名 instancesRespondToSel ector]用于判断是否包含某个对象方法
    // 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:@"gx"];

    SEL s3 = @selector(objectFun:value2:);
    [p performSelector:s3 withObject:@"gx" withObject:@"璐璐"];

    SEL s4 = @selector(classFun);
    [Person performSelector:s4];

    SEL s5 = @selector(classFun:);
    [Person performSelector:s5 withObject:@"gx"];

    SEL s6 = @selector(classFun:value2:);
    [Person performSelector:s6 withObject:@"gx" withObject:@"璐璐
"];
  • 作为方法形参
@implementation Person

- (void)makeObject:(id) obj performSelector:(SEL) selector
{
    [obj performSelector:selector];
}
@end

int main(int argc, const char * argv[]) {

    Person *p = [Person new];
    SEL s1 = @selector(eat);
    Dog *d = [Dog new];
    [p makeObject:d performSelector:s1];

    return 0;
}

3.OC方法查找顺序

iOS开发--OC基础_第13张图片
sldxldxydx.png
  • 1.给实例对象发送消息的过程(调用对象方法)
    • 根据对象的isA指针去该对象的类方法中查找,如果找到了就执行
    • 如果没有找到,就去该类的父类类对象中查找
    • 如果没有找到就一直往上找,直到根类(NSObject)
    • 如果都没有找到就报错
  • 2.给类对象发送消息(调用类方法)
    • 根据类对象的isA指针去元对象中查找,如果找到了就执行
    • 如果没有找到就去父元对象中查找
    • 如果如果没有找到就一直往上查找,直到根类(NSOject)
    • 如果都没有找到就报错

你可能感兴趣的:(iOS开发--OC基础)