iOS学习笔记1——OC基础

本文是Objective-C初学者的学习笔记,详细介绍了OC的基础知识,包括OC与C的区别、NSLog与printf、函数与方法、方法的注意事项、类加载、面向对象概念、字符串使用、内存管理等内容,旨在帮助开发者掌握OC编程的基础技能。
摘要由CSDN通过智能技术生成

OC语言基础

OC与C

OC完全兼容C, 所以可以在OC程序中编写C语言代码,并且可以将C语言的源文件和OC的源文件组合在一起生成可执行文件。

printf与NSLog的区别

  1. NSLog会自动换行
  2. NSLog在输出内容时会附加一些系统信息(含有对象的内存地址)
  3. NSLog和printf接收的参数不一样
   printf("c hello world\n");
   NSLog(@"OC hello World");

函数和方法的区别

  1. 函数属于整个文件, 方法属于某一个类
    方法如果离开类就不行
  2. 函数可以直接调用, 方法必须用对象或者类来调用
    注意: 虽然函数属于整个文件, 但是如果把函数写在类的声明中会不识别
  3. 不能把函数当做方法来调用, 也不能把方法当做函数来调用

方法的注意点

方法可以没有声明只有实现
方法可以只有声明没有实现, 编译不会报错, 但是运行会报错
如果方法只有声明没有实现, 那么运行时会报:
reason: '+[Person demo]: unrecognized selector sent to class 0x100001140’
发送了一个不能识别的消息, 在Person类中没有+开头的demo方法
reason: '-[Person test]: unrecognized selector sent to instance 0x100400000’

类也有一个注意点:
类可以只有实现没有声明
注意: 在开发中不建议这样写

初学常见报错情况

 //    1.只有类的声明,没有类的实现
 //    2.漏了@end
 //    3. @interface和@implementation嵌套
 //    4.成员变量没有写在括号里面
 //    5.方法的声明写在了大括号里面
 //    6.成员变量不能在{}中进行初始化、不能被直接拿出去访问
 //    7.方法不能当做函数一样调用
 //    8.OC方法只能声明在@interface和@end之间,只能实现在@implementation和@end之间。也就是说OC方法不能独立于类存在
 //    9.C函数不属于类,跟类没有联系,C函数只归定义函数的文件所有
 //    10.C函数不能访问OC对象的成员
 //    11.低级错误:方法有声明,但是实现的时候写成了函数
 //    12.OC可以没有@interface同样可以定义一个类

#pragma mark的使用

#pragma mark - 类1
……
#pragma mark - 类2
……
#pragma mark - 类3
……

OC面向对象概念

 1.什么是面向对象?
 找对象使用对象的方法(功能)
 
 2.对象
 3.什么是类?
 类就是用于描述对象的共性特征
 主要用于描述对象的属性和行为
 
 4.如何定义一个类
 4.1类的声明
 声明类的目的: 告诉系统我们这个类中有哪些属性和方法
 类名: 首字母大写
 : NSObject 为了让我们的类具备创建对象的能力, 也就是可以使用new方法
 @interface 类名 : NSObject
 {
    属性; // 属性必须写到{}中, 属性名称以_开头
 }
 方法; // 方法必须写到{}外面
 @end
 
 4.2类的实现
 @implementation 类名
 
 方法的实现
 
 @end
 
 5.如何创建对象
 要想创建对象必须给类发送一个new消息, 调用类的new方法
 
 >开辟存储空间
 >初始化属性
 >返回地址
 类名 *p = [类名 new];
 
 5.1如何发送消息?  [类名/对象 方法名称]
 
 
 6.修改对象的属性和获取对象的属性
 因为类的本质是一个结构体, 所以我们是用一个指向结构体的指针保存了对象的地址, 所以我们可以通过指针操作结构体的方式来操作对象
 p->属性名称 = 值;  // 对象的属性默认是受保护的, 必须把属性变为公开的才可以直接访问@public
 NSLog(@"age = %i", p->age);
 
 7.类中可以定义两种方法
 7.1对象方法
    以-开头
    可以直接访问对象的属性
    必须使用对象调用
    
    对象方法中可以直接调用其它对象方法
    对象方法中可以直接调用类方法
 
 7.2类方法
    以+开头
    不可以直接访问对象的属性
    必须使用类调用
    
    类方法中不可以直接调用对象方法
    类方法中可以直接调用类方法
 
 7.3方法定义的注意点
    方法名采用 驼峰命名
    方法中的数据类型都必须使用()括起来
    方法中每个参数的数据类型前面都必须加上:
    :也是方法名的一部分

