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方法时返回一个不可变类型。

对于容器类:基本与非容器类相同,但是由于容器内元素对象始终是指针复制,因此容器内对象都为浅拷贝。

有两种方式实现容器类的完全深拷贝:

  1. copyItems
    - copyItems: 方法是 NSArray 和 NSMutableArray 的一个方法,用于创建一个新的数组,并对数组中的元素进行拷贝。该方法会遍历原始数组的每个元素,并对每个元素执行 copy 操作,然后将拷贝后的元素添加到新的数组中。
    - 注意:当容器类对象也为容器类时,使用该方法无法进行深拷贝,对容器内元素依旧是浅拷贝。
  2. 解档与归档

具体例子:

通过使用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类重新抄一遍。好好学习吧。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值