深入理解OC和面向对象

·  阅读 1599

一、Property

1.Instance variable与Property有什么区别?
Instance variable只是对象的一个成员变量,@Property还包含了成员变量的读取方法和对成员变量的一些限定操作。
Instance variable默认修饰符是@protected,只能在本类和子类中访问,可以通过编写@getter和@setter方法实现外部对变量进行赋值和取值。 @property默认会自动生成一个_开头的私有成员变量(在.m文件中生成)以及@getter和@setter方法的声明和实现。如果需要对传入的数据进行过滤,必须重写@getter/@setter方法,但若同时重写@getter和@setter方法,@property则不会自动生成一个_开头的私有成员变量。@property只能写在类的声明(@interface)中。

2.OC中有哪些Property Attributes,它们分别代表什么意思?

  1. Atomicity(原子性):有atomic和nonatomic两个值可选,默认值是atomic。atomic是保证读取变量是线程安全的,即它会保证每次getter和setter的操作都会正确的执行完毕,而不用担心其它线程在你get的时候set,可以说保证了某种程度上的线程安全。而nonatomic是不能保证线程安全的。但是nonatomic比atomic速度要快。

  2. Access(存取特性):存取特性有readwrite(默认值)和readonly。定义了这个属性是「只读」,还是「读写」皆可。如果是readwrite,就是告诉编译器,同时生成getter和setter。如果是readonly,只生成getter。

  3. Storage(内存管理特性):strong、weak、assign、copy、retain 、unsafe_unretained 6个attributes。
    strong (默认值)(ARC新增的特性)表明该属性对被赋值对象持有强引用。一个对象只有不被任何指针所强引用(retain count=0),它才会销毁。如果不希望对象被回收,可以使用strong指示符。基本数据类型(非对象类型,如int, float, BOOL)默认值并不是strong,strong只能用于对象类型。
    weak(ARC新增的特性)也会给你一个引用指向对象。但是是弱引用,不会增加retain count。如果对象A被销毁,所有指向对象A的弱引用都会自动设置为nil。常用weak解决strong reference cycles问题。
    assign(MRC特性)它的作用和weak类似,唯一区别是:如果对象A被销毁,所有指向这个对象A的属性并不会自动设置为nil。这时候这些属性就变成野指针,再访问这些属性,程序就会crash。在ARC下,assign就变成用于修饰基本数据类型,也就是非对象数据类型,如:int、BOOL、float等。
    retain(MRC特性),它是strong的同义词,两者功能一致。当把某个对象赋值给该属性时,该属性原来所引用的对象的引用计数减1,被赋值对象的引用计数加1。在未启用ARC机制的的情况下,retain可以保证一个对象的引用计数大于1时,该对象不会被回收。启用ARC后一般较少使用retain。
    copy是为了防止属性被意外修改的。所有有mutable(可变)版本的属性类型,如NSString, NSArray, NSDictionary等等——他们都有可变的版本类型:NSMutableString, NSMutableArray, NSMutableDictionary。可变版本会出现属性值被意外改变的可能。所以它们都应该用copy。
    unsafe_unretained和__assign一样,唯一的区别是:assign用于修饰基本数据类型,__unsafe_unretained用于修饰对象类型。

  4. getter=setter=:就是重命名getter和setter方法。

  5. Nullability: nullable, nonnull, null_resettable,null_unspecified。nullable: 表示修饰的属性或参数可以为空。nonnull: 非空,表示修饰的属性或参数不能为空。nonnull,nullable只能修饰对象,不能修饰基本数据类型。null_resettable: get方法不能返回为空,set方法可以为空。null_unspecified:不确定是否为空。

  6. class:声明property为类属性。

  • 几乎所有情况,都写上nonatomic;
  • 对外「只读」的,写上readonly
  • 一般的对象属性,写上strong
  • 需要解决strong reference cycles问题的对象属性,strong改为weak
  • 有mutable(可变)版本的对象属性,strong改为copy
  • 基本数据类型(int, float, BOOL)(非对象属性),用assign

3.不指定任何Property Attributes时,默认的都有哪些?(考虑MRC和ARC两种情况)
MRC: atomic(macOS)/nonatomic(iOS),assign,readwrite。
ARC: atomic(macOS)/nonatomic(iOS),strong(对象)/assign(非对象),readwrite。

