目录展开

目录
isKindOfClass && isMemberOfClass
我们还需要重写 alloc 函数,使 alloc 函数返回同一个对象地址。
隐藏和封装
有四种访问控制符:==@private (当前类访问权限),@package (与映像访问权限相同),@protect (子类访问权限),@public (公共访问权限)。
访问控制符
1.private(当前类访问权限) 成员变量只能在当前类的内部被访问,用于彻底隐藏成员变量。在类的实现部分定义的成员变量默认使用这种访问权限。
2.package(与映像访问权限相同) 成员变量可以在当前类以及当前类实现的同一个映像的任意地方进行访问。用于部分隐藏成员变量
3.protected(子类访问权限) 成员变量可以在当前类,当前类的子类的任意地方进行访问。类的接口部分定义的成员变量默认这个访问权限
4.public(公共访问权限) 这个成员变量可以在任意地方进行访问。
理解 @package 访问控制符
@package 使受他控制的成员变量不仅可以在当前类访问,还可以在相同映像的其他程序中访问。关键何为相同映像?
同一映像的概念: 简单的说,就是 编译后生成的同一个框架或同一个执行文件 ,当我们想开发一个基础框架时,如果用 private 就限制的太死了,其他函数可能需要直接访问这个成员变量,但是该框架又不希望外部程序访问我们的成员变量,就可以考虑用 package 了
** 当编译器最后把 @package 限制的成员变量所在的类,其他类,函数编译成一个框架库后,这些类、函数就都在一个映像中(注意这里的函数包括我们的主函数) **。也就是说当我们使用 @package 之后我们的主函数也可以调用我们的成员变量,但是当其他程序引用这个框架库时,由于其他程序与这个框架库不在一个映像中,其他程序就无法访问我们的被 @package 限制的成员变量。
对象初始化
一、为对象分配空间
无论创建哪个对象,我们都先需要使用 alloc 方法来分配我们的内存,alloc 方法来自 NSObject 类,而所有的类都是他的子类。 我们调用 alloc 方法时,系统帮我们完成以下的事情。
1、系统为我们的所有实例变量分配内存空间 2、将每个实例变量的内存空间都置为 0,同时对应的类型置为对应的空值。 仅仅分配内存空间的对象还不能使用,还需要执行初始化,也就是 init 才可以使用它。
二、初始化方法与对象初始化
重写一个 init 来代替 NSObject 的 init
#import "Person.h"
@implementation Person
-(id) init { if (self = [super init]) { self.name = @"四川芬达"; self.saying = @"他们朝我扔粑粑"; self.age = 123; } return self;}
@endNS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic,copy) NSString* name;@property (nonatomic,copy) NSString* saying;@property (nonatomic,assign) NSInteger age;
@end
NS_ASSUME_NONNULL_END#import <Foundation/Foundation.h>#import "Person.h"
int main(int argc, const char * argv[]) { @autoreleasepool { Person* singer = [[Person alloc] init]; NSLog(@"%@",singer.name); NSLog(@"%@",singer.saying); NSLog(@"%ld",singer.age); } return 0;}OC 属性及属性关键字
@property
属性(@property)声明访问器及其语义;在自动合成的常见情况下,编译器会生成 getter/setter,并可生成后备实例变量,但属性不等同于 “ivar + 两个方法”。
可以用 @property 语法来声明属性。@property 会帮我们自动生成属性的 setter 和 getter 方法的声明。
@synthesize
@synthesize 可显式要求编译器合成属性访问器,并可指定后备实例变量;现代 Objective-C 通常会自动合成普通属性。
你还可以通过 @synthesize 来指定实例变量名字,如果你不喜欢默认的以下划线开头来命名实例变量的话。但最好还是用默认的,否则影响可读性。
如果不想令编译器合成存取方法,则可以自己实现。如果你只实现了其中一个存取方法 setter or getter,那么另一个还是会由编译器来合成。但是需要注意的是,如果你实现了属性所需的全部方法(如果属性是 readwrite 则需实现 setter and getter,如果是 readonly 则只需实现 getter 方法),那么编译器就不会自动进行 @synthesize,这时候就不会生成该属性的实例变量,需要根据实际情况自己手动 @synthesize 一下。
@synthesize ivar = _ivar@dynamic
@dynamic 告诉编译器不要自动合成该属性的 getter/setter;这些访问器必须由其他机制在运行时提供,否则调用时会因缺少方法实现而失败。它不等于 “运行时自动提供方法”。
类、元类、父类的关系
我们先给出类与对象源码的定义:
我们的类实际上就是一个结构体指针。
typedef struct objc_class *Class;struct objc_class { Class isa; Class super_class; const char *name; long version; long info; long instance_size; struct objc_ivar_list *ivars; struct objc_method_list **methodLists; struct objc_cache *cache; struct objc_protocol_list *protocols;};我们再来看看对象的定义:

