object-c 经典实用面试题+自己总结的答案(3)

object-c 经典实用面试题+自己总结的答案(3),第1张

11. 参数传入 NSError ** 12. NSObject 有没有父类。他的 super class 指向哪。isa 指向那。*

NSObject没有父类,他的super class 指向nil.isa指针指向自己.
OC中每个类中都包含一个isa变量,显然这里的isa是指向另一个类的指针,说白了就是表明这个类是哪个类的实例,以便找到代码中调用的本类或父类的类方法。对于NSObject及其子类,指向的就是它的元类,正如实例中也有个isa指针指向其所属的类一样。而对于元类,每个元类的isa都指向根元类。那么根元类的isa指向哪里?——它自己。这样就构成一个封闭的循环,实现了无懈可击的OC类系统。这种关系在下面的图中有清晰的体现。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wbA7A0oc-1649150121073)(http://www.kaotop.com/file/tupian/20220519/1361289384_8487.PNG)]

13. 有几种 Block。ARC 下有几种,MRC 下有几种。

简单分为三种来用:

像函数一样定义和使用,,不同于函数的是可以定义在方法内也可以定义在方法外定义成property的属性来使用用作修饰词
接下来一样样的来展示; 1. 像函数一样定义和使用,不同于函数的是可以定义在方法内也可以定义在方法外 1)在方法中使用
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
      NSString *(^thisBlock) (NSString *thisName) = ^(NSString *name){
        return [NSString stringWithFormat:@"%@:%@",@"name",name];
    };
    NSLog(@"%@",thisBlock(@"xiaoming"));
}
2)在方法外使用,就像定义一个方法一样
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    BlockVoid(24);


}
void (^BlockVoid) (int age) = ^(int xiaomingAge){
    NSLog(@"xiaomingAge:%d",xiaomingAge);
};

总结: 上面俩block也是block的一种写法不过这么用的貌似不多见,大多时候这么写反而显得麻烦了(从这里可以看出,block可以定义在方法内部也可以定义在方法外部,在外部的时候看着是不是更像函数了)

2. 定义成property的属性来使用
/**
 常规无返回值有参数block,比较常用
 */
@property(nonatomic,copy)void (^myBlock) (NSString *name,int age);

/**
 常规有返回值有参数block,使用起来其实和上面那个差别不大
 */
@property(nonatomic,copy)NSString *(^haveReturnBlock) (NSString *name);

//用法
- (void)test2
{
    _myBlock(@"CodeLiu",24);
}

- (NSString *)test3
{
    return _haveReturnBlock(@"小花");
}

除了这个用法,还有一种很好玩的用法,看着比较生动,其实是一样的:

#import 

/**
 定义一个block

 @param returnContent 返回值
 */
typedef void(^RetureContentBlock)(id returnContent);

@interface LHBlockForUse : NSObject

/**
 用定义的block来声明一个变量
 */
@property(nonatomic,copy)RetureContentBlock returnBlock;

@end

//用法
- (void)test1
{
    _returnBlock(@"You can put every type in here");
}

3.用作修饰词

在Block内是不能修改Block外的变量的,如果要修改就需要用__block进行修饰

__block int lastAge = 24;
    void (^lastAgeBlock) (int age) = ^(int addAge){
        lastAge = lastAge + addAge;
        NSLog(@"xiaomingLastAge:%d",lastAge);
    };
lastAgeBlock(1);
14. __block 如何实现。

我们都知道:Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。

__block修饰符是如何做到修改变量值的

如果把val变量加上__block修饰符,编译器会怎么做呢?

    //int val = 0; 原代码
    __block int val = 0;//修改后的代码

编译后的代码:

struct __Block_byref_val_0 {
    void *__isa;
    __Block_byref_val_0 *forwarding;
    int __flags;
    int __size;
    int val;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0 *Desc;
    __Block_byref_val_0 *val;

    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,__Block_byref_val_0 *_val, int flags=0) : val(_val->__forwrding){
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
}
};

struct void __main_block_func_0(struct __main_block_impl_0 *__cself){
    __Block_byref_val_0 *val = __cself->val;
    printf("val = %d",val->__forwarding->val);
}

但是关键在于__main_block_impl_0结构体中的这一行:

    __Block_byref_val_0 *val;

由于__main_block_impl_0结构体中现在保存了一个指针变量,所以任何对这个指针的 *** 作,是可以影响到原来的变量的。

进一步,我们考虑截获的自动变量是Objective-C的对象的情况。在开启ARC的情况下,将会强引用这个对象一次。这也保证了原对象不被销毁,但与此同时,也会导致循环引用问题。

需要注意的是,在未开启ARC的情况下,如果变量附有__block修饰符,将不会被retain,因此反而可以避免循环引用的问题。

总结

在本文的开头,提出两个简单的问题,如果你不能从根本上弄懂这两个问题,那么希望你阅读完本文后能有所收获。

为什么block中不能修改普通变量的值?__block的作用就是让变量的值在block中可以修改么?
如果有的读者认为,问题太简单了,

而且你的答案是:

(因为编译器会有警告,各种教程也都说了不能修改。
应该是的吧。)

回到开篇的两个问题,答案应该很明显了。

由于无法直接获得原变量,技术上无法实现修改,所以编译器直接禁止了。

都可以用来让变量在block中可以修改,但是在非ARC模式下,__block修饰符会避免循环引用。注意:block的循环引用并非__block修饰符引起,而是由其本身的特性引起的。

block是什么?

很多教程、资料上都称Block是“带有自动变量值的匿名函数”。这样的解释显然是正确的,但也是不利于初学者理解的。
block参考链接

15. weak 的实现原理

很少有人知道weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址数组。更多人的人只是知道weak是弱引用,所引用对象的计数器不会加一,并在引用对象被释放的时候自动被设置为nil。通常用于解决循环引用问题。但现在单知道这些已经不足以应对面试了,好多公司会问weak的原理。weak的原理是什么呢?下面就分析一下weak的工作原理(只是自己对这个问题好奇,学习过程中的笔记,希望对读者也有所帮助)。

weak 实现原理的概括

在 runtime 阶段,对于 weak 变量而言,系统使用 hash 表进行管理,将weak 变量所指向的地址作为 hash 表的 key,当某个对象的引用计数为零的时候,系统根据对象的内存地址找到所有指向该对象的 weak 变量,并将这些 weak 变量置为 nil

Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。

weak 的实现原理可以概括一下三步:

初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。

添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。

释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?当释放对象时,其基本流程如下:

调用objc_release

因为对象的引用计数为0,所以执行dealloc

在dealloc中,调用了_objc_rootDealloc函数

在_objc_rootDealloc中,调用了object_dispose函数

调用objc_destructInstance

最后调用objc_clear_deallocating

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/996443.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存