OC考核结束咯,我也结束咯
前言
这周进行了OC学习考核,表现不尽人意。时间的拉长并未带来基础的牢固,望未来能够删掉这段话。
1. 属性和成员变量的区别
要区别这两个,我们首先知道这两个是什么:
- 成员变量:指在类实现部分或者接口部分定义的变量,OC中的成员变量都实例变量。主要为内部使用,外部无法访问。
- 属性:属性是通过使用@property关键字来声明的,有自己的setter和getter方法的成员变量,主要用来给外部访问。
由此来看,属性可以说是成员变量的封装,它要求有自己的setter和getter方法,并且可以使用标识符定义。
什么接下来从代码角度加深理解:
@interface GCUser : NSObject
{
int age;//成员变量
}
@property (nonatomic, copy) NSString *name;//属性
@end
上述代码中定义了一个age的成员变量已经name属性。
需要注意的是:成员变量即可在类的.h文件定义,也可在类的.m实现定义。在.h文件中定义的成员变量权限默认为protected,而在.m文件中定义的成员变量的权限则默认为private。
而在.m文件中,对属性需要进行@synthsize的声明,如果没写,系统将会自动生成一个@synthsize声明,其产生的成员变量即为属性名加上下划线。
即:
@synthsize name = _name
2. ==和isEqual的区别
- 先看 == :
使用== 来判断两个变量是否相等时,如果两个变量时基本类型的变量,且都是数值型,则只要两个变量的值相等,==判断就返回真。
对于两个指针类型的变量,他们必须指向同一个对象,即保存的内存地址相同时,==判断才会返回真。
- 再看isEqual:
isEqual方法是NSObject提供的一个实例方法,该方法默认比较对象的地址,即NSObject类的isEqual运行结果与==运算符相同。通常,我们会将isEqual方法进行重写
重写前的isEqual方法其实与 == 无异。但是,以NSString类为例,OC已经在其中重写了isEqual方法,该方法比较字符串的字符序列。如下图代码:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *s1 = [NSString stringWithFormat:@"3g你好!"];
NSString *s2 = [NSString stringWithFormat:@"3g你好!"];
NSLog(@"s2与s3的isEqual方法是否相等:%d",[s1 isEqual: s2]);
// out: 1
}
}
我们可以根据具体要求,重写isEqua方法,具体在之前的博客有写过,就不再赘述。
答:==判断两个基本数值类型的变量时,判断值是否相等,而判断指针类型的变量时,只有保存的指针地址类型相同时,才会返回True。
isEqual重写前与 == 相同,可以根据自定义标准进行重写。
3. 类别和扩展能添加属性和成员变量吗?
答:类别不可添加属性和成员变量,只能添加方法。
扩展可以添加成员变量,属性。但是默认为@private类型,只能在该类中调用。
我们来看类别的源码:
//Category 是表示一个指向类别的结构体的指针,其定义如下:
typedef struct objc_category *Category;struct objc_category {
char *category_name OBJC2_UNAVAILABLE; // 分类名
char *class_name OBJC2_UNAVAILABLE; // 分类所属的类名
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 实例方法列表
struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 类方法列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 分类所实现的协议列表
}
这个结构体主要包含了类别定义的实例方法与类方法,并没有属性列表。
当我们具体实践的时候,会提示如下报错
从而我们得到类别不能添加属性的实质原因:
类中用@property声明属性,编译器会自动帮我们生成成员变量和setter/getter方法,但类别的指针结构体中,根本没有属性列表。所以在分类中用@property声明属性,既无法生成成员变量也无法生成setter/getter方法。
需要注意的是,只用@property声明属性,编译和运行都会通过,但当使用@synthesize合成成员变量和setter/getter方法时,就会产生报错。
//创建扩展
#import "Father.h"
#import <Foundation/Foundation.h>
@interface Father ()
@property (nonatomic , copy) NSString* name;
@end
//这里略去类的接口和实现。
#import <Foundation/Foundation.h>
#import "Father.h"
#import "Father+addName.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Father* a = [[Father alloc] init];
a.name = @"火火";
NSLog(@"打印扩展定义的属性:%@",a.name);
}
return 0;
}
运行后打印出扩展定义的属性
4. NSString的三种类型及其创建方式
先来理解一下标签指针的概念:
内容博客来源在这篇博客中,介绍了什么是标签指针。
我们需要记住,当数据足够小时,直接将内容存放进指针变量的地址。
答:三种类型:__NSCFConstantStr,__NSCFString,NSTaggedPointerString。
- __NSCFConstantStr:常量字符串,是一种编译时常量,储存在字符串常量区,当使用不同字符串对象创建相同的内容是,其对象的内存地址相同,说明是一种单例模式。
- __NSCFString:在运行时创建的一种 NSString 子类,被储存在堆区即动态区。
- NSTaggedPointerString:是不可释放掉的单例常量对象,当字符串内容足够小时,直接将字符串内容储存进字符串指针变量的地址中。
创建方式:
__NSCFConstantStr类型:直接使用@“”进行定义,即可存入常量区,
__NSCFString类型:使用NSString类中提供的字符串初始化的类方法或者实例方法,即可实现。
NSTaggedPointerString类型:在使用字符串初始化的类方法或者实例方法是,需要初始化的对象内容足够小。
代码示例:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *a = @"hello";
NSString *b = @"hello,iOS!";
NSString *c = [NSString stringWithFormat:@"hello"];
NSString *d = [NSString stringWithFormat:@"hello,iOS!"];
NSString *e = [NSString stringWithFormat:@"嗨"];
NSLog(@"%p %@ \n %p %@ \n%p %@\n %p %@ \n%p %@", a, [a class], b, [b class] , c, [c class], d, [d class], e, [e class]);
}
return 0;
}
代码运行结果:
5. isMemberOf和isKindOf有什么区别
答:
从使用层面来看:
isKindOf:
- 类调用:从类的ISA指针开始,判断当前元类是否为目标,不是则判断父类的元类继承链。(superclass的ISA指针继承链)。
- 元类调用:因为元类的ISA指针指向根元类,即从 RootMetaClass 开始比较判断是不是我们传入的cls,如果不是,再看根类NSObject,如果NSObject也不是,返回false。
- 对象调用:判断cls是不是类对象继承链上的一个。即从对象的类开始判断,不是则判断类的父类,直到NSObject。
isMemberOf:
- 类调用:判断当前类的ISA指针指向的元类与cls是否相同。
- 元类调用:判断当前元类的ISA指针指向的根元类与cls是否相同。
- 对象调用:判断当前的对象的ISA指针指向的类与cls是否相同。
注意:isMemberOf只做一次判断,而isKindOf是判断一个继承链上的内容。
6. 深浅拷贝,容器类的完全深拷贝的两种方法
答:
深浅拷贝的特点:浅拷贝是创建一个副本,进行指针拷贝,拷贝指针地址。深拷贝是创建一个副本,进行内容拷贝,用完全不同的区域存放内容。
对于非容器类:不可变类的mutable copy为深拷贝,copy为浅拷贝。可变类都为深拷贝,但是使用copy方法时返回一个不可变类型。
对于容器类:基本与非容器类相同,但是由于容器内元素对象始终是指针复制,因此容器内对象都为浅拷贝。
有两种方式实现容器类的完全深拷贝:
- copyItems
- copyItems: 方法是 NSArray 和 NSMutableArray 的一个方法,用于创建一个新的数组,并对数组中的元素进行拷贝。该方法会遍历原始数组的每个元素,并对每个元素执行 copy 操作,然后将拷贝后的元素添加到新的数组中。
- 注意:当容器类对象也为容器类时,使用该方法无法进行深拷贝,对容器内元素依旧是浅拷贝。 - 解档与归档
具体例子:
通过使用copyItems: 方法,来实现容器内元素的深拷贝。
#import <Foundation/Foundation.h>
@interface LYUser : NSObject
@property (nonatomic, strong) NSMutableArray* array1;
@end
#import "LYUser.h"
@implementation LYUser
@synthesize array1;
- (id) init {
if (self = [super init])
{
array1 = [[NSMutableArray alloc] init];
}
return self;
}
- (id) copyWithZone: (NSZone*) zone
{
LYUser* t = [[LYUser allocWithZone:zone] init];
t.array1 = self.array1;
return t;
}
@end
#import <Foundation/Foundation.h>
#import "LYUser.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
LYUser* a = [[LYUser alloc] init];
[a.array1 addObjectsFromArray:@[@"胡桃", @"火狗", @"哈迪"]];
NSArray* b = [NSArray arrayWithObject:a];
NSArray* d = [b copy];
NSArray* e = [[NSArray alloc] initWithArray:b copyItems:YES];
NSLog(@"b[0]:%p",b[0]);
NSLog(@"d[0]:%p",d[0]);
NSLog(@"e[0]:%p",e[0]);
NSLog(@"数组位置");
NSLog(@"%p",b);
NSLog(@"%p",d);
NSLog(@"%p",e);
NSLog(@"----------");
}
return 0;
}
上述代码尝试了使用copyItems:方法来实现数组完全深拷贝。代码运行结果如下:
我们可以看出,直接使用copy方法进行的是浅拷贝,数组元素也是浅拷贝。
而使用copyItems:方法后,对数组进行的是深拷贝,数组元素也是深拷贝,因此实现了完全的深拷贝。
解档与归档:
上述方法虽然好用,但是只在容器类装的是非容器类时可行,当容器类装的也是容器类时,则无法实现完全深拷贝。此时就需要使用解档与归档来进行完全的深拷贝。
下面给出等容器类中的元素也为容器类时,使用copyItems:方法的结果
#import <Foundation/Foundation.h>
#import "LYUser.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSArray *value1 = @[@"胡桃", @"火狗", @"哈迪"];
NSArray *value2 = @[@"刘炼", @"殷紫萍", @"妖呆姬"];
LYUser* a = [[LYUser alloc] init];
[a.array1 addObject:value1];
[a.array1 addObject:value2];
NSArray* b = [NSArray arrayWithObject:a];
NSArray* d = [NSArray arrayWithObject:b];
NSArray* e = [[NSArray alloc] initWithArray:d copyItems:YES];
NSLog(@"b[0]:%p",b[0]);
NSLog(@"d[0]:%p",d[0]);
NSLog(@"e[0]:%p",e[0]);
}
return 0;
}
下图为运行结果:
结果显示此时对数组元素为浅拷贝。
下面使用解档与归档的方法实现完全深拷贝:
#import <Foundation/Foundation.h>
#import "LYUser.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSArray *value1 = @[@"胡桃", @"火狗", @"哈迪"];
NSArray *value2 = @[@"刘炼", @"殷紫萍", @"妖呆姬"];
LYUser* a = [[LYUser alloc] init];
[a.array1 addObject:value1];
[a.array1 addObject:value2];
LYUser* b = [a copy];
NSArray *d = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:a.array1]];
NSLog(@"b[0][0]:%p", a.array1[0]);
NSLog(@"d[0][0]:%p", d[0][0]);
NSLog(@"%@", a.array1[0][0]);
NSLog(@"%@", d[0][0]);
}
return 0;
}
运行结果如下:
总结
1.属性与成员变量的区别:
属性相当于对成员变量的封装,有自己的getter和setter方法,可以使用标识符。
2.==和is Equal:的区别:
二者在NSObject类中没有区别,但是在具体的如NSString类中,已经重写了isEqual:方法,并且可以根据自己的需求自定义isEqual:方法。
3.类别与扩展能添加属性和成员变量吗?
类别不可以添加属性和成员变量,根本原因是在类别的结构体中,没有属性列表,无法实现setter和getter方法,并且不能够生成成员变量。
4.NSString的三种类型及其创建方式:
三种类型:__NSCFConstantStr,__NSCFString,NSTaggedPointerString。
第一个__NSCFConstantStr类为常量型,直接使用@“”创建,引用计数很大,无法释放。
第二种__NSCFString类是在运行时创建的一种 NSString 子类,被储存在堆区即动态区。使用实例或者类方法进行创建即可创建该类。
第三种NSTaggedPointerString为标签字符串,当使用实例或类方法创建的字符串内容足够小时,直接将字符串内容储存进字符串指针变量的地址中
5.isMemberOf和isKindOf的区别:
isKindOf是对继承链上每个isa指针指向的元类进行多次判断,而isMemberOf只对当前类上的isa指针进行一次判断。具体不在赘述。
6.深浅拷贝以及对容器类完全深拷贝的两种方式:
通过实践得出,使用NSArray* a定义的数组,不管使用类方法,实例方法,还是常量方法初始化,创建出来的元素都储存在静态区,因此需要自定义一个类来实现储存进动态区。
而对容器类装着非容器类时,可以使用 copyItems 方法来进行完全的深拷贝,而当容器类内装着容器类时,需要使用解档与归档的方法来进行完全的深拷贝,目前对解档和归档也只是仅会使用。
实际答题情况很不好,对于该学习的内容并未做好答好。下来再把容器类的完全深拷贝仔细学习一遍,把NSString类重新抄一遍。好好学习吧。