4.NSString(NSArray、NSDictionary)类型的Property为什么使用copy attribute,而不能使用strong?
用@property声明 NSString、NSArray、NSDictionary 经常使用copy关键字,是因为他们有对应的可变子类:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置属性值时拷贝一份。如果我们使用是strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。
当属性类型为NSString时,经常用copy特质来保护其封装性,因为传递给@setter方法的值有可能指向一个NSMutableString类的实例。这个类是NSString的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。

5.weak与assign的区别
assign 和 weak类似,weak 比 assign 多了一个功能就是当属性所指向的对象消失的时候(也就是内存引用计数为0)会自动赋值为 nil ,这样再向 weak 修饰的属性发送消息就不会导致野指针操作crash。assign 可以用非 OC 对象,而 weak 必须用于 OC 对象。

6.Auto Property Synthesis的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?
如果使用了属性的话,那么编译器就会自动编写访问属性所需的方法和向类中添加私有类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字,此过程叫做“自动合成”( auto synthesis)。

  • 如果指定了成员变量的名称,会生成一个指定的名称的成员变量。
  • 如果没有指定成员变量的名称会自动生成一个属性同名前面加下划线的成员变量。
  • 如果这个成员变量已经存在了就不再生成了。
  • 假如property名为foo,存在一个名为_foo的实例变量,不会自动合成新变量。

7.在有了Auto Property Synthesis之后,@synthesize还有哪些使用场景?

  1. 想要重命名实例变量不为属性同名前加下划线,可通过手动添加@synthesize foo = yourname实现。
  2. 当你想手动管理 @property 的所有内容时,你就会尝试通过实现 @property 的所有“存取方法”或者使用 @dynamic 来达到这个目的,这时编译器就会认为你打算手动管理 @property,于是编译器就禁用了 autosynthesis。这时如果不去手动定义 ivar,那么就得借助 @synthesize foo = _foo 来手动合成 ivar。

8.@dynamic有什么作用?
@dynamic 禁用了autosynthesis,告诉编译器成员变量和属性的 setter 与 getter 方法由用户自己实现,不自动生成。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到调用@setter或@getter 方法时会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
@dynamic is used in "subclass more specific property": 例如父类的property是NSString类型,但是子类想让这个property是 NSMutableString类型,这时候就需要在子类添加@dynamic,然后定义属性类型为NSMutableString,让其继承父类的成员变量和@setter方法和@getter方法,实现多态。

二、Method

1.什么是class method?什么是instance method?
class method:以加号 “+” 开头,相当于static方法。被限定在类范围内,只能通过类调用,不能被类的实例调用。class method存储在元类对象中,类对象通过isa指针指向元类对象获取其中的class method。
instance method:以减号 “-” 开头。限定在对象实例的范围内,即只能通过实例调用,在实例化之前不能运行。instance method存储在类对象中,实例对象通过isa指针指向类对象获取其中的instance method。

2.什么是initializer?
Implemented by subclasses to initialize a new object (the receiver) immediately after memory for it has been allocated.Return an initialized object, or nil if an object could not be created for some reason that would not result in an exception.
An init message is coupled with an alloc (or allocWithZone:) message in the same line of code: SomeClass *object = [[SomeClass alloc] init]; An object isn’t ready to be used until it has been initialized.

3.+[initialize]和+[load]有什么区别?

  1. load和initialize方法都是在实例化对象之前调用,且在一个类中都只会被调用一次。load方法是一定会在runtime中被调用的,只要类被添加到runtime中了,就会调用load方法。initialize方法则是在类收到第一条消息前调用,就是执行类的一些方法的时候。也就是说这个方法是懒加载,没有用到这个类就不会调用,可以节省系统资源。这两个方法会被自动调用,不能手动调用它们。
  2. 一个类的load方法在整个程序中只会被调用一次,如果子类实现了load方法,子类收到第一条消息时会先调用父类的load方法,再调用子类的load方法,如果父类的load方法已经被调用过,就直接调用子类的load方法。如果子类没有实现load方法,子类收到第一条消息时会调用父类的load方法,但如果父类的load方法已经被调用过,则不会再调用。
    initialize方法则不同。如果子类实现了initialize方法,子类收到第一条消息时会先调用父类的initialize方法,再调用子类的initialize方法,如果父类的initialize方法已经被调用过,就直接调用子类的initialize方法。如果子类没有实现initialize方法,不管父类的initialize方法是否被调用过,子类收到第一条消息时都会调用父类的initialize方法,所以父类的initialize方法可能会被调用多次。
  3. load方法通常用来进行Method Swizzle,initialize方法一般用于对一个类进行一次性初始化。
  4. load和initialize方法内部使用了锁,因此它们是线程安全的。实现时要尽可能保持简单,避免阻塞线程,不要再使用锁。

