iOS里面的多级指针

作者: 妖精的尾巴毛 | 来源:发表于2019-05-30 14:37 被阅读86次

最近面试,碰到一个好玩的公司,出了一个面试题,ta给我了一张纸,让写出NSArray的enumerateObjectsUsingBlock内部怎么实现的。

    NSArray * list = @[@"1",@"2",@"3"];
    [list enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if([obj isEqualToString:@"2"]){
           *stop = YES;
        }
    }];

这题目漂漂亮亮的写出来,起码需要以下要求:
1、对block有点理解,要不然写不出block,哈哈。
2、对指针有点理解,要不然写不出来。
3、对自己足够自信,对苹果sdk内部实现不能有恐惧。

block晚点再说,先看看指针。

1、指针基础:p、*p和&p三者的区别

指针四元素:

指针的类型

你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型

指针所指向的类型

把指针声明语句中的指针名字和名字左边的指针声明符去掉,剩下的就是指针所指向的类型*

指针的值

指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。

指针本身所占据的内存区

指针本身占了多大的内存?用函数sizeof(指针的类型)测一下就知道了,不同位数的机器大小不同

 NSInteger aaa = 3;
 NSLog(@"aaa的内存地址====%p",&aaa); //aaa的内存地址0x111

 NSInteger *bbb = &aaa;
 NSLog(@"bbb变量存的内容====%p",bbb); //这获取的就是示意图中的0x111
 NSLog(@"bbb的内存地址====%p",&bbb); //这获取的就是示意图中的0x222
变量内存示意.png

bbb是一个指针变量的名字,表示此指针变量指向的内存地址,如果使用%p来输出的话,它将是一个16进制数,从上面的结果可以看到打印bbb和&aaa的值是一样

*bbb表示此指针指向的内存地址中存放的内容,一般是一个和指针类型一致的变量或者常量
&是取地址运算符,&bbb就是取指针bbb的地址;

&bbb和bbb的区别在于:指针bbb同时也是个变量,既然是变量,编译器肯定要为其分配内存地址,&bbb就表示编译器为变量bbb分配的内存地址;而因为bbb是一个指针变量,这种特殊的身份注定了它要指向另外一个内存地址,程序员按照程序的需要让它指向一个内存地址,所以bbb表示它指向的内存地址。

2、基本数据类型、对象类型

    char a = 10;
    char *p = &a;
    char value = *p;
    printf("value的值:%d", value);  //输出结果:value的值:10
    NSString *name = @"solo";
    NSLog(@"xxxx的内存地址====%p",name); //下图中solo的内存首地址,也是name指针在内存中存的内容
    NSLog(@"name的内存地址===%p",&name); //name指针的内存地址
    NSLog(@"name的description===%@",name); //name的description

对象类型,内存分布复杂,结构体是一片内存区域。


示意图1.png

NSString本身也是一个对象,它不止是char *这些基本类型这么简单。本质上OC的对象是一个结构体,是一片内存区域,我们并没有方法能直接完整打印出这个结构体。NSLog遇到%@格式和接收对象作为参数时,直接调用的是对象的description方法。这里与基本数据类型的处理是有区别的。

3、iOS中多级指针的应用

指针在iOS中运用十分广泛,只是太频繁没意识到而已,其实每个实例对象都是指针。这里说的应用是指多级指针的运用。下面这俩货我面写代码会经常看到:

    NSArray * list = @[@"1",@"2",@"3"];
    [list enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"enumerateObjectsUsingBlock===%@",obj);
        if([obj isEqualToString:@"2"]){
           *stop = YES;
        }
    }];
    NSError *error = nil;
    [[NSFileManager defaultManager] moveItemAtPath:@"" toPath:@"" error:& error];
    if (error) {
       NSLog(@"move failed:%@", [error localizedDescription]);
    }

为什么这么写能修改函数外面的值?换个有函数实现简单的栗子:

- (void)testBaseData
{
    NSInteger aaa = 3;
    NSLog(@"函数前===%p",&aaa);//函数前===0x7ffeec80fba8
    [self getNewCount:&aaa];
    NSLog(@"aaa2===%ld",aaa);//aaa2===200
}

-(void)getNewCount:(NSInteger *)countaa
{
    NSLog(@"函数里===%p",countaa);//函数里===0x7ffeec80fba8
    *countaa = 200;
}

基本数据类型,会显得比较简单。可以看出函数前和函数里指针一样。
把aaa的内存地址,赋值给了指针countaa,countaa就是函数外面的aaa。修改countaa的值,就是修改aaa变量在内存中存的值。

- (void)showError
{
    NSError *error = nil;
    NSLog(@"函数前==%p", &error);//函数前==0x7ffee06dfd38
    [self handleResponseCode:0 error:&error];
    NSLog(@"函数后值==%@", error);//函数后值==Error Domain=NSCocoaErrorDomain Code=0 "(null)" UserInfo={code=0}
}

- (void)handleResponseCode:(NSInteger)code error:(NSError **)err
{
    NSLog(@"函数里==%p", err);//函数里==0x7ffee06dfd30
    if (code == 0) {
        *err = [NSError errorWithDomain:NSCocoaErrorDomain code:code userInfo:@{@"code":@(code)}];
    }
}