NString基本使用

int main(int argc, const char * argv[]) {
    
    /*
    // C语言中的字符串不是对象
    char *name1 = "lnj";
    char name2[] = "lmj";
    
    // OC中的字符串是一个对象
    // 正是因为OC中的字符串是一个对象, 所以它就具备了很多功能
    NSString *str = @"lk";
    
    Iphone *p = [Iphone new];
    // 注意: 输出C语言的字符串使用%s
    //      输出OC的字符串使用%@,  %@就专门用于输出对象类型的
//    NSLog(@"content = %s", [p loadMessage]);
    NSLog(@"content = %@", [p loadMessage]);
     */
    
    // 1.如何创建OC字符串
//    NSString *str = @"lnj";
    
//    printf("age = %i, height = %f\n", 30, 1.75);
//    NSString *str = [NSString stringWithFormat:@"age = %i, height = %f\n", 30, 1.75];
//    NSLog(@"str = %@", str);
    
    // 2.如何计算字符串的长度
//    char name[] = "lnj"; // l n j \0
    char name[] = "李";
    
    // 包含\0
    size_t size = sizeof(name);
    printf("size = %lu\n", size);
    
    // 不包含\0
    size_t length = strlen(name);
    printf("length = %lu\n", length);
    
    // 不包含\0的
    // 计算出来的是个数, 而不是占用的字节数
//    NSString *str = @"lnj";
    NSString *str = @"李南江";
    NSUInteger len = [str length];
    NSLog(@"len = %lu", len);
    return 0;
}

修改项目模板

/*
 修改项目模板以及main函数中的内容
 /Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/Project Templates/Mac/Application/Command Line Tool.xctemplate/
 
 修改OC文件头部的描述信息
 /Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/File Templates/Source/Cocoa Class.xctemplate
 
 */

/*
 Xcode文档安装的位置1:
 /Applications/Xcode.app/Contents/Developer/Documentation/DocSets
 注意: 拷贝之前最好将默认的文档删除, 因为如果同时存在高版本和低版本的文档, 那么低版本的不会显示
 Xcode文档安装的位置2:
 /Users/你的用户名/Library/Developer/Shared/Documentation/DocSets
 如果没有该文件夹可以自己创建一个
 
 */

匿名对象

// .h和.m之间切换快捷键 command + control + ⬆️

int main(int argc, const char * argv[]) {
   // 匿名就是没有名字, 匿名对象就是没有名字的对象
   
   // 1.有名字的对象
   // 只要用一个指针保存了某个对象的地址, 我们就可以称这个指针为某个对象
   // 称p为Person对象
   Person *p =[Person new]; // 0ffc12
   p->_age = 30;
   p->_name= @"lnj";
   [p say];
   /*
    0ffc12->_age = 30;
    0ffc12->_name= @"lnj";
    [0ffc12 say];

    */
   
   // 2.没有名字的对象
   // 无论有没有名字, 只要调用new方法都会返回对象的地址
   // 每次new都会新开辟一块存储空间
   [Person new]->_age = 30;
   [Person new]->_name = @"LMJ";
   [[Person new] say];
   
   
   // 3.匿名对象的应用场景
   // 3.1当对象只需要使用一次的时候就可以使用匿名对象
   Iphone *phone = [Iphone new]; // 0ffb11   phone = 0ffb11
   [phone brand]; // [0ffb11 brand];
   
   [[Iphone new] brand]; // [0fff5 brand];
   
   // 3.2匿名对象可以作为方法的参数(实参)
   Person *p1 = [Person new];
//    Iphone *phone1 = [Iphone new];
//    [p1 signal:phone1];
   [p1 signal:[Iphone new]];
   
   
   return 0;
}