OC 的类其实也是一个对象,一个对象就要有一个它属于的类,意味着类也要有一个 isa 指针,指向其所属的类。那么类的类是什么?就是我们所说的元类,所以,元类就是类的所属类。

这样我们便可以总结出我们类、元类、父类的基本关系:
isa 指向 实例对象的所属类实际上就是其所属类,我们的实例对象就是图中的 Instance of Subclass。 接下来我们的 isa 指向了我们的类,那么继续研究我们类的所属类。 从图中可以看出,我们类的所属类实际上就指向了我们的元类。 接下来我们再看我们的元类,我们所有元类的所属类实际上都是根元类,在笔者参考的资料中是这样理解的,因为我们 OC 中几乎所有的类都是 NSObject 的子类,所以我们的根元类也可以认为是我们的 NSObject 的元类。 接着我们再看图中根元类的虚线,根元类所属的类指向了它本身。
superClass 指向 我们这里的 superClass 指向就是我们类的父类,到了这里其实就比较好理解了,元类与我们的类一样,其父类都是一层层往上的,而不像元类所属的类一样,全部指向我们的根元类
在这里唯一需要注意的是我们的根元类的父类是我们的根类,也可以理解为 NSObject 类。而 NSObject 类的父类就是 nil 了。
isKindOfClass && isMemberOfClass
re1:1 [NSObject类对象 isKindOfClass:[NSObject class]]NSObject 的元类的继承链最终指向 NSObject 类本身(因为根元类的父类是 NSObject),因此返回 YES(1)
re2:0 [NSObject类对象 isMemberOfClass:[NSObject class]]NSObject 的类对象的元类是 NSObject 的元类(metaclass),而非 NSObject 类本身,因此返回 NO(0)。
re3:0 [GGObject类对象 isKindOfClass:[GGObject class]]GGObject 的元类的继承链未指向 GGObject 类本身(除非显式修改元类继承关系),因此 isKindOfClass: 返回 NO(0)。
re4:0 [GGObject类对象 isMemberOfClass:[GGObject class]]类对象的元类与目标类 GGObject 不匹配,因此 isMemberOfClass: 返回 NO(0)。
re5:1 [[NSObject分配的对象] isKindOfClass:[NSObject class]]实例对象是 NSObject 的直接实例,且 isKindOfClass: 会检查类的继承链。NSObject 是所有类的根类,因此返回 YES(1)。
re6:1 [[NSObject分配的对象] isMemberOfClass:[NSObject class]]实例对象直接属于 NSObject 类,因此 isMemberOfClass: 返回 YES(1)。
re7:1 [[GGObject分配的对象] isKindOfClass:[GGObject class]]GGObject 实例的类继承自 NSObject,但 isKindOfClass: 会检查到其自身类 GGObject,因此返回 YES(1)。
re8:1 [[GGObject分配的对象] isMemberOfClass:[GGObject class]]实例对象直接属于 GGObject 类,因此 isMemberOfClass: 返回 YES(1)。isKindOfClasss:判断类或元类继承链是否包含目标类
isMemberOfClass:严格匹配当前类或元类是否等于目标类
特殊:NSObject 的元类的父类是 NSObject 类本身,因此 [NSObject class] isKindOfClass:[NSObject class] 返回 YES,而其他类(如 GGObject)的元类无此特性
+isKindOfClass 类方法是从当前类的 isa 指向 (也就是当前类的元类) 开始,沿着 superclass 继承链查找判断和对比类是否相等。 -isKindOfClass 对象方法是从 [self class] (当前类) 开始,沿着 superclass 继承链查找判断和对比类是否相等。 +isMemberOfClass 类方法是直接判断当前类的 isa 指向 (也就是当前类的元类) 和对比类是否相等。 -isMemberOfClass 对象方法是直接判断 [self class] (当前类) 和对比类是否相等。