对着下面的内存示意图分析下:

示意图.png

error是个指针,开始指向nil,在调用下面的函数的时候,通过取地符&,把error的内存地址(0x222)赋值给了新的指针变量err。
在函数中,*err就是err指针指向的变量(就是函数外面的error)。修改 *err的指针指向就是修改函数外error的指针指向。
注意:这里函数外的内存地址0x7ffee06dfd38和函数里面0x7ffee06dfd30会有略微不同,是__autoreleasing搞的鬼,暂且忽略,具体看这个吧

结论:我们通过一个指针参数作为桥梁,成功修改了函数外面变量的值。

这么写到底有啥好处?谁也不会没事撑的,搞这么个幺蛾子。

看个栗子:

- (void)testManyParameter
{
    NSString * name = @"solo";
    NSInteger  age = 18;
    NSString * money = @"100W";
    BOOL isRichGuy = YES;
    CGFloat degress = 0.55;

    [self fucNewName:&name age:&age money:&money isRichGuy:&isRichGuy degress:&degress];
    NSLog(@"name====%@",name);
}

-(void)fucNewName:(NSString **)newName
              age:(NSInteger *)age
            money:(NSString **)money
        isRichGuy:(BOOL *)isRichGuy
          degress:(CGFloat *)degress
{
    *newName = @"fuck";
    *age = 20;
    *money = @"2000";
    *isRichGuy = NO;
    *degress = 0.66;
}

看出来了吧,返回参数可以不用放容器里返回,完全可以直接修改函数外面的值,也不用担心NSInteger这些变量不能直接放进容器里面的问题。

4、空指针nil、野指针、僵尸对象

1.空指针值为nil,没有指向。由于iOS中采用的是对调用者发消息,如果消息的接受者为nil,对空指针发任何消息不起任何作用。

    NSObject * object = nil;
    NSLog(@"函数==%p", &object);//函数==0x7ffeeaba0ba8
    NSLog(@"函数==%p", object);//函数==0x0
示意图.png

这里object存储的是0x0,表示object是一个空指针,空指针也是指针,也有四元素。

2.野指针不是nil指针,是指向"垃圾"内存(不可用内存)的指针。当所指向的对象被释放或者收回,但是对该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址,此情况下该指针便称野指针。如用assign修饰对象就出现导致野指针,因为对象创建出来没有任何强指针指向它,所以创建完以后立即会被释放了,这时候指针指向的地方已经不可用,所以就成了野指针。
3.僵尸对象,在OC中,对象被释放后所占用的内存在没有被复写(重新分配给其他对象)前称为僵尸对象,这是野指针是可以访问该内存的,因为对象的数据还在,所以程序不会报错。但是该内存一旦重新分配给其他对象就会出现问题。

最后给出最开始题目的答案: NSArray的enumerateObjectsUsingBlock实现类似下面:

- (void)enumTestBlock:(void (^)(id obj, NSUInteger index, BOOL * stop))enumBlock {
    BOOL stopNow = NO;
    for (int index = 0; index < self.count; index++ ) {
        if (!stopNow) {
            enumBlock(self[index], index, &stopNow);
        } else {
            break;
        }
    }
}

一些自己的理解记录下来,希望没有不对的地方。
参考1:https://www.jianshu.com/p/5b2c7bbc32d6
参考2:https://www.jianshu.com/p/c58e089ba219
参考3:https://blog.csdn.net/wnnvv/article/details/81144219

相关文章

  • iOS里面的多级指针

    最近面试,碰到一个好玩的公司,出了一个面试题,ta给我了一张纸,让写出NSArray的enumerateObjec...

  • C语言多级指针

    多级指针 指针中保存着其他指针的地址,我们就称之为多级指针 多级指针的定义 在要保存的指针变量的基础上加一颗星即可...

  • 多级指针

    多级指针注意引用即可,主要的还是多级指针和动态内存之间的跨函数使用内存

  • 【C语言笔记】<十五>指针

    指针的基本概念 指针的注意点 多级指针 指针为什么分类型

  • C++ supplement 指针进阶及const限定符

    在上一节中说到了基本的指针的定义与解析,下面便是升级版,多级指针的理解: 所谓多级指针在于有多重的指针导向,因为指...

  • 多级指针的本质及推理过程

    多级指针的作用 如果要保存某个指针的地址, 你就会需要用到多级指针. 例如: 普通变量的地址需要用一级指针来保存一...

  • iOS 多级 UIScrollView 嵌套的实现方案

    iOS 多级 UIScrollView 嵌套的实现方案 iOS 多级 UIScrollView 嵌套的实现方案

  • iOS开发笔记-多级指针

    include int main(void){int i = 10;int *p = &i;i...

  • 12 C指针

    1、指针定义 2、指针进阶 3、指针使用 4、多级指针 4、指针和数组 5、指针和字符串 6、高级指针之和指向函数的指针

  • C语言day08-13多级指针

    pragma mark 多级指针 pragma mark 概念 pragma mark 代码

网友评论

    本文标题:iOS里面的多级指针

    本文链接:https://www.haomeiwen.com/subject/nlgftctx.html