没有废话直接来,传送门:
iOS 基础读书杂集(一)
NO.11 处理KVC:setValue赋值时,给属性赋值nil的问题
当我们给引用数据类型赋值nil,不会出现问题,但是给int类型呢?
举例:
@interface JJTest : NSObject
{
//定义两个属性,一个string,一个int
NSString *_name;
NSInteger _age;
}
- (void)viewDidLoad {
[super viewDidLoad];
JJTest *test = [[JJTest alloc]init];
//给这两个属性都赋值为nil
[test setValue:nil forKey:@"_name"];
[test setValue:nil forKey:@"_age"];
NSLog(@"name = %@",[test valueForKey:@"_name"]);
NSLog(@"age = %@",[test valueForKey:@"_age"]);
}
//崩溃报错:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[<JJTest 0x600000030e60> setNilValueForKey]: could not set nil as the value for the key _age.'
//报错是因为:给基本数据类型赋值nil.
//解决方法:
上面的报错信息是因为在程序尝试给一个属性赋值为nil的时候,该属性又不接受nil值,那么程序会自动调用setNilValueForKey:方法
那么我们可以重写这个方法来解决这个崩溃问题
//重写setNilValueForKey:方法
-(void)setNilValueForKey:(NSString *)key{
//如果尝试将key为_age的属性设置为nil
if ([key isEqualToString:@"_age"]) {
//那么将_age直接设置为0
_age = 0;
}else{
//其它情况依然回调父类的方法
[super setNilValueForKey:key];
}
}
//再次运行打印:
name = (null)
age = 0
NO.12 KVO的简单模拟实现
使用KVO的步骤:
1.为被监听对象(通常是数据模型组件)注册监听器
2.监听对象重写observeValueForKeyPath:ofObject:change:context方法
举例:
@interface JJTest1 : NSObject -- 相当于模型类
/**<#注释#>*/
@property (nonatomic , copy) NSString *name;
/**<#注释#>*/
@property (nonatomic , assign) NSInteger age;
@end
@class JJTest1;
@interface JJTest : NSObject -- 相当于Cell类
//在这里JJTest类相当于一个视图View. JJTest1相当于一个模型数据类
/**test1*/
@property (nonatomic , weak) JJTest1 *test1;
//这个方法用于显示Model对象的状态
-(void)showModelInfo;
@end
#import "JJTest.h"
#import "JJTest1.h"
@implementation JJTest
//展示模型数据
-(void)showModelInfo{
NSLog(@"test1的名称为%@,年龄为%ld",self.test1.name,self.test1.age);
}
//重写setTest1方法
//监听器的设置添加时机:当JJTest对象对test1属性设置值的时候,添加监听器
-(void)setTest1:(JJTest1 *)test1{
_test1 = test1;
//为test1添加监听器,监听test1的name属性的改变
//理解为:test1对象你的name属性被这个JJTest对象监听了,只要你这个name属性改变了。下面的observeValueForKeyPath:方法就会被调用
[self.test1 addObserver:self forKeyPath:@"name" options: NSKeyValueObservingOptionNew context:nil];
//监听test1的age属性改变
[self.test1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
}
//重写该方法,当被监听的数据模型发生改变时,就会回调监听器的该方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"observeValueForKeyPath:方法被调用");
//获取修改时所设置的数据
NSLog(@"被修改的keyPath为:%@",keyPath);
NSLog(@"被修改的对象为:%@",object);
NSLog(@"新被修改的属性值为:%@",[change objectForKey:@"new"]);
NSLog(@"被修改的上下文为:%@",context);
}
-(void)dealloc{
//移除监听器
[self.test1 removeObserver:self forKeyPath:@"name"];
[self.test1 removeObserver:self forKeyPath:@"age"];
}
//使用:
- (void)viewDidLoad {
[super viewDidLoad];
//创建JJTest1对象
JJTest1 *test1 = [[JJTest1 alloc]init];
//设置test1的属性值
test1.name = @"tmac";
test1.age = 18;
//解读:以上就相当于这个在cell中通过 JJModel *model = self.modelArr[indexPath.row];获取到了对应cell行的模型数据
//创建JJTest对象
JJTest *test = [[JJTest alloc]init];
//将test的test1属性设置为test1
test.test1 = test1;
//解读:这个就相当于 cell.model = model.现在在这一瞬间我们还给model的每个属性添加了监听器
//调用方法展示一下test1中的东西
[test showModelInfo];
//再次更改test1对象的属性,将会激发监听器方法
test1.name = @"kobe";
test1.age = 24;
/*
observeValueForKeyPath:方法被调用
被修改的keyPath为:name
被修改的对象为:<JJTest1: 0x60000003f9a0>
新被修改的属性值为:kobe
被修改的上下文为:(null)
observeValueForKeyPath:方法被调用
被修改的keyPath为:age
被修改的对象为:<JJTest1: 0x60000003f9a0>
新被修改的属性值为:24
被修改的上下文为:(null)
*/
}
NO.13 对象初始化
Objective-C的初始化是分为两步
alloc:分配内存空间
init:初始化对象
Java: new xxx();构造器是一样的原理,只不过两步合成为一步了
NO.14 便利初始化
//原有初始化方法
-(id)init;
//便利初始化方法
-(id)initWithName:(NSString *) age:(NSInteger)age;
NO.15 重写父类方法
1.子类重写父类方法,直接在类实现里重写父类方法的实现部分
2.通过super关键字可以调用父类的实例方法
//父类方法
-(void)fly{
NSLog(@"飞起来了");
}
//子类重写父类方法]
-(void)fly{
NSLog(@"我不会飞啊!");
}
//子类中单独写个方法,可以在里面用Super调用被覆盖的父类方法
-(void)fatherMethod{
[super fly];
}
NO.16 子类成员变量问题
1.由于子类继承自父类,会获得父类中所有的成员变量。所以
子类接口部分不允许定义与父类接口部分重名的成员变量
2.类实现中定义的成员变量不受影响
NO.17 多态
概述: Objective-C 指针类型的变量有两个: 一个是编译时的类型,一个是运行时的类型。
编译时的类型由声明该变量时使用的类型决定
运行时的类型由实际赋给该变量的对象决定
如果编译时类型和运行时类型不一致,就可能出现所谓的多态
NO.18 多态简单演示
//定义一个父类
@interface JJFather : NSObject
//在父类定义两个方法
-(void)method1;
-(void)method2;
@end
@implementation JJFather
//实现这个两个方法
-(void)method1{
NSLog(@"父类的普通method1方法");
}
-(void)method2{
NSLog(@"父类即将被覆盖的method2方法");
}
@end
//搞一个子类继承JJFather
//继承自JJFather
@interface JJSon : JJFather
//子类自己的一个方法
-(void)sonMethod;
@end
@implementation JJSon
//子类独有的方法
-(void)sonMethod{
NSLog(@"子类独有的方法");
}
//子类覆盖父类的方法
-(void)method2{
NSLog(@"子类覆盖了父类的method2方法");
}
@end
//多态演示:
- (void)viewDidLoad {
[super viewDidLoad];
//演示多态
//1.下面编译时类型和运行时类型完全一致,不存在多态
JJFather *father = [[JJFather alloc]init];
//调用父类自己的方法
[father method1];
[father method2];
//2.下面编译时类型和运行时类型完全一致,不存在多态
JJSon *son = [[JJSon alloc]init];
//分别调用父类的方法和子类自己的方法
[son method1];
[son method2];
[son sonMethod];
//3.下面编译时类型和运行时类型不一致,多态产生
//解释:[[JJSon alloc]init]:创建了一个JJSon的对象,然后由父类类型的引用指向这个对象
//其实按照Java的说法是父类引用指向子类对象,进行了一次向上转型
JJFather *fatherSon = [[JJSon alloc]init];
//调用方法
//(1).
[fatherSon method1];//调用从父类继承的方法
//(2).
[fatherSon method2];//调用覆盖父类的方法
//(3).调用子类特有的方法是不行的
//[fatherSon sonMethod];
/**解释:
对于(2):编译时会看JJFather的类型,从而找到method2方法的声明。
当运行时,总是看赋值对象的类型,所以运行时会找到子类重写父类后的method2方法的实现
对于(3).你就发现在fatherSon指针变量调用这个sonMethod时,编译时在JJFather中找不到该方法的声明,所以直接会报编译错误。
这就是多态...
**/
}
//打印结果:
test1[899:35039] 父类的普通method1方法
test1[899:35039] 父类即将被覆盖的method2方法
test1[899:35039] 父类的普通method1方法
test1[899:35039] 子类覆盖了父类的method2方法
test1[899:35039] 子类独有的方法
test1[899:35039] 父类的普通method1方法
test1[899:35039] 子类覆盖了父类的method2方法
NO.19 Objective-C 包装类
基本概念不做过多概念,我们只说注意点
1. NSInteger / NSUInteger / CGFloat
这三个并不是包装类,依然是基本数据类型。 在64位平台和类似于64位平台的各种平台上,NSInteger-> long, NSUInteger-> unsigned long, CGFloat-> double. 所以为了更好的兼容不同的平台,当需要定义整型变量的时候,用这三个
2. Objective-C虽然提供了类似于自动装箱的机制,把int型等直接赋值给NSNumber变量。但机制并不完善,使用自动装箱生成的NSNumber不支持ARC. 因此通常建议显式将基本类型的值包装成NSNumber对象
基本类型变量-> [NSNumber numberWihtXxx:值]-> 包装类对象
包装类对象-> [NSNumber对象 xxxValue]-> 基本类型变量
3.为什么需要将基本类型包装为类对象:
只有通过包装才能将基本类型放入数组,集合中.
NO.20 重写description方法
//创建一个测试类
@interface Test : NSObject
/**姓名*/
@property (nonatomic , copy) NSString *name;
//便利构造
-(instancetype)initWithName:(NSString *)name;
@end
@implementation Test
//便利构造
-(instancetype)initWithName:(NSString *)name{
if (self = [super init]) {
self.name = name;
}
return self;
}
@end
//演示:
- (void)viewDidLoad {
[super viewDidLoad];
Test *test = [[Test alloc]initWithName:@"克里斯蒂亚诺-罗"];
NSLog(@"打印结果:%@",test);
//打印结果:<Test: 0x608000001f90>
//我们解读这个打印:
//上面的结果是<Test: 16进制的首地址>
//那么上面是怎么来的,其实我们直接打印test和 [test description]方法的返回值
//所以对于我们程序员来说,有时需要自己的自定义类能够详细显示一些信息,我们就可以重写这个方法
//description:是所有继承自NSObject类都有的方法,所以我们在Test的实现中重写这个方法
//重写description方法
// -(NSString *)description{
// return [NSString stringWithFormat:@"Test类-> name = %@",self.name];
// }
//重写后:打印结果:Test类-> name = 克里斯蒂亚诺-罗
}
网友评论