4.什么是类工厂方法?它与多态有什么关系?
多态是指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,简单的说:就是用父类的指针指向子类的对象。
类工厂方法是快速创建对象的类方法,主要用于给对象分配存储空间和初始化这块存储空间。在类工厂方法中创建对象不能用类名来创建([[ClassName alloc] init]),一定要用self创建([[self alloc] init]),这样谁调用当前方法,self就代表谁,在继承中子类对象就可以通过父类的类工厂方法创建。

5.什么是-[description]?
NSLog函数一旦发现用%@输出某个OC对象时,就会调用这个对象的description方法(这个方法返回值是NSString类型),并且将description方法返回的字符串代替%@的位置进行输出。
description方法的默认实现是返回这样的格式:<类名: 对象的内存地址>。

千万不要在description方法中同时使用%@和self,下面的写法是错误的:
复制代码
- (NSString *)description {
     return [NSString stringWithFormat:@"%@", self];
 }
复制代码
同时使用了%@和self,代表要调用self的description方法,因此最终会导致程序陷入
死循环,循环调用description方法。
复制代码

6.Selector是什么?SEL类型是什么?
Selector是一个SEL类型的对象,它实际上就是c中的字符串,调用@selector()返回一个SEL对象,它就是方法的名字,在类对象的方法列表中存着名字与方法(函数指针)的对应关系,根据一个SEL对象就可以找到方法的地址,找到方法地址后就可以调用该方法。这些都是运行时特性,发消息就是发送SEL,然后根据SEL找到地址,调用方法。
When an Objective-C method is called, it is converted to objc_msgSend function. objc_msgSend is a C function defined in objc/message.h file. The first parameter is the receiving Objective-C object itself, the second parameter is the selector, and the rest of parameters are the parameters passed to the Objective-C method. The process is called message passing. It is objc_msgSend function that figure out how to handle the message to the receiving object at runtime.
SEL类型的定义:typedef struct objc_selector *SEL

7.类的私有方法命名有什么需要注意的?

  1. 尽量和公共方法有一个区分,例如可以在所有私有方法前加p_,例如:p_methodName
  2. 不应该在方法前直接使用下划线(_),因为苹果的所有私有方法都是以下划线开头的,如果你的方法也以下划线开头,则很有可能会在不知不觉中重写了父类的私有方法。

8.如何声明一个方法为当子类override它时,必须调用super?
父类的方法名后面加上NS_REQUIRES_SUPER; 子类重写这个方法就会自动警告提示要调用这个super方法。
-(void)methodName NS_REQUIRES_SUPER;

9.当继承一个父类时,如果父类遵守了某个协议,而该协议中有optional的方法,在我override该optional的方法时,如何知道是否需要调用super?换句话说,就是我如何知道父类是否实现了该optional的方法?
将父类实现了的optional方法在@interface里声明一下。

三、Object-Oriented

1.对象是什么?
对象本质就是一块内存,存着它的成员变量。

2.什么是Abstract Class?
Abstract class指的是用关键字abstract修饰的类,叫做抽象类,是不允许实例化的类,不能直接创建对象,必须要通过子类创建才能使用abstract类的方法。
在类声明中使用abstract修饰符以指示某个类只能是其他类的基类。标记为抽象或包含在抽象类中的成员必须通过从抽象类派生的类来实现。 用关键字abstract修饰的类称为抽象类(abstractclass)。其声明方法存在而不去实现,它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该方法的情况。虽然不能创建abstract类的实例,然而可以创建一个变量,其类型是一个抽象类,并让它指向具体子类的一个实例。Abstract类的子类为它们父类中的所有抽象方法提供实现,否则它们也是抽象类。
abstract 类不能直接用new运算符创建对象!必须产生其子类,由子类创建对象,才可以调用abstract 类的方法。