编译结果:

关键字
关键字 含义与用途 @interface 声明一个类或协议 @implementation 实现一个类或协议 @end 结束一个类、实现或协议的定义 @class 向前声明类,解决循环引用 @public @private @protected 成员变量访问控制 @property 声明属性(自动生成 getter/setter) @synthesize 合成属性对应的实例变量(早期) @dynamic 声明属性由开发者手动实现 @protocol 声明协议(类似 Java 中 interface) @optional / @required 协议方法的实现可选或必须 @selector 获取方法选择器 SEL @encode 获取类型的编码 @defs 获取结构体定义(已弃用) @try @catch @finally 异常处理 @throw 抛出异常 @autoreleasepool 自动释放池,用于内存管理 @import 模块导入(代替 #import,更现代) @synchronized 线程同步
@interface MyClass : NSObject { int _value;}- (void)doSomething;@end
@implementation MyClass- (void)doSomething { NSLog(@"Doing something");}@end
int main(int argc, const char * argv[]) { @autoreleasepool { NSString *str = [[NSString alloc] initWithFormat:@"Hello"]; NSLog(@"%@", str); } // 自动释放池在此处释放 return 0;}
@interface MyClass : NSObject@property (nonatomic, strong) NSString *name;@end
@implementation MyClass@synthesize name; // 自动生成 _name 和 name 的 getter/setter@end
@protocol MyDelegate <NSObject>@required- (void)doTask;
@optional- (void)optionalTask;@end
SEL s = @selector(doSomething);
@class AnotherClass;
@interface MyClass : NSObject@property (nonatomic, strong) AnotherClass *obj;@end
@synchronized(self) { // 线程安全的代码}assign
assign 对属性执行不持有对象的简单赋值,不增加所赋对象的引用计数;它主要适用于 NSInteger、short、float、double 和结构体等标量或 C 数据类型。用于对象指针时,对象释放后指针不会自动置为 nil,可能成为悬垂指针。基本数据类型通常用 assign,不是因为它们一定存放在栈上,而是因为它们不是需要管理对象生命周期的指针。
weak
weak:该属性也是一个弱引用声明类型,使用 weak 修饰的对象是不会造成引用计数器 +1 的,并且引用的对象如果被释放了以后会自动变成 nil,不会出现野指针,很好的解决了内存引起的崩溃情况。通常我们会在 block 和协议的时候使用 weak 修饰,通过这样的修饰,我们可以规避掉循环引用的问题。
strong
strong:该属性是一个强引用声明类型,只要该强引用指向被赋值的对象,那么该对象就不会自动回收。
**retain **
retain:MRC 下取得对象的一份所有权;ARC 代码通常用 strong 表达强所有权,不能把 retain 说成用于 “会改变值并影响其他方法” 的变量。
copy
copy:若使用 copy 指示符,则调用 setter 方法给变量赋值的时候,会将被赋值的对象复制一个副本,再将该副本赋值给成员变量。copy 指示符会将原成员变量所引用对象的计数减 1。 相当于就是说,不用 copy 的话,会创建一个新的空间,它的内容和原对象内容一模一样,然后指针是指向新空间的。当再有什么操作在对那个对象操作的话,只是在原空间上操作,对新空间没有影响。当成员变量的类型是可变类型,或其子类是可变类型的时候,被赋值的对象有可能在赋值后被修改,如果程序不需要这种修改影响 setter 方法设置的成员变量的值,此时就可考虑用 copy 指示符。
strong、copy
** 如果属性声明中指定了 copy 特性,合成方法会使用类的 copy 方法 ** ,这里注意:属性并没有 MutableCopy 特性。 即使是可变的实例变量,也是使用 copy 特性,正如方法 copyWithZone:的执行结果。所以,按照约定会生成一个对象的不可变副本。
相同之处:用于修饰标识拥有关系的对象
strong 通常持有同一个对象;copy 按对象的 NSCopying 语义取得副本,但不可变对象的 copy 可能直接返回原对象,不能断言每次都会分配新内存。NSString 属性通常使用 copy,避免可变输入后续修改影响属性值。
回顾一下深浅拷贝
深拷贝就是内容拷贝,浅拷贝就是指针拷贝。
深拷贝、浅拷贝要按对象及其成员的具体复制行为判断;深拷贝通常创建独立副本,浅拷贝可能共享对象或成员。不能将它们一概简化为 “地址不同” 与 “引用计数加一”。
#import <Foundation/Foundation.h>#import "Person.h"
int main(int argc, const char * argv[]) { @autoreleasepool { NSMutableString *otherName = [[NSMutableString alloc] initWithString:@"Jack "]; Person *person = [[Person alloc] init]; person.name = otherName; person.age = 23;
[otherName appendString:@"and rose"]; NSLog(@"person.name = %@",person.name);
} return 0;}copy 的结果:

strong 的结果:

因为 otherName 是可变的,person.name 属性是 copy,所以创建了新的字符串,属于深拷贝,内容拷贝,我们拷贝出来了一个对象,后面的赋值操作都是针对新建的对象进行操作,而我们实际的调用还是原本的对象。所以值并不会改变。 如果设置为 strong,strong 会持有原来的对象,使原来的对象引用计数 +1,其实就是浅拷贝、指针拷贝。这时我们进行操作,更改其值就使本对象发生了改变。
我们既然创建的是不可变类型,那我们就不希望其发生改变,所以这个时候我们就应该使用 copy 关键字,strong 会使其在外部被修改时发生改变
那么对于可变类型字符串 ,我们使用 copy 时,按约定会生成一个对象的不可变副本,此时我们进行增删改操作就会因为找不到对象而崩溃

readonly、readwrite
这是访问权限的控制,决定该属性是否可读和可写,默认是 readwrite,所以我们定义属性的时候,一般不需要这个修饰。只有只读属性才加上 readonly
readonly 控制属性的读写权限:只生成 getter,不生成 setter。
单例模式
单例模式是因为在某些时候,程序多次创建这一类的对象没有实际的意义,那么我们就只在程序运行的时候只初始化一次,自然就产生了一个单例模式。
定义:
单例模式通过统一的访问点提供一个共享实例。实例可按实现策略在首次访问时创建或提前创建;它不是在编译时分配好的全局对象。
有三个注意点
- 单例类只有一个实例
- 单例类只能自己创建自己的实例
- 单例类必须给其他对象提供这一实例
基本创建
单例模式最简单的创建方法是先定义一个 static 全局变量用于保存已创建的 Singleton 对象。每次需要获取该实例时,程序先判断,该 static 变量是否为 nil,如果该全局变量为 nil,则初始化一个实例并赋值给 static 全局变量。
这里只展示此方法下 Singleton 类的实现部分:
static id _instance = nil; //定义static全局变量
@implementation Singleton
+ (id) shareInstance { // 先判断_instance是否为空 if (_instance == nil) { // 为空则初始化 _instance = [[self alloc] init]; } // 返回实例 return _instance;}
@end### 使用dispatch_once
上面的方法在单线程下可以保证单例只被初始化一次;但在多线程环境中,如果多个线程在初始化完成前同时访问,可能各自创建并返回不同实例。
苹果提供了 dispatch_once(dispatch_once_t *predicate,dispatch_block_t block);函数来避免这个问题,该函数保证相关的块必定会执行,且执行一次。此操作完全是线程安全的。
同样只有实现部分代码:
```objective-cstatic id _instance = nil; //定义static全局变量
@implementation Singleton
+ (id) shareInstance { // static修饰标记变量 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [[self alloc] init]; }); return _instance;}
@end### 我们还需要 重写alloc函数 ,使alloc函数返回同一个对象地址。
```objective-c#import "Singleton.h"
@implementation Singleton
// 懒汉式线程安全单例+ (instancetype)sharedInstance { static Singleton *instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[super allocWithZone:NULL] init]; }); return instance;}
// 防止通过 alloc/init 创建新实例+ (instancetype)allocWithZone:(struct _NSZone *)zone { return [self sharedInstance];}
// 防止 copy- (id)copyWithZone:(NSZone *)zone { return self;}
// 防止 mutableCopy- (id)mutableCopyWithZone:(NSZone *)zone { return self;}
@end### 至于两种不同模式:
**懒汉模式**:
· 优点: **延迟加载**:懒汉模式只有在第一次访问单例实例时才会进行初始化,可以节省资源,提高性能,因为实例只有在需要时才会被创建。 **节省内存:**如果单例对象很大或者初始化过程开销较大,懒汉模式可以避免在程序启动时就创建不必要的对象。 **线程安全性:**可以通过加锁机制(如双重检查锁定)来实现线程安全。
· 缺点: **线程安全性开销:**懒汉模式在实现线程安全时可能需要额外的同步机制,这会引入一些性能开销。 **复杂性增加:**实现线程安全的懒汉模式可能需要编写复杂的代码,容易引入错误。 缺点: **线程安全性开销:**懒汉模式在实现线程安全时可能需要额外的同步机制,这会引入一些性能开销。 **复杂性增加:**实现线程安全的懒汉模式可能需要编写复杂的代码,容易引入错误。
**饿汉模式:**
**优点:** **简单:**饿汉模式实现简单,不需要考虑线程安全问题,因为实例在类加载时就已经创建。 **线程安全性:**由于实例在类加载时创建,不会存在多个实例的风险,因此线程安全。
**缺点:** **无法实现延迟加载:**饿汉模式在程序启动时就创建实例,无法实现延迟加载,可能会浪费资源,特别是当实例很大或初始化开销较大时。 **可能引起性能问题:**如果单例类的实例在程序启动时没有被使用,那么创建实例的开销可能是不必要的。 不适用于某些情况:如果单例对象的创建依赖于某些外部因素,而这些因素在程序启动时无法确定,那么饿汉模式可能不适用。
总的来说,懒汉模式适用于需要延迟加载实例的情况,可以节省资源和提高性能,但需要考虑线程安全性。饿汉模式适用于需要简单实现和线程安全性的情况,但不支持延迟加载。选择哪种模式应根据具体需求和性能考虑来决定。
## 重写description方法
当 `NSLog` 以 `%@` 打印对象时,会通过对象的 `description` 文本进行格式化;NSObject 默认描述通常包含类名和地址信息,并非固定只返回十六进制地址。需要自定义调试输出时,可以重写 `description`。
```objective-c@implementation Person
- (NSString *)description { return [NSString stringWithFormat:@"Person: name = %@, age = %ld", self.name, (long)self.age];}
@end== 与 isEqual 与 hash
比较方式 方法 / 符号 作用 是否可重写 比较内容 指针比较 == 判断两个对象地址是否相同 不可重写 内存地址 内容比较 isEqual: 判断两个对象内容是否相等 通常需重写 对象内容 哈希值比较 hash 通常与 isEqual: 搭配使用 通常需重写 内容生成的整数
isEqual 默认是实现是 ==(也就是地址比较)因此,如果你定义自己的类,** 要比较内容,必须重写该方法 **
@implementation Person
- (BOOL)isEqual:(id)object { if (![object isKindOfClass:[Person class]]) return NO; Person *other = (Person *)object; return [self.name isEqualToString:other.name] && self.age == other.age;}hash:对象的哈希值,用于快速查找和集合操作。默认 NSObject 会返回一个基于地址的 hash。当你重写了 isEqual,必须配套重写 hash
下面举几个例子:
@interface Person : NSObject@property (nonatomic, copy) NSString *name;@property (nonatomic, assign) NSInteger age;@end
@implementation Person
- (BOOL)isEqual:(id)object { if (![object isKindOfClass:[Person class]]) return NO; Person *other = (Person *)object; return [self.name isEqualToString:other.name] && self.age == other.age;}
- (NSUInteger)hash { return self.name.hash ^ self.age;}
@end@interface Book : NSObject@property (nonatomic, copy) NSString *title;@property (nonatomic, copy) NSString *isbn;@end
@implementation Book
- (BOOL)isEqual:(id)object { if (![object isKindOfClass:[Book class]]) return NO; return [self.isbn isEqualToString:((Book *)object).isbn];}
- (NSUInteger)hash { return self.isbn.hash; // 因为只看 isbn 是否相同}
@end@interface UserAccount : NSObject@property (nonatomic, copy) NSString *username;@property (nonatomic, copy) NSString *email;@end
@implementation UserAccount
- (BOOL)isEqual:(id)object { if (![object isKindOfClass:[UserAccount class]]) return NO; UserAccount *other = object; return [self.username isEqualToString:other.username] && [self.email isEqualToString:other.email];}
- (NSUInteger)hash { return self.username.hash ^ self.email.hash;}
@end特别鸣谢:感谢 iOS 各级学长学姐的博客分享
原文发布于 CSDN:【OC】OC 语言学习 —— 重点内容总结与拓展(上)