iOS 问题集锦及答案

1.#import和#include的区别,@class代表什么?

import会代入头文件的所有信息,包括实体百年来那个和方法等,
include也是带入文件的所有信息,跟#import差异是在#import引用的文件只会被引用一次,不会递归包含的问题
@class代表的意思是,不需要导入文件内容,也不需要知道如何定义的,我只告诉编译器这个就是个类的名称,

2.谈谈Objective-C的内存管理方式和过程?

在App运行时创造了大量的对象,Objective-C中的对象时存储在堆中的,系统并不会自动释放堆中的内存(基本类型也就是值类型是由系统自己管理的,放在栈上),so,ObjC的内存管理是需要开发去手动维护的。

一、引用计数

在Xcode4.2以后的版本中都是引入了ARC机制,程序编译时xcode可以自动给你的代码添加内存释放代码,但是如果编写释放内存的代码就会报错,所以需要在xcode中主动关闭ARC,这样才能有助于ObjC的理解。

在ObjC中,每个对象内部都会有一个retainCount整数,叫"引用计数",当一个对象创建之后它的引用计数为1,当调用这个对象的alloc、retain、new、copy方法之后引用计数会在动在原有的基础上加1,当调用这个对象的release方法之后它的引用计数会减1,如果一个对象的引用计数为0,则系统会自动调用这个对象的dealloc方法来销毁这个对象,在手动管理内存的时候需要遵循一个原则谁创建、谁释放。

当用户调用方法的时候实际上是在向这个对象发送一条消息,且ObjC中允许向一个nil的对象发送消息,在释放完成以后最好直接给这个对象设置为nil,为了防止野指针的出现,当这个对象的引用计数为0的时候,系统会自动调用dealloc方法来销毁对象,可以在这里将所有的野指针设置为nil。

在手动使用的时候有可能会遇到野指针问题,比如下面例子:

    ************************************************************
    /***   DogModel.h ***/
    ************************************************************
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    @interface DogModel : NSObject
    
    @property(nonatomic,copy)NSString *dogName;
    
    -(void)doing;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    ************************************************************
    /***   DogModel.m ***/
    ************************************************************
    #import "DogModel.h"
    @implementation DogModel
    -(void)doing{
        NSLog(@"%@ 会汪汪汪!",self.dogName);
    }
    -(void)dealloc{
        NSLog(@"DogModel = (%@)dealloc method.",self.dogName);
        [super dealloc];
    }
    @end
    
    
    ************************************************************
    /***   UserModel.h ***/
    ************************************************************
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @class DogModel;
    
    @interface UserModel : NSObject
    
    @property(nonatomic,copy)NSString *UserName;
    
    - (void)setDog:(DogModel *) dog;
    -(DogModel*)Dog;
    
    @end
    
    NS_ASSUME_NONNULL_END
    ************************************************************
    /***   UserModel.m ***/
    ************************************************************
    #import "UserModel.h"
    
    @interface UserModel ()
    {
        DogModel *_Dog;
    }
    @end
    
    
    @implementation UserModel
    
    
    - (void)setDog:(DogModel *)dog{
         _Dog = dog;  
    }
    
    -(DogModel*)Dog{
        return _Dog;
    }
    
    -(void)dealloc{
        NSLog(@"UserModel = (%@)dealloc method.",self.UserName);
        [_Dog release];
        _Dog = nil;
        [super dealloc];
    }
    
    @end

    ************************************************************
    /***   调用代码 ***/
    ************************************************************
    UserModel *user = [[UserModel alloc]init];
    user.UserName = @"struggle";
        
    DogModel *dog=[[DogModel alloc]init];
    dog.dogName=@"小黑";
        
    user.Dog=dog;
        
    [dog release];
    dog=nil;
    
    [user.Dog doing];    
    [user release];
    user = nil;

抛出异常

Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
经过调试发现是野指针的问题,主要是下面两段代码出的问题。

/***   调用代码 ***/
DogModel *dog=[[DogModel alloc]init];
dog.dogName=@"小黑";
user.Dog=dog;
[dog release];
dog=nil;
/***   UserModel.m ***/
- (void)setDog:(DogModel *)dog{
     _Dog = dog;  
}

当创建一个新的DogModel的实例变量 dog直接赋值给了user.Dog其真正目的只是给了user.Dog一个指针,user.Dog跟dog对应的内存空间是一样的。并且retainCount的值时1,当[dog release]后,retainCount = 0,这个对象也就释放掉了,而user.Dog实际上已经是一个野指针了。

解决方案
/***   UserModel.m ***/
- (void)setDog:(DogModel *)dog{
     if(_Dog != dog){
        [_Dog release];
        _Dog = [dog retain];
     }
}

这个方法,跟property中的(retain)是一样的

二、属性参数