oc中没有定义抽象类,但是可以通过以下两种方式实现:

@interface Person : NSObject          
+(instancetype)new NS_UNAVAILABLE;  
+(instancetype)alloc NS_UNAVAILABLE;   
+(instancetype)allocWithZone:(struct _NSZone *)zone NS_UNAVAILABLE;   
复制代码
@implementation Person  
-(instancetype)init  
{
    if (self.class == Person.class) {
        return nil;
    }
}
复制代码

3.isa指针是什么?有什么作用?对象为什么需要isa指针?
在Objective-C中,任何类的定义都是对象。类和类的实例(对象)没有任何本质上的区别。任何对象都有isa指针,isa是一个Class类型的指针,他指向对象的类,类保存了对象方法的列表。而Class也是类,也有个isa的指针,指向meteClass(元类),元类保存了类方法的列表。元类(meteClass)也是类,它也是对象,元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass)。根元类的isa指针指向本身,这样形成了一个封闭的内循环。

4.Class Extension是什么?
Class Extension也称为“匿名分类”。一般Class Extension写在类的源文件(.m)中,@interface部分为Class Extension。其被设计出来就是为了解决两个问题的,其一,定义类私有变量和私有方法的地方。其二,实现public readonly,private readwrite的property(意思是在h头文件中定义一个属性对外是readonly的,但在类的内部希望是可读写的,所以可以在m源文件中的@interface部分重新定义此属性为readwrite,此时此属性对外是只读的,对内是读写的)。

5.什么是Designated Initializer?

6.Object in OC如何进行memory layout?(考虑有super class的情况)
最左边的是对象(Instance),中间的是类(Class),最右边的是元类(Meta Class)。实例变量(包括父类)都保存在对象本身的存储空间内;本类的实例方法保存在类中,本类的类方法保存在元类中。

对象存储在堆区,内存布局如下:isa 指针指向其类,其余空间保存各级的实例变量(ivar)。

类对象的内存布局如下:isa 指向其元类,super_class指向其父类,此外还包含实例变量列表、实例方法列表、协议列表。(实例变量包含了变量的名称、类型、偏移等,但却不包括变量的值——值在对象而非类中)

7.[NSString init]是否能行?为什么?
不行,在运行时会报错。因为类的alloc方法是为实例对象开辟存储空间(还将所有成员变量设置为0),返回实例对象地址,再调用对象的+init方法初始化成员变量(但实际上什么也没做)。而类对象调用的是-init方法的,在运行时会抛出异常cannot init a class object。

8.谈谈OC中的copy和mutableCopy(shallow copy & deep copy)?
通过copy拷贝的对象是不可变的,通过mutableCopy拷贝的对象是可变的。拷贝的要求是拷贝出来的对象和原来的对象不能相互影响。如果通过不可变的对象调用mutableCopy会生成一个可变的新对象;通过可变的对象调用mutableCopy会生成一个可变的新对象;通过可变的对象调用copy会生成一个不可变的新对象;而通过不可变的对象调用copy不会生成一个新的对象,因为原来的对象是不能修改的,拷贝出来的对象也是不能修改的,既然两个都不能修改,那么它们永远不可能影响到另外一个对象,所以oc为了对内存进行优化,就不会生成新的对象。如果没有生成新的对象,我们称之为shallow copy,是指针拷贝;如果生成新的对象,我们称之为deep copy,是内容拷贝。

9.id与instancetype的区别?

  1. id是可以指向任意对象的万能指针,在编译的时候不会做类型检查,所以可以通过id类型直接调用指向对象中的所有方法,编译器不会报错,当然如果调用到不属于指向对象中的方法, 而编译时又不会报错, 所以可能导致运行时的错误。instancetype是oc的一个关键字,专门用于在自定义构造方法和类工厂方法中取代id作为返回值类型,这样可以在编译的时候就判断出对象的真实类型,避免对象错误访问了不属于自己的方法而在编译的时候不报错。
  2. id可以用来定义变量,可以作为返回值,可以作为形参。而instancetype只能作为返回值。