description

// 可以重写description方法, 返回我们需要打印的内容
// 只要利用%@打印对象, 就会调用description
// 如果打印的是对象就会调用-号开头的description方法

/*
如果通过%@打印对象就会调用-号开头的
如果通过%@打印类对象就会调用+号开头的
*/
// +仅仅作为了解, 开发中99%的情况使用的都是-号开头的description
int main(int argc, const char * argv[]) {
   
   Person *p = [Person new];
   [p setAge:30];
   [p setName:@"lnj"];
   [p setHeigth:1.75];
   [p setWeight:65];
   [p setTel:@"13554499311"];
   [p setEmail:@"lnj@520it.com"];
   
//    NSLog(@"age = %i, name = %@, height = %f, weight = %f, tel = %@, email = %@", [p age], [p name], [p height], [p weight], [p tel], [p email]);
   // %@是用来打印对象的, 其实%@的本质是用于打印字符串
   // 只要利用%@打印某个对象, 系统内部默认就会调用父类的description方法
   // 调用该方法, 该方法会返回一个字符串, 字符串的默认格式 <类的名称: 对象的地址>
   
   NSLog(@"person = %@", p);
   
   NSLog(@"%@", p);
   // class注意c是小写, 只要给类发送class消息, 就会返回当前类的类对象
   // 1.获取Person对应的类对象
   Class c = [Person class];
   // 2.打印Person的类对象
   NSLog(@"当前对象对应的类 = %@", c);
   NSLog(@"当前对象的地址 = %p", p);
   return 0;
}

getter和setter方法

/*
setter方法:
作用: 设置成员变量的值
格式:
1. setter方法一定是对象方法
2. 一定没有返回值
3. 一定以set开头, 并且set后面跟上需要设置的成员变量的名称去掉下划线, 并且首字母大写
4. 一定有参数, 参数类型一定和需要设置的成员变量的类型一致, 并且参数名称就是成员变量的名称去掉下划线
*/
- (void)setSize:(int)size;

/*
getter方法:
作用: 获取成员变量的值
格式:
1. getter方法一定是对象方法
2.一定有返回值, 而且返回值一定和获取的成员变量的类型一致
3.方法名称就是获取的成员变量的名称去掉下划线
4. 一定没有参数
*/
- (int)size;

super关键字

super是个编译器的指令符号,只是告诉编译器在执行的时候,去调谁的方法,而self则是方法调用时的一个隐私参数;
// 只需要利用super给父类的方法发送一个消息, 那么系统就会自动调用父类的方法
// 如果以后想在子类中调用父类的方法可以使用super
// 如果想在给父类方法进行扩展的同时保留父类的方法, 那么可以使用super调用父类同名的方法
/*
super在类方法中, 一定会调用父类的类方法
super在对象方法中, 一定会调用父类的对象方法
可以利用super在任意方法中调用父类中的方法
*/

self关键字

OC语言中的self,就相当于C++、Java中的this指针。
要理解什么是self,什么是成员变量,什么是对象方法,什么是类方法
成员变量:成员变量是一个实例对象的具体状态特征,并且这些状态特征是可以改变的,如张三的年龄,身高,体重等
对象方法:一个实例对象的行为,比如张三具有吃的行为,张三做出这样行为的时候,有可能会影响,自身的某些状态特征,比如张三吃可能会增加张三体重和身高。
类方法:类方法是某个类的行为,可以直接通过类名调用;如果在类方法中需要使用某些数据,必须通过参数传入;它不能访问成员变量。

类方法中的self

