iOS 基础读书杂集(二)

没有废话直接来,传送门:

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: '[ 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 *)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
     被修改的对象为:
     新被修改的属性值为:kobe
     被修改的上下文为:(null)
     observeValueForKeyPath:方法被调用
     被修改的keyPath为:age
     被修改的对象为:
     新被修改的属性值为: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和 [test description]方法的返回值
    //所以对于我们程序员来说,有时需要自己的自定义类能够详细显示一些信息,我们就可以重写这个方法
    //description:是所有继承自NSObject类都有的方法,所以我们在Test的实现中重写这个方法
    
    //重写description方法
//    -(NSString *)description{
//        return [NSString stringWithFormat:@"Test类-> name = %@",self.name];
//    }
    
    //重写后:打印结果:Test类-> name = 克里斯蒂亚诺-罗
 }

你可能感兴趣的:(iOS 基础读书杂集(二))