@property自动实现你的属性的getter、setter方法,并且提供一些参数供选择

参数 详解
atomic 对属性加锁,多线程下县城安全,默认值
nonatomic 对属性不加锁,多线程不安全,但是速度快
readwrite 生成getter、setter方法,可读可写,默认值
readonly 只生成getter方法,只读
assign 直接赋值,默认值
weak 相当于assign,多了一点就是对象被干掉时,weak引用自动设置为nil
retain 先release原来的值,再retain新值
Strong 跟retain一样,在ARC中,不需要手动释放内存
copy 先release原来的值,在copy新值

assign,setter方法

-(void)setNamenum:(int)num{
    _Namenum=num;
}

retain,setter方法

-(void)setNamenum:(calss*)num{
    if(_Namenum != num)
    [_Namenum release];
    _Namenum = [num retain];
}

copy,setter方法

-(void)setNamenum:(calss*)num{
    if(_Namenum != num)
    [_Namenum release];
    _Namenum = [num copy];
}

三、自动释放池

在ObjC中还有一种自动释放机制, 使用的时候首先用@autoreleasepool关键字声明一个代码块,在代码块中初始化你的对象,如果对象在初始化的时候加入了autorelease方法,那么在这个代码块执行完成以后,在块中只要调用过autorelease方法的对象都会自动调用release方法。这就是自动释放池,这样release方法是一起呗调用的,可能有点用处。

    @autoreleasepool {
        UserModel *user1 = [[[UserModel alloc]init]autorelease];
        user1.UserName = @"用户1";
        
        UserModel *user2 = [[[UserModel alloc]init]autorelease];
        user2.UserName = @"用户2";
        
    }

打印结果

2019-01-25 23:26:53.031860+0800 MRC_demo[3264:662385] UserModel = (用户2)dealloc method.
2019-01-25 23:26:53.031967+0800 MRC_demo[3264:662385] UserModel = (用户1)dealloc method.

从结果来看自动释放池是一个队列,先进后出,压栈的形式。

根据理解又实验了2次:

  1. 在autorelease之后user2加上retain,实验证明user2没有被释放,说明autorelease只是调用了一次release
  2. 在autorelease之后user2加上release,程序崩溃Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)野指针,将user2 = nil就会解决,ObjC可以向nil对象发送消息。
自动释放池的总结
  1. autorelease不会改变retaincount的值,只是放入自动释放池
  2. 自动释放池,就是在代码块完成以后,调用autorelease的对象自动调用release方法(只是一次),如果对象的retaincount>1那么就没法释放
  3. 自动释放池是统一销毁,那么在这个代码块中占用的内存会相对来说多一点,可以使用多个自动释放池

3. Objective-C有私有方法吗?私有变量呢?

  • 在ObjC中按照正常的规则来将是有私有方法,跟私有变量的,只要我不在.h文件声明就可以,只是在表面上是这样的,
  • 因为ObjC是一个动态语言,我们可以通过runtime来动态的获取这些property、方法、ivar,得到名称或者直接修改值都是可以的。

代码如下:

/********************************************************/
                       对象类代码
/********************************************************/
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface UserModel : NSObject
- (instancetype)initWithUserName:(NSString *)name;
@end

NS_ASSUME_NONNULL_END


@interface UserModel ()
{
    NSString * userName;
}
@end

@implementation UserModel

- (instancetype)initWithUserName:(NSString *)name{
    
    self = [super init];
    if(self){
        userName = name;
    }
    return self;
}

- (void)doing{
    NSLog(@"这是我的私有方法");
}
@end

/********************************************************/
                       调用私有方法代码
        当然这段代码是将所有的对象都得到然后都屌用一遍
/********************************************************/

     int Count;
     Method *methodList = class_copyMethodList([UserModel class], &Count);
     for (unsigned int i = 0; i < Count; i++) {
         Method method = methodList[i];
         NSLog(@"method --> %@", NSStringFromSelector(method_getName(method)));
        objc_msgSend(user, NSSelectorFromString(method_getName(method)));
     }
     
/********************************************************/
                       调用私有变量代码
            这里是将所有的变量都取出来然后都修改成"我去"
/********************************************************/

    int varcount;
    Ivar *varlist = class_copyIvarList([user class], &varcount);
    
    for (unsigned int i = 0; i < varcount; i++) {
        Ivar ivar = varlist[i];
        object_setIvarWithStrongDefault(user, ivar, @"我去");
        NSLog(@"%@",object_getIvar(user, ivar));
    }

当然runtime还是可以做到很多事情并不仅仅是这些,不过从题目来看已经够充分的说明了ObjC是否存在私有变量跟私有方法了。答案是不存在。

问题待续

猜你喜欢

转载自blog.csdn.net/weixin_34244102/article/details/87039510