10.id与NSObject*的区别?
id是万能指针,可以指向任意对象。它简单地申明了指向对象的指针,没有给编译器任何类型信息,因此,id类型是运行时的动态类型,编译器无法知道它的真实类型,即使你发送一个id类型没有的方法,也不会产生编译警告。
NSObject是静态类型,只能访问retain,release,description等这些NSObject拥有的方法。它不是万能指针,因为并不是所有的Foundation/Cocoa对象都继承自NSObject,比如NSProxy,Protocol和Class就不从NSObject继承,所以你无法使用NSObject*指向这些对象,即使这些对象有release和retain这样的通用方法。

11.什么是单例?为什么要有单例?如何实现?

通过定义宏来实现单例:
如果是MRC,增加下面的代码:
12.@class有什么用?

  1. 提升编译效率。@class仅仅告诉编译器后面的名称是一个类,不会做任何拷贝操作,所以在.h中用@class可以提升编译效率。例如如果都在.h中import,假如A拷贝了B,B拷贝了C,如果C被修改了,那么B和A都要重新拷贝,因为C修改了B会重新拷贝,B重新拷贝就相当于B也被修改了,那么A也会重新拷贝。但是如果在.h中用@class,在.m中用import,那么如果一个文件发生改变,只有和这个文件有直接关系的那个文件才会重新拷贝。

2. 解决循环拷贝。如果.h中都用import,那么A拷贝B,B拷贝A会形成死循环。如果在.h中用@class,那么不会做任何拷贝操作,而在.m中用import只会拷贝相应的文件,并不会形成循环。

四、Memory Management

1.什么是Zombie Objects?
Zombie Objects:已经被释放掉的对象。一般来说,访问已经释放的对象或向它发消息会引起错误。因为指针指向的内存块认为你无权访问或它无法执行该消息,这时候内核会抛出一个异常,表明你不能访问该存储区域(EXC_BAD_ACCESS类型错误),但是并不会给太多的信息来定位错误来源,这让问题无从找起。

调试解决该类问题一般采用NSZombieEnabled(开启僵尸模式)。通过生成僵尸对象来替换dealloc的实现,当对象引用计数为0的时候,将需要dealloc的对象转化为僵尸对象。僵尸对象的作用是在你向它发送消息时,就不会向之前那样Crash或者产生一个难以理解的行为,而是放出一个错误消息,它会显示一段日志并自动跳入debug,因此我们就可以找到具体或者大概是哪个对象被错误的释放了。

一旦开启僵尸模式,每次通过指针访问对象的时候。都会去检查指针指向的对象是否为僵尸对象。那么这样的话就影响效率了。

2.什么是Dangling Pointer?
当一个指针指向一个僵尸对象(不可用内存),我们就称这个指针为野指针。

内存回收的本质:
1.申请一块空间,实际上是向系统申请一块别人不再使用的空间。
2.释放一块空间,指的是占用的空间不再使用,这个时候系统可以分配给别人去使用。
3.在这个空间分配给别人之前,数据还是存在的。 OC对象释放以后,表示OC对象占用的空间可以分配给别人。 但是再分配给别人之前,这个空间仍然存在,对象的数据仍然存在。

使用野指针访问僵尸对象,有的时候会出问题,有的时候不会出问题:1.当野指针指向的僵尸对象所占用的空间还没有分配给别人的时候,这个时候其实是可以访问的,因为对象的数据还在。2.当野指针指向的对象所占用的空间分配给了别人的时候,这个时候访问就会出问题。3.所以不要通过一个野指针去访问一个僵尸对象。为了避免给野指针发送消息会报错,一般情况下,当一个对象被释放后我们会将这个对象的指针设置为空指针。

3.ARC与MRC的联系和区别?
MRC 手动管理内存(Manual Reference Counting):
系统是根据对象的引用计数器来判断什么时候需要回收一个对象所占用的内存,引用计数器是一个整数,可以理解为”对象被引用的次数”,每个OC对象都有自己的引用计数器,任何一个对象,刚创建的时候,初始的引用计数为1(当使用alloc、new或者copy创建一个对象时,对象的引用计数器默认就是1),为保证对象的存在,每当引用到对象需要给对象发送一条retain消息,可以使引用计数器值+1,当不再需要对象时,通过给对象发送一条release消息,可以使引用计数器值-1,当对象的引用计数为0时,系统就知道这个对象不再需要使用了,所以可以释放它的内存,对象即将被销毁时系统会自动给对象发送一条dealloc消息(因此,从dealloc方法有没有被调用,就可以判断出对象是否被销毁)。一般会重写dealloc方法,在这里释放相关资源,dealloc就是对象的遗言,一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用。
当我们不再使用一个对象的时候应该将其空间释放,但是有时候我们不知道何时应该将其释放。为了解决这个问题,Objective-C提供了autorelease方法。autorelease是一种支持引用计数的内存管理方式,只要给对象发送一条autorelease消息,就会将对象放到一个自动释放池中,当自动释放池被销毁时,会对池子里面的所有对象做一次release操作,autorelease实际上只是把对release的调用延迟了。
MRC下循环引用问题:A对象要拥有B对象,而B对应又要拥有A对象,此时会形成循环retain,导致A对象和B对象永远无法释放。如何解决这个问题呢?当两端互相引用时,应该一端用retain,一端用assign。

ARC 自动管理内存(Automatic Reference Counting):
使用ARC后,系统会检测出何时需要保持对象,何时需要自动释放对象,何时需要释放对象,编译器会管理好对象的内存,会在何时的地方插入retain, release和autorelease,通过生成正确的代码去自动释放或者保持对象。我们完全不用担心编译器会出错。
ARC判断一个对象是否需要释放不是通过引用计数来进行判断的,而是通过强指针来进行判断的。强指针是被__strong修饰的指针,默认所有对象的指针变量都是强指针,弱指针是被__weak修饰的指针。只要还有一个强指针变量指向对象,对象就会保持在内存中。
ARC的注意点:不允许调用对象的 release方法;不允许调用autorelease方法;重写父类的dealloc方法时,不能再调用 [super dealloc]。
ARC下循环引用问题:ARC和MRC一样,如果A拥有B,B也拥有A,那么必须一方使用弱指针。

4.unsafe_unretained如何使用?
__unsafe_unretained和__weak一样,表示的是对象的一种弱引用关系,唯一的区别是:__weak修饰的对象被释放后,指向对象的指针会置空,也就是指向nil,不会产生野指针;而__unsafe_unretained修饰的对象被释放后,指针不会置空,而是变成一个野指针,那么此时如果访问这个对象的话,程序就可能会Crash,抛出BAD_ACCESS的异常。
__unsafe_unretained用于oc与c之间的交互,因为c中没有对象引用的概念,所以当c与oc交互的时候要把对象修饰为__unsafe_unretained,表明它释放后指向它的指针会变成野指针。

5.为什么NSString的retainCount非常大?
Objective-C中NSString对象与其他类型的对象在引用计数上存在着不小的差别。对于NSString的对象,不同的创建方式以及不同的字符个数都会影响对象的引用计数。

  1. 首先,当字符串的个数小于10个的时候,不管采用哪一种创建方式所得到的字符串都不存在引用计数一说,NSString的retainCount都非常大。对于通过字符串常量创建和通过initWithString和stringWithString创建的NSString对象存储在常量区,通过initWithFormt和stringWithFormat创建的NSString对象存储在已知的五大区域(栈区,堆区,静态区(全局区),常量区,代码区)之外。

  2. 当字符串的长度大于等于10时,开始产生差别。

    NSString *s = @"1234567890";     
    NSString *s = [[NSString alloc] initWithString:@"1234567890"];
    NSString *s = [[NSString stringWithString:@"1234567890"]; 
    复制代码

对于通过字符串常量创建和通过initWithString和stringWithString(其实就是通过浅拷贝返回一个字符串对象,指向的是字符串常量)创建的NSString对象,不管字符串的内容和长度怎么变化,该对象始终是存储在常量区的,没有引用计数一说;硬要加一个引用计数的话,那么就是无符号长整型的最大值。

   NSString *s = [NSString stringWithFormat:@"1234567890"]; 
   NSString *s = [[NSString alloc] initWithFormat:@"1234567890"];   
复制代码

字符串长度大于10个以后,NSString对象(通过initWithFormt和stringWithFormat创建)与Objective-C中其他类型的对象是一致的,都存在堆区,引用计数会随着retain、release、copy、autorelease和alloc的使用而发生变化。

6.-[dealloc]有什么用?
当一个对象即将被销毁时系统会自动给对象发送一条dealloc消息(因此,从dealloc方法有没有被调用,就可以判断出对象是否被销毁)。一般会重写dealloc方法,在这里释放相关资源,dealloc就是对象的遗言。

五、Category

1.如何在Category中给class增加存储属性?
利用objc_setAssociatedObject函数进行对象的关联:
关联对象是指某个OC对象通过一个唯一的key连接到一个类的实例上。关联对象并不是成员变量,关联对象是由一个全局哈希表存储的键值对中的值。AssociationsManager里面是由一个静态全局哈希表AssociationsHashMap来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。而map的key是一个对象的指针地址,map的value又是另外一个AssociationsHashMap,里面保存了这个对象的所有关联对象的key-value对。

实现代码:给系统类UIViewController添加一个name属性

#import <UIKit/UIKit.h>
@interface UIViewController (Utilities)
@property(nonatomic,copy)NSString *name;
@end
======================================
#import "UIViewController+Utilities.h"
#import <objc/runtime.h>
@implementation UIViewController (Utilities)
-(void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY);
}
-(NSString *)name
{
    return objc_getAssociatedObject(self, @selector(name));
}
@end
复制代码

与 Associated Objects 相关的函数主要有三个,我们可以在 runtime 源码的 runtime.h 文件中找到它们的声明:

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object);
复制代码

objc_setAssociatedObject 用于给对象添加关联对象,传入 nil 则可以移除已有的关联对象;
objc_getAssociatedObject 用于获取关联对象; objc_removeAssociatedObjects 用于移除一个对象的所有关联对象

2.Category与Class Extension的区别?

  1. extension在编译期决议,它就是类的一部分,但是category则完全不一样,它是在Runtime时决议的。extension在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。
  2. extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension,除非创建子类再添加extension。而category不需要有类的源码,我们可以给系统提供的类添加category。
  3. extension可以添加实例变量,而category不可以。因为在Runtime中,objc_class结构体大小是固定的,不可能往这个结构体中添加数据,只能修改。所以ivars指向的是一个固定区域,只能修改成员变量值,不能增加成员变量个数。methodList是一个二维数组,所以可以修改*methodLists的值来增加成员方法,虽没办法扩展methodLists指向的内存区域,却可以改变这个内存区域的值(存储的是指针)。因此,可以动态添加方法,不能添加成员变量。
  4. extension和category都可以添加属性,但是category的属性不能生成成员变量和getter、setter方法的实现。

3.Category中的方法命名上有什么需要注意的地方?
最好加上自己的前缀,避免覆盖别人在其它Category中实现的同名方法。

六、Protocol

1.Protocol与Delegate的关系?
代理是协议的一种应用场景,协议可以通过类的代理实现,也可以类自己实现。
协议(protocol),就是使用了这个协议后就要按照这个协议来办事,协议要求实现的方法就一定要实现。
委托(delegate),顾名思义就是委托别人办事,就是当一件事情发生后,自己不处理,让别人来处理。
delegate只是一种模式,当一个类把自己内部一部分实现暴露给另外一个类去做的时候,就叫实际做事的类为delegate,但是这个delegate想做事就需要实现原有类规定的一些protocol。
举个例子:我上班的工作主要内容包括 1.写代码 2.写文档 3.测试程序 4.接电话。1,2我自己全权负责,但是后面3,4我不想自己做,所以我想找个助手(delegate)帮我做这些事,于是我定了一个招聘要求(Protocol),里写明我的助手需要会做3,4这三件事。很快,我招到一个助手。于是以后每当我遇到需要测试程序或者接电话的活,我就把他转交给助手(delegate)去处理,助手处理完后如果有处理结果(返回值)助手会告诉我,也许我会拿来用。如果不需要或者没有结果,我就接着做下面的事。

2.Protocol与Abstract Class的区别?

  1. 类只能继承一个抽象类,但可以实现多个协议。
  2. 抽象类是不能被实例化的类,只能作为由其他类继承的基类; 协议则定义了实现某种服务的一般规范,但不指定如何实现。
  3. 在抽象类中可以实现一些方法和属性(实际上,抽象类可以完全实现 、部分实现、根本不实现);在接口中只能定义方法,不能实现方法

3.Protocol与多态的区别?
多态具有继承性,你可以选择是否覆盖父类的方法,使用子类自己的实现。
协议仅仅是规定了必须实现的方法,每个实现了此协议的类必须实现协议规定的所有required 方法。

4.说说对NSObject Protocol的理解?
NSObject协议提供了一组方法作为Objective-C对象的基础,直接遵守NSObject协议的类为根类(NSObject,NSCopy…),所以所有继承自根类的子类都具备NSObject协议中描述的功能。

5.说说对NSCopying Protocol的理解?
A protocol that objects adopt to provide functional copies of themselves.
NSCopying declares one method, copyWithZone:, but copying is commonly invoked with the convenience method copy. The copy method is defined for all objects inheriting from NSObject and simply invokes copyWithZone: with the default zone.
If a subclass inherits NSCopying from its superclass and declares additional instance variables, the subclass has to override copyWithZone: to properly handle its own instance variables, invoking the superclass’s implementation first.

七、Block

1.Block是如何实现的?
对于一般的block来说,他的数据就是传入的参数和定义这个block时截获的变量。而它的算法就是我们往里面写的那些方法,函数调用等。

void blockFunc1()
{
    int num = 100;
    void (^block)() = ^{
        NSLog(@"num equal %d", num);
    };
    num = 200;
    block();
}
复制代码

将OC重写为C:

void blockFunc1()
{
    int num = 100;
    // *************************重点句***********************
    void (*block)() = &__blockFunc1_block_impl_0(__blockFunc1_block_func_0, &__blockFunc1_block_desc_0_DATA, num));
    // *****************************************************
    num = 200;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
复制代码

这里我们可以看到,block实际上是指向结构体的指针。这个结构体由两个成员变量结构体,捕获的局部变量和一个构造函数组成。两个结构体分别是____block_impl__和main_block_desc_0类型的。其中____block_impl__结构体中有一个函数指针,指针将指向____mian_block_func_0__类型的结构体。

2.__block有什么用?为什么需要__block?
在外界变量前加上__block可以实现在block中改变外部变量的值。

不加__block是值传递:
加__block是地址传递:
3.Block为什么会导致retain cycle?
循环引用问题的根源在于Block和obj可能会互相强引用,互相retain对方,这样就导致了retain cycle,最后这个Block和obj就变成了孤岛,谁也释放不了谁。

@interface Class:()
{
    NSString *strVar;//成员变量
}
@end
@implementation Class
@property (nonatomic, copy) NSString *strVVV;//成员属性
- (void)viewDidLoad {
    // 强引用示例1
    self.myBlock = ^{
        [self doSomething];
    };
    // 强引用示例2      
     self
    self.myBlock = ^{
        strVar = @"haha";
    };
    // 强引用示例3
    // 在Block中使用成员属性,retain的不是这个属性,而会retain self
    self.myBlock = ^{
        _strVVV = @"vvvvv";
    };
}
@end
       +-----------+           +-----------+
       |    self   |           |   Block   |
  ---> |           | --------> |           |
       | retain 2  | <-------- | retain 1  |
       |           |           |           |
       +-----------+           +-----------+

复制代码

4.如何避免block retain cycle?

  1. weak-strong dance
__weak typeof(self) weakSelf = self;
self.myBlock = ^{
    if (!weakSelf) {
        return;
    }
    __strong typeof(self) self = weakSelf;
    NSLog(@"%@", self.name);
};
复制代码
  1. heap-stack dance
self.myBlock = ^(Person *self) {
    NSLog(@"%@", self.name);
};
self.myBlock(self);
复制代码

5.Block与函数指针的区别?
Block就是一个代码块,函数指针是Block的一部分。定义完block之后,其实是创建了一个函数,在创建block结构体的时候把函数的指针和局部变量一起传给了block,之后可以拿出来调用。
在ios开发中,blocks是对象,它封装了一段代码,这段代码可以在任何时候执行。Blocks可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。它和传统的函数指针很类似,但是有区别:blocks是inline的,并且它对局部变量是只读的(值传递)。

分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改