OC语法学习分点总结
- 1. OC中的发送消息与c++的调用函数类似,但是有个重要的区别:==OC中采用动态绑定==
- 2. 动态绑定的缺点
- 3.oc中的属性,是不是类似于c++中的类成员变量?
- 4.快速枚举
- 5.OC中没有迭代器的概念,但是有快速枚举法
- 6.id类型
- 7.OC中的引用也是不可变的,其实感觉OC中的引用,更像是指针赋值而不是引用
- 8.NSObject
- 9.OC中在接口部分不直接声明成员变量,而在类的实现部分中声明实例变量
- 10.编译器会自动生成属性的getter和setter访问方法,如果需要,我们可以在实现中对他进行重写,同时编译器根据属性的声明自动生成了访问方法,使你可以像访问成员变量一样使用点语法来访问属性。
- 11. +代表类方法,-代表实例方法
- 12. 形参标签
- 13. nstancetype
- 14.类对象本身存在堆上,指向对象的指针,存在栈上
- 15. OC中的类变量,通过全局变量或静态变量实现
- 16. @package : 通常用于框架开发中,它允许同一框架内的类访问某些属性或方法,但不允许框架外的类访问
- 17. OC中的框架,可能类似于c++中的库,命名空间,模块等概念,但c++没有完全对应OC中框架的概念
- 18.合成存取方法
- 19.手动编写setter方法的语法格式
- 20.assign,atomic,copy等额外指示符
- 21.重点理解一下copy属性
- 22.理解引用计数
- 引用计数的增加
- 引用计数的减少
- 示例
- 23.其他额外指示符
- 24.OC中没有明确的抽象类
- 25.static注意点
- 26.重写自定义初始化示例
- 27.super关键字 : super就是一个临时的父类对象
前置声明,由于笔者学习过c++语言,因此文章中部分知识点会与c++做类比,方便同样学过c++语言的童鞋来学习
1. OC中的发送消息与c++的调用函数类似,但是有个重要的区别:OC中采用动态绑定
这意味着在运行时根据对象的实际类型来确定要调用的方法,而不是在编译时确定。
这种动态性使 Objective-C 非常灵活,可以实现多态、运行时切换方法等高级编程模式。它也支持在运行时添加、交换方法,以及更多高级功能,这些都是静态绑定难以实现的。
总之,尽管发送消息在概念上与函数调用相似,但它的动态性是 Objective-C 中一项强大的特性,使得编程更加灵活,适用于需要在运行时动态决定调用哪个方法的情况。
2. 动态绑定的缺点
-
性能开销:动态绑定通常比静态绑定更慢,因为在运行时需要查找方法的地址或虚函数表,这会引入一定的性能开销。对于性能敏感的应用程序,这可能是一个问题。
-
运行时错误:由于动态绑定在运行时确定方法,因此一些错误可能在运行时才被发现,而不是在编译时。这可以增加调试和测试的难度,因为需要更多的运行时测试来捕获这些错误。
-
可维护性:动态绑定可以使代码更加复杂,因为不同的对象可能在不同的时间点调用不同的方法,这会增加代码的可维护性难度。在大型项目中,更难追踪方法的调用。
-
代码可读性:由于动态绑定使代码更加灵活,有时候可能不容易理解一个方法将如何被调用。这可能降低代码的可读性和可理解性。
-
安全性:动态绑定可能导致潜在的安全问题,如对象未初始化或未预期的对象类型。这可能导致运行时崩溃或安全漏洞。
-
难以优化:由于方法的调用在运行时才能确定,编译器难以进行某些优化,如内联函数。这可能会影响代码的性能。
3.oc中的属性,是不是类似于c++中的类成员变量?
在 Objective-C 中,属性与 C++ 中的类成员变量类似,但也有一些不同之处。属性是 Objective-C 中对象的状态信息的封装,通常用于对对象的数据进行访问和修改。以下是一些关于 Objective-C 属性与 C++ 类成员变量的相似点和不同点:
相似点:
-
封装数据:属性和类成员变量都用于封装对象的数据。它们允许你将数据与对象的行为(方法)关联起来,并限制对数据的直接访问。
-
可访问性:你可以为属性和类成员变量定义访问权限,控制其他代码能否直接访问它们。这类似于 C++ 中的
public
、private
、protected
访问修饰符。 -
实例变量:在 Objective-C 中,属性通常对应于实例变量(实际的数据存储)。你可以通过生成
@synthesize
语句来自动生成属性的实例变量,或者自己显式声明实例变量。
不同点:
-
自动存取方法:Objective-C 的属性通常伴随着自动生成的访问方法(getter 和 setter)。这意味着你可以通过点语法(
.
)来访问和修改属性的值,就像访问对象的成员一样。在 C++ 中,你需要自己编写 getter 和 setter 方法。 -
封装性和控制:Objective-C 中的属性通常更强调封装性和控制。你可以在属性的定义中添加自定义逻辑,例如验证输入或触发其他操作。在 C++ 中,类成员变量可以直接访问,属性提供了更多的封装。
-
语法:属性的定义在 Objective-C 中使用
@property
关键字,而在 C++ 中,你只需在类中声明成员变量。
示例 Objective-C 属性定义:
@property (nonatomic, strong) NSString *name;
示例 C++ 类成员变量定义:
class MyClass {
public:
std::string name;
};
总之,属性在 Objective-C 中与类成员变量类似,但它们提供了更多的控制和封装,并自动创建访问方法。这使得 Objective-C 更易于维护和扩展对象的状态信息。
4.快速枚举
for (NSString *fruit in fruits) {
NSLog(@"I like %@", fruit);
}
这段代码是 Objective-C 中的快速枚举语法,类似于 C++ 中的范围-based for 循环。让我用 C++ 的知识来解释这段代码:
这段代码的功能是遍历一个名为 fruits
的集合(通常是一个数组或其他集合类型),并输出每个元素的文本表示。
相当于在 C++ 中的类似代码:
for (const auto& fruit : fruits) {
std::cout << "I like " << fruit << std::endl;
}
在这两种情况下,我们使用循环来迭代集合(Objective-C 中的 fruits
和 C++ 中的 fruits
),并使用迭代变量(Objective-C 中的 fruit
和 C++ 中的 fruit
)来代表集合中的每个元素。在每次循环迭代中,我们执行相同的操作,输出一个消息,其中包含集合中的元素。
虽然语法不同,但这两种语言都提供了一种方便的方法来遍历集合中的元素,使代码更易于编写和理解。
5.OC中没有迭代器的概念,但是有快速枚举法
6.id类型
在 Objective-C 中,id 是一种特殊的数据类型,表示一个未知的对象类型。它类似于 C++ 的 void* 指针,用于表示不明确的对象类型,或者可以接受任何对象类型的引用。
注意:id类型不是像c++中的auto那样自动类型推断,id是一个动态类型,可以引用任何OC对象
7.OC中的引用也是不可变的,其实感觉OC中的引用,更像是指针赋值而不是引用
8.NSObject
在 Objective-C 中,NSObject
是一个根类(Root Class),它是所有其他类的基类,也就是所有 Objective-C 类的直接或间接超类(父类)。NSObject
定义了一些最基本的方法和属性,用于处理对象的生命周期和一些基本的操作。
当你定义一个 Objective-C 类时,通常都会使该类继承自 NSObject
,这样该类将继承 NSObject
中定义的方法和属性,以便正确管理对象的内存、生命周期和其他基本操作。
例如:
@interface Calculator : NSObject
这表示你正在创建一个名为 Calculator
的类,并将其继承自 NSObject
,这意味着 Calculator
类将继承 NSObject
的功能,包括对象的内存管理和其他方法。
NSObject
是 Objective-C 中的一个核心类,它包含了对象导航、内存管理和一些其他常见操作的方法。在 Objective-C 中,大多数类都继承自 NSObject
,以便它们可以利用这些通用的功能。
9.OC中在接口部分不直接声明成员变量,而在类的实现部分中声明实例变量
\在 Objective-C 中,通常不直接在接口部分声明实例变量(成员变量)。相反,你可以在类的实现部分(.m
文件)中声明实例变量,而在接口部分(.h
文件)中声明属性。属性是用于访问和管理实例变量的一种方式。
在 Objective-C 2.0 中引入了属性(property)的概念,属性可以自动生成实例变量的访问方法。属性提供了一种更高级的方式来处理对象的数据封装和访问,同时隐藏了底层的实例变量。这使得你可以将属性的访问方法视为对象的行为,而不必直接暴露实例变量。
下面是一个例子,演示了如何在接口部分声明属性,并在实现部分声明实例变量和属性的访问方法:
Person.h(接口部分):
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (void)sayHello;
- (void)introduceYourself;
@end
Person.m(实现部分):
#import "Person.h"
@implementation Person {
// 在这里声明实例变量,通常不需要,因为属性会自动生成实例变量
}
// 实现属性的访问方法,不需要手动实现,通常由编译器自动生成
@synthesize name = _name;
@synthesize age = _age;
- (void)sayHello {
NSLog(@"Hello, my name is %@", self.name);
}
- (void)introduceYourself {
NSLog(@"I am %@ years old.", @(self.age));
}
@end
在上述示例中,我们在接口部分中声明了属性 name
和 age
,而在实现部分中可以选择声明对应的实例变量 _name
和 _age
。属性的访问方法通常由编译器自动生成,不需要手动实现。
这种属性的方式提供了一种更加抽象和封装的数据访问方法,同时允许你在需要时自定义实例变量的行为。因此,通常不需要在接口部分明确声明实例变量。
10.编译器会自动生成属性的getter和setter访问方法,如果需要,我们可以在实现中对他进行重写,同时编译器根据属性的声明自动生成了访问方法,使你可以像访问成员变量一样使用点语法来访问属性。
11. +代表类方法,-代表实例方法
12. 形参标签
在 Objective-C 方法声明中,形参标签(parameter label)用于描述方法的参数,但它们与 C++ 函数的形参类型不同。
在 Objective-C 中,方法声明通常分为两部分:方法名和参数描述。参数描述包括形参标签和形参类型。形参标签用于描述参数的目的或用途,而形参类型描述参数的数据类型。
例如,这是一个 Objective-C 方法声明的示例:
- (void)calculateAreaForWidth:(double)width andHeight:(double)height;
在上面的例子中,calculateAreaForWidth:andHeight:
是方法名,width
和 height
是形参标签,而 double
是形参类型。形参标签可以用于提供对参数的更清晰的描述,以便调用方法的代码更容易理解其用途。
与之不同,C++ 中的函数参数通常只包括参数的类型和名称,没有像 Objective-C 中的形参标签那样的附加描述。例如,C++ 函数的参数声明可能如下所示:
void calculateArea(double width, double height);
在这里,double
是参数的类型,而 width
和 height
是参数的名称。
13. nstancetype
instancetype
是 Objective-C 中的特殊关键字,通常用于指定方法的返回类型。它的主要用途是表示方法返回的是一个对象实例,但不指定具体的类。这使得在类继承和多态的情况下,能够更加灵活地返回正确的对象类型。
instancetype
常常用于构造方法(通常是初始化方法),以确保构造方法返回正确类型的对象。它在编写类方法时也可以用于表示返回的是对象实例。
示例,instancetype
的用法:
- (instancetype)init {
// 在构造方法中使用instancetype,以便在子类中正确返回对象实例
self = [super init];
if (self) {
// 初始化操作
}
return self;
}
在上面的例子中,- (instancetype)init
方法用 instancetype
关键字来表示它返回一个对象实例,但不指定具体的类。这在子类继承和多态的情况下非常有用,因为它确保了正确的对象类型。
总之,instancetype
是 Objective-C 中的关键字,用于指定方法返回的是一个对象实例,但不指定具体的类。这有助于确保方法返回正确类型的对象,从而提高代码的灵活性和可维护性。
14.类对象本身存在堆上,指向对象的指针,存在栈上
15. OC中的类变量,通过全局变量或静态变量实现
类变量(Class Variables):在 Objective-C 中,类变量通常通过全局变量或静态变量实现。这些变量是属于类而不是对象实例的,可以在类的方法中使用。虽然 Objective-C 不直接支持类似 C++ 中的静态成员变量的声明,但你可以使用全局变量或静态变量来模拟类变量的行为。
以下是一个示例,演示如何用OC模拟一个类变量:
// MyClass.h
extern int classVariable;
@interface MyClass : NSObject
+ (void)setClassVariable:(int)value;
+ (int)classVariable;
@end
// MyClass.m
#import "MyClass.h"
int classVariable = 0; // 类变量的实际定义和初始化
@implementation MyClass
+ (void)setClassVariable:(int)value {
classVariable = value;
}
+ (int)classVariable {
return classVariable;
}
@end
在这个示例中,classVariable
被定义为一个全局变量,它在 .m
文件中进行了初始化。MyClass
类声明了两个类方法 setClassVariable:
和 classVariable
,它们分别用于设置和获取类变量的值。
你可以在应用中使用这个类变量,例如:
[MyClass setClassVariable:42];
int value = [MyClass classVariable];
NSLog(@"Class Variable: %d", value);
这个示例演示了如何使用全局变量模拟类变量,以便在 Objective-C 中共享数据与类关联的数据。请注意,这不是 Objective-C 中的标准做法,但可以模拟类似的行为。在实际项目中,你可以使用全局变量或静态变量来实现类似的功能。
全局变量实现类变量的原理:这个变量语法上跟这个类没有任何关系,只是在全局中定义的变量,在类中也可以访问而已,这样就实现了数据共享
16. @package : 通常用于框架开发中,它允许同一框架内的类访问某些属性或方法,但不允许框架外的类访问
@package
是 Objective-C 中的一个访问权限修饰符,用于指定访问权限的级别。Objective-C 中的访问权限级别有四个,从最具限制性到最开放的依次是 @private
、@package
、@protected
和 @public
。
@private
:最严格的访问权限,只允许在类的声明中访问。@package
:允许在同一框架(Framework)内的类中访问,但不允许在不同框架内的类中访问。@protected
:允许在同一类和子类中访问。@public
:最开放的权限,允许在任何地方访问。
@package
通常用于框架开发中,它允许同一框架内的类访问某些属性或方法,但不允许框架外的类访问。这有助于将一些内部实现细节隐藏起来,以确保框架的封装性。
与 C++ 的访问权限修饰符类似,Objective-C 的访问权限修饰符允许你控制哪些部分的代码可以访问类的成员(属性和方法)。这有助于实现数据封装和控制访问级别,以确保代码的可维护性和安全性。
示例:
@interface MyClass : NSObject
{
@package
int packageVar; // 声明一个@package级别的成员变量
}
@end
在这个示例中,packageVar
是一个具有 @package
访问权限级别的成员变量,只允许同一框架内的类访问。
17. OC中的框架,可能类似于c++中的库,命名空间,模块等概念,但c++没有完全对应OC中框架的概念
在 Objective-C 中,框架通常是一个文件夹,它包含了库文件以及与库文件相关的一些头文件、资源和其他内容。这个文件夹通常有 .framework
扩展名,但不限于此。
一个框架包括了你封装进去的一些代码,包括类、方法、函数、数据结构等等。它还可以包含资源文件,如图像、声音、文本文件等。这些框架中的代码和资源可以被其他应用程序或开发者引用和使用,从而提供了一种模块化的方式来扩展应用程序的功能。
框架的主要目的是提供代码的封装和重用,它使得你能够将特定功能的实现隐藏在框架背后,只暴露出公共接口(头文件),从而降低了代码的复杂性,提高了代码的可维护性,并允许多个应用程序共享相同的功能。
所以,框架是一种非常有用的方式来组织和共享代码,尤其在开发大型应用程序或多个应用程序需要共享某些功能时非常有用。
18.合成存取方法
在 Objective-C 中,属性(Property)通常用于访问对象的实例变量。合成存取方法(Synthesized Accessors)是 Objective-C 编译器自动生成的方法,用于访问和修改属性的值。合成存取方法可以减少手动编写访问方法的工作,提高代码的可读性和可维护性。
下面是有关合成存取方法的重要信息:
-
自动合成:在 Objective-C 中,你可以使用
@property
关键字声明属性,然后使用@synthesize
或不使用@synthesize
(在现代 Objective-C 中通常不需要)来自动生成属性的存取方法。例如:@interface MyClass : NSObject @property (nonatomic, strong) NSString *name; @end
这里的
name
属性将自动生成存取方法。 -
合成存取方法的命名:合成存取方法的命名遵循一定的规则,通常为
setter
和getter
方法,分别用于设置和获取属性的值。例如,对于name
属性,生成的方法名称为setName:
和name
。setName:
:设置属性的方法。你可以使用self.name = @"John";
这样的语法来调用它。name
:获取属性的方法。你可以使用NSString *n = self.name;
这样的语法来调用它。
-
自定义存取方法:如果你希望自定义属性的存取方法,可以手动编写
getter
和setter
方法,并使用@synthesize
指令来告诉编译器不自动生成存取方法。这样你可以在方法中添加额外的逻辑,例如验证或处理。@interface MyClass : NSObject @property (nonatomic, strong) NSString *name; @end @implementation MyClass - (void)setName:(NSString *)newName { // 自定义的setter方法 if (newName != _name) { _name = [newName uppercaseString]; } } @synthesize name; // 不生成存取方法 @end
合成存取方法简化了属性的访问和修改,而不必手动编写大量的 getter 和 setter 方法。这提高了代码的可读性和可维护性,并使属性的使用更加便捷。但如果需要自定义存取方法,你仍然可以手动实现它们。
19.手动编写setter方法的语法格式
手动编写 setter 方法的语法格式在 Objective-C 是固定的,它通常遵循以下形式:
- (void)setPropertyName:(propertyType)newPropertyValue {
// 可以在这里添加自定义逻辑
_propertyName = newPropertyValue;
}
在这个格式中:
setPropertyName
是 setter 方法的名称,其中 “PropertyName” 是属性名称的首字母大写形式。例如,如果属性名称是 “name”,那么 setter 方法名称就是 “setName”。propertyType
是属性的类型,通常是对象类型,如NSString
,NSArray
,NSNumber
等。newPropertyValue
是将要设置的属性的新值,它是一个参数,你可以在方法内部进行操作。
这是一个示例,演示如何手动编写一个属性 “name” 的 setter 方法:
- (void)setName:(NSString *)newName {
if (![newName isEqualToString:_name]) {
_name = [newName uppercaseString]; // 在这个例子中,将属性值转为大写
}
}
你可以根据需要在 setter 方法中添加自定义逻辑,例如验证新值或进行额外的处理,但要确保最后将新值赋给属性的实例变量(通常是以 “_” 开头的变量名)。这是手动编写 setter 方法的一般格式。
20.assign,atomic,copy等额外指示符
在 Objective-C 中,属性(Properties)可以使用附加的指示符来控制其行为和内存管理。以下是一些常见的属性指示符和它们的作用:
-
assign
(默认):assign
指示符用于非对象类型的属性,例如基本数据类型(int
、float
等)和 C 结构体。- 它仅仅是将属性的值直接赋值给实例变量,不会增加引用计数。
- 对于对象类型,
assign
指示符应避免使用,因为它不会管理对象的生命周期,容易导致悬空指针问题。
-
retain
(已废弃):retain
指示符用于以手动方式管理内存的早期 Objective-C 代码。- 它会增加引用计数,需要手动释放对象。
- 在现代 Objective-C 中,一般使用更安全的
strong
替代。
-
strong
:strong
指示符用于对象属性,它会增加对象的引用计数,确保对象在被引用时不会被销毁。- 是默认的属性指示符,通常用于对象属性。
- 适用于 ARC(Automatic Reference Counting)下。
-
copy
:copy
指示符用于对象属性,它会创建对象的不可变副本,并将副本分配给属性。- 通常用于不希望属性的值受到外部修改的情况,或者用于属性是不可变的类。
- 防止对象被其他代码修改。
-
atomic
(默认):atomic
指示符用于确保属性在多线程环境下访问时是安全的。它会生成线程安全的存取方法。- 在多线程环境中,如果多个线程同时访问同一个属性,
atomic
会确保访问操作的原子性,但它的性能开销较大。
-
nonatomic
:nonatomic
指示符用于不需要线程安全保证的情况,它会生成非线程安全的存取方法。- 在单线程应用或者能够自己控制线程安全的情况下,使用
nonatomic
可以提高性能。
这些属性指示符可以根据属性的需求来选择。默认情况下,assign
用于基本数据类型,strong
用于对象类型,atomic
用于多线程环境。你可以根据具体情况和需求选择适当的属性指示符来确保代码的正确性和性能。在现代 Objective-C 中,通常会使用 ARC(Automatic Reference Counting),它会自动管理对象的内存,因此减少了手动管理的需求。
21.重点理解一下copy属性
将一个成员变量的属性设置为copy,用c++的知识来解释就是,在对该变量赋值时,会进行深拷贝,而不是浅拷贝.
就是说: a(copy属性)=b,会先拷贝一份b临时变量tmp,再将临时变量tmp赋值给a
22.理解引用计数
引用计数(Reference Counting)是 Objective-C 中的内存管理机制,它用于跟踪对象被引用的次数,确保在不再需要对象时能够正确释放其内存,避免内存泄漏。
下面将详细讲解引用计数,包括引用计数的增加和减少,以及在何时释放对象。
引用计数的增加
-
分配内存并初始化对象:当你使用
alloc
或init
方法创建一个对象时,对象的引用计数被设置为 1。Person *person = [[Person alloc] init];
-
通过
retain
方法增加引用计数:你可以使用retain
方法来增加对象的引用计数。这通常发生在需要共享对象的情况下。Person *anotherPerson = [person retain];
-
通过属性的
strong
指示符增加引用计数:当你将一个对象分配给具有strong
属性的属性时,对象的引用计数会增加。self.someProperty = person; // someProperty 具有 strong 指示符
引用计数的减少
-
通过
release
方法减少引用计数:你可以使用release
方法来减少对象的引用计数。当引用计数达到 0 时,对象会被销毁。[person release];
-
通过属性的
strong
指示符赋予nil
来减少引用计数:将属性设置为nil
会减少对象的引用计数。当没有任何引用指向对象时,对象会被销毁。self.someProperty = nil; // someProperty 具有 strong 指示符
示例
以下是一个示例,演示对象的引用计数如何增加和减少:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 创建一个 Person 对象,引用计数为 1
Person *person = [[Person alloc] init];
// 创建另一个指向同一对象的引用,引用计数为 2
Person *anotherPerson = [person retain];
// 将对象分配给属性,引用计数增加,属性持有对象
self.someProperty = person; // someProperty 具有 strong 指示符
// 释放一个引用,引用计数减少
[person release]; // 引用计数为 1
// 将属性设置为 nil,引用计数减少,对象被销毁
self.someProperty = nil; // someProperty 具有 strong 指示符,引用计数为 0,对象销毁
}
return 0;
}
在这个示例中,引用计数如何增加和减少的情况得以演示。请注意,在实际开发中,通常使用 ARC(Automatic Reference Counting)自动管理引用计数,而不需要手动调用 retain
、release
和 nil
。
23.其他额外指示符
除了前面提到的 assign
、retain
、strong
、copy
、atomic
、nonatomic
这些属性指示符,还有一些其他的属性指示符和关键字,用于在 Objective-C 中更精细地控制属性的行为,包括以下几种:
-
weak
:用于声明一个弱引用属性。弱引用不会增加对象的引用计数,当没有其他强引用指向对象时,对象会自动被释放,避免循环引用。@property (weak) NSObject *someWeakProperty;
-
(×)
unsafe_unretained
:类似于weak
,但不会自动置为nil
,因此需要手动管理对象的生命周期。这通常用于在不支持 ARC 的旧代码中。@property (unsafe_unretained) NSObject *someUnsafeProperty;
-
readonly
:用于声明只读属性,只能通过 getter 方法访问,不能通过 setter 方法修改。@property (readonly) NSString *readOnlyProperty;
-
readwrite
:用于声明可读可写的属性,通常用于与readonly
属性相对应。@property (readwrite) NSString *readWriteProperty;
-
getter
和setter
:你可以使用这些关键字来自定义属性的 getter 和 setter 方法的名称,以覆盖默认的方法名称。@property (getter=myGetterMethod, setter=mySetterMethod:) NSString *customProperty;
这些额外的属性指示符和关键字允许你更精细地控制属性的行为,根据需要选择合适的指示符来管理对象的引用计数和内存。需要注意的是,在现代 Objective-C 中,使用 ARC(Automatic Reference Counting)可以大大减少手动管理引用计数的需求,因此许多属性可以使用默认的 strong
、weak
等指示符来自动管理内存。
24.OC中没有明确的抽象类
在 Objective-C 中,没有像 Java 或 C++ 中那样显式支持抽象类的语法关键字。然而,你可以通过一些技巧来创建抽象类的模拟,并确保其子类实现特定的方法。
以下是创建抽象类的步骤:
-
创建一个基类:首先,你创建一个基类(通常称为抽象基类),该基类是所有相关子类的父类。这个基类应该包含一些方法的声明,这些方法需要在子类中实现。
@interface AbstractClass : NSObject - (void)abstractMethod; @end
-
不提供方法实现:在抽象基类中,你只需要提供方法的声明,而不提供具体的方法实现。这意味着抽象基类的方法没有默认行为,必须由子类来实现。
@implementation AbstractClass - (void)abstractMethod { // 没有提供默认实现 } @end
-
创建子类并实现抽象方法:然后,你可以创建具体的子类,它继承自抽象基类,并实现抽象方法。
@interface ConcreteClass : AbstractClass - (void)abstractMethod; @end @implementation ConcreteClass - (void)abstractMethod { // 实现抽象方法 NSLog(@"ConcreteClass's implementation of abstractMethod"); } @end
-
使用子类:现在你可以创建子类的实例并使用它们,调用抽象方法时会执行子类中的实现。
ConcreteClass *obj = [[ConcreteClass alloc] init]; [obj abstractMethod]; // 输出 "ConcreteClass's implementation of abstractMethod"
这种方式模拟了抽象类的概念。抽象类定义了接口和一些方法的声明,但没有提供默认实现,而具体的子类负责提供这些方法的具体实现。这确保了所有的子类都实现了特定的方法,但仍允许每个子类提供自己的实现。
需要注意的是,Objective-C 不会强制执行抽象方法的实现,这是一种约定,开发者需要确保所有的子类都正确地实现了抽象方法。如果一个子类没有实现抽象方法,它将继承来自抽象基类的空实现。
在 Objective-C 中,子类需要在其接口声明中再次声明(覆盖)从抽象基类继承的方法。这是为了让编译器知道子类有意要实现这个方法,否则编译器会认为方法未实现,从而产生警告或错误。
25.static注意点
OC中的static关键字不能用于修饰成员变量,它只能修饰局部变量.全局变量和函数,static修饰局部变量表示该局部变量存储到静态存储区;修饰全局变量用于限制该成员变量只能在当前源文件访问;修饰函数用于限制该函数只能在当前源文件中调用;
26.重写自定义初始化示例
在 Objective-C 中,你可以重写自定义初始化方法,通常是 init
方法的变体,以在对象创建时执行一些额外的操作或为属性设置默认值。下面是一个示例,展示如何重写自定义初始化方法:
// FKPerson.h
#import <Foundation/Foundation.h>
@interface FKPerson : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
@end
// FKPerson.m
#import "FKPerson.h"
@implementation FKPerson
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
self = [super init]; // 调用父类的初始化方法
if (self) {
// 在初始化方法中为属性赋值
self.name = name;
self.age = age;
// 在这里可以执行其他初始化操作
}
return self;
}
@end
在上述示例中,FKPerson
类重写了自定义的初始化方法 initWithName:age:
。在这个方法中,它首先调用父类的初始化方法([super init]
),然后为对象的属性赋值。你可以在初始化方法中执行其他任何必要的初始化操作。
使用自定义初始化方法时,你可以更灵活地为对象提供初始状态。示例中的 initWithName:age:
方法允许你在创建对象时指定姓名和年龄,并在初始化时将它们赋给对象的属性。
使用示例:
FKPerson *person = [[FKPerson alloc] initWithName:@"John" age:30];
NSLog(@"Name: %@, Age: %ld", person.name, (long)person.age);
这将创建一个 FKPerson
对象,并在初始化时为 name
和 age
属性赋值,然后输出属性的值。
27.super关键字 : super就是一个临时的父类对象
1.无论父类接口部分的成员变量使用何种访问控制符限制,子类接口部分定义的成员变量都不允许与父类接口部分定义的成员变量重名
2.父类在类实现部分定义的成员变量对子类没有任何影响,反过来子类对父类也一样
3.当子类实现部分定义了与父类重名的成员变量时,子类的成员变量就会隐藏父类的成员变量
因此,子类方法很难直接访问到父类的成员变量,此时就需要调用父类的方法来访问父类中被隐藏的成员变量如通过super.a(变量名)的方式访问,"."方法就是父类对象通过父类方法来访问,当然也可以是其他父类方法
如果子类中隐藏了父类中的成员变量,那么即使程序只创建了一个子类对象,该对象内部依然有两块内存来保存_a的成员变量
一块内存保存父类中被隐藏的_a成员变量,可以通过父类中定义的方法来访问
一块是保存子类实现部分定义的_a成员变量,可以在子类方法中直接访问