在整个程序运行过程中,一个类有且仅有一个类对象。
通过类名调用方法就是给这个类对象发送消息。
类方法的self就是这个类对象
在类方法中可以通过self来调用其他的类方法
不能在类方法中去调用对象方法或成员变量,因为对象方法与成员变量都是属于具体的实例对象的。

对象方法中的self

在整个程序运行过程中,对象可以有0个或多个
通过对象调用方法就是给这个对象发送消息
对象方法中self就是调用这个方法的当前对象。
在对象方法中,可以通过self来调用本对象上的其他方法
在对象方法中,可以通过self来访问成员变量

self总结

谁调用self所在的方法,那么self就是谁

self在类方法中,就是这个类的类对象,全局只有一个,可通过self调用本类中的其他类方法,但是不能通过self来调用对象方法或访问成员变量

self在对象方法中,就是调用这个方法的那个对象, 可以通过self调用本类中其他的对象方法,访问成员变量,但不能通过self调用本类的类方法。

通过self调用方法的格式:[self 方法名];

通过self访问成员变量格式:self->成员变量名

self使用注意

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

全局变量、成员变量、局部变量

全局变量:只要是有声明它的地方都能使用
成员变量:只能在本类和其子类的对象方法中使用
局部变量:只能在本函数或方法中使用
从作用域的范围来看:全局变量 > 成员变量 > 局部变量
当不同的作用域中出现了同名的变量,内部作用域的变量覆盖外部作用域变量,所以同名变量的覆盖顺序为:局部变量覆盖成员变量,成员变量覆盖全局变量
如果在对象方法中出现与成员变量同名的局部变量,如果此时想使用该成员变量可以通过self->成员变量名的方式

点语法

当点语法使用在 “=“赋值符号左侧的时候,点语法会被展开为setter方法的调用,其他情况(等号右侧、直接使用)为点语法展开为getter方法的调用

点语法的本质是方法的调用,而不是访问成员变量,当使用点语法时,编译器会自动展开成相应的方法调用。

切记点语法的本质是转换成相应的对setter和getter方法调用,如果没有set和get方法,则不能使用点语法。

不要在getter 与 setter方法中使用本属性的点语法

- (void) setAge:(int)age {
    // 下面的代码会引发死循环
    self.age = age;
        //编译器展开后 [self setAge:age]
}


- (int) age {
    // 下面的代码会引发死循环
    return self.age;
          // 编译器展开后 [self   age]
}

继承

基本概念

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

@interface 子类名称 : 父类名称

@end

相关特性

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

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

1、在自己类中找
2、如果没有,去父类中找
3、如果父类中没有,就去父类的父类中
4、如果父类的父类也没有,就还往上找,直到找到基类(NSObject)
5、如果NSObject都没有就报错了

3.继承的注意事项

子类不能定义和父类同名的成员变量,私有成员变量也不可以;因为子类继承父类,子类将会拥有父类的所有成员变量,若在子类中定义父类同名成员变量 属于重复定义。

OC类支持单一继承,不支持多继承;也就是说一个类只能有一个直接父类

OC类支持多层继承

实例变量修饰符

作用域

1)@public (公开的)在有对象的前提下,任何地方都可以直接访问。
2)@protected (受保护的)只能在当前类和子类的对象方法中访问
3)@private (私有的)只能在当前类的对象方法中才能直接访问
4)@package (框架级别的)作用域介于私有和公开之间,只要处于同一个框架中相当于@public,在框架外部相当于@private

在子类中的访问

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

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

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

私有变量和私有方法

OC中的私有变量

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

@implementation Dog
{
    @public
    int _age;
}
@end

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是编译器的指令

什么是编译器的指令 ?

编译器指令就是用来告诉编译器要做什么!

@property会让编译器做什么呢?

@property 用在声明文件中告诉编译器声明成员变量的的访问器(getter/setter)方法

这样的好处是:免去我们手工书写getter和setter方法繁琐的代码

@property基本使用

在Xocde4.4之前, 可以使用@porperty来代替getter/setter方法的声明
也就是说我们只需要写上@porperty就不用写getter/setter方法的声明

用@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属性


@interface Person : NSObject


 如果给一个属性同时提供了getter/setter方法, 那么我们称这个属性为可读可写属性
 如果只提供了getter方法, 那么我们称这个属性为只读属性
 如果只提供了setter方法, 那么我们称这个属性为只写属性
 如果既没有提供getter也没有提供setter方法, 那么我们称这个属性为私有属性
 
 
 格式:
 @property(属性修饰符) 数据类型 变量名称;
 
 readwrite: 代表既生成getter方法 , 也生成setter方法
 默认情况下 @property就是readwrite的
@property(readwrite) int age;
/*
 - (void)setHeight:(double)height;
 - (double)height;
 
 - (void)setHeight:(double)height;
 - (double)abc;
 */
@property(getter=abc) double height;

/*
 - (void)setWeight:(double)weight;
 - (void)tiZhong:(double)weight;
 */
@property(setter=tiZhong:) double weight;
 readonly: 代表只生成getter方法不生成setter方法
@property(readonly) NSString * name;

// 是否已婚
 程序员之间有一个约定, 一般情况下获取BOOL类型的属性的值, 我们都会将获取的方法名称改为isXXX
@property(getter=isMarried) BOOL married;
@end

@property增强

@interface Person : NSObject
/*
{
    @public
    int _age;
    int age;
}
 
 */
 从Xcode4.4以后apple对@property进行了一个增强, 以后只要利用一个@property就可以同时生成setter/getter方法的声明和实现
 没有告诉@property要将传入的参数赋值给谁, 默认@property会将传入的属性赋值给_开头的成员变量
 
 @property有一个弊端: 它只会生成最简单的getter/setter方法的声明和实现, 并不会对传入的数据进行过滤
 如果想对传入的数据进行过滤, 那么我们就必须重写getter/setter方法
 如果不想对传入的数据进行过滤, 仅仅是提供一个方法给外界操作成员变量, 那么就可以使用@property
 
 如果利用@property来生成getter/setter方法, 那么我们可以不写成员变量, 系统会自动给我们生成一个_开头的成员变量
 注意: @property自动帮我们生成的成员变量是一个私有的成员变量, 也就是说是在.m文件中生成的, 而不是在.h文件中生成的
 
// age? _age;
/*
 - (void)setAge:(int)age;
 - (int)age;
 */
@property int age;

@end

@synthesize

基本概念

@synthesize是编译器的指令

什么是编译器的指令 ?

编译器指令就是用来告诉编译器要做什么!

@synthesize会让编译器做什么呢?

@synthesize 用在实现文件中告诉编译器实现成员变量的的访问器(getter/setter)方法
这样的好处是:免去我们手工书写getterr和setter方法繁琐的代码

@synthesize基本使用

在@implementation中, 用来自动生成setter和getter的实现
用@synthesize age = _age;就可以代替

- (int)age{
	return _age;
}
- (void)setAge:(int)age{
	_age = age;
}

@synthesize编写步骤

1.在@implementation和@end之间写上@synthesize
2.在@synthesize后面写上和@property中一样的属性名称, 这样@synthesize就会将@property生成的什么拷贝到@implementation中
3.由于getter/setter方法实现是要将传入的形参 给属性和获取属性的值,所以在@synthesize的属性后面写上要将传入的值赋值给谁和要返回哪个属性的值, 并用等号连接
以下写法会赋值给谁?

@interface Person : NSObject
{
    @public
    int _age;
    int _number;
}

@property int age;

@end

@implementation Person

@synthesize age = _number;

@end

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

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

    return 0;
}

@synthesize注意点

@synthesize age = _age;

setter和getter实现中会访问成员变量_age
如果成员变量_age不存在,就会自动生成一个@private的成员变量_age

@synthesize age;

setter和getter实现中会访问@synthesize后同名成员变量age
如果成员变量age不存在,就会自动生成一个@private的成员变量age

多个属性可以通过一行@synthesize搞定,多个属性之间用逗号连接
@synthesize age = _age, number = _number, name = _name;

id类型

静态类型和动态类型

静态类型
将一个指针变量定义为特定类的对象时,使用的是静态类型,在编译的时候就知道这个指针变量所属的类,这个变量总是存储特定类的对象。

Person *p = [Person new];

动态类型
这一特性是程序直到执行时才确定对象所属的类

id obj = [Person new];

为什么要有动态类型?

我们知道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是运行时特性

虽然说id数据类型可以存储任何类型的对象,但是不要养成滥用这种通用类型的习惯

如没有使用到多态尽量使用静态类型
静态类型可以更早的发现错误(在编译阶段而不是运行阶段)
静态类型能够提高程序的可读性
使用动态类型前最好判断其真实类型

动态类型判断类型

  • -(BOOL)isKindOfClass:classObj //判断实例对象是否是这个类或者这个类的子类的实例

new方法实现原理

new方法的内部会分别调用两个方法来完成3件事情:
(1)使用alloc方法来分配存储空间(返回分配的对象);
(2)使用init方法来对对象进行初始化。
(3)返回对象的首地址

构造方法

重写init方法

想在对象创建完毕后,成员变量马上就有一些默认的值就可以重写init方法

重写init方法格式:

- (instancetype)init {
    if (self = [super init]) {
        // Initialize self.
    }
    return self;
}

构造方法中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)一定是对象方法,以减号开头
(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

自定义类工厂方法

什么是工厂方法(快速创建方法)?

类工厂方法是一种用于分配、初始化实例并返回一个它自己的实例的类方法。类工厂方法很方便,因为它们允许您只使用一个步骤(而不是两个步骤)就能创建对象. 例如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;
}

类的本质及内存分配

  • 类的本质其实也是一个对象(类对象)
  • 程序中第一次使用该类的时候被创建,在整个程序中只有一份。
  • 此后每次使用都是这个类对象,它在程序运行时一直存在。
  • 类对象是一种数据结构,存储类的基本信息:类大小,类名称,类的版本,继承层次,以及消息与函数的映射表等
  • 类对象代表类,Class类型,对象方法属于类对象
  • 如果消息的接收者是类名,则类名代表类对象
  • 所有类的实例都由类对象生成,类对象会把实例的isa的值修改成自己的地址,每个实例的isa都指向该实例的类对象

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

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

类的加载

+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]

+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类型

什么是SEL类型

SEL类型代表着方法的签名,在类对象的方法列表中存储着该签名与方法代码的对应关系

每个类的方法列表都存储在类对象中

每个方法都有一个与之对应的SEL类型的对象

根据一个SEL对象就可以找到方法的地址,进而调用方法

SEL类型的定义

  • typedef struct objc_selector *SEL;

首先把test这个方法名包装成sel类型的数据

根据SEL数据到该类的类对象中,去找对应的方法的代码,如果找到了就执行该代码

如果没有找到根据类对象上的父类的类对象指针,去父类的类对象中查找,如果找到了,则执行父类的代码

如果没有找到,一直像上找,直到基类(NSObject)

如果都没有找到就报错。

注意:
在这个操作过程中有缓存,第一次找的时候是一个一个的找,非常耗性能,之后再用到的时候就直接使用。

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

    // [类名 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:@"lnj"];

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

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

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

    SEL s6 = @selector(classFun:value2:);
    [Person performSelector:s6 withObject:@"lnj" withObject:@"lmj"];

作为方法形参

@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;
}

OC方法查找顺序

1.给实例对象发送消息的过程(调用对象方法)

根据对象的isA指针去该对象的类方法中查找,如果找到了就执行
如果没有找到,就去该类的父类类对象中查找
如果没有找到就一直往上找,直到跟类(NSObject)
如果都没有找到就报错

2.给类对象发送消息(调用类方法)

根据类对象的isA指针去元对象中查找,如果找到了就执行
如果没有找到就去父元对象中查找
如果如果没有找到就一直往上查找,直到根类(NSOject)
如果都没有找到就报错

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值