前言
前面几期博客中,我们看了苹果开发常用主流框架的源码,在其中我们也学会了一些设计理念和设计思路。从这篇博客开始我们开起了对于Objective-C源码的部分解析。虽然Objective-C官方是非开源的,但小编找到了GnuStep程序,来模仿苹果官方实现了OC中Foundation的部分源代码,是值的我们来分析和理解的。
NSArray
在OC开发中,我们使用相对较为多的,就是数组操作,对于数组OC定义了NSArray和NSMutableArray,对于NSArray来说,是一个不可变的数组。这篇文章就开始从NSArray入手,一步一步探讨关于NSArray的底层实现方法。
打开NSArray源码,我们就看到熟悉的方法,初始化操作等。
+ (instancetype) array;
+ (instancetype) arrayWithArray: (GS_GENERIC_CLASS(NSArray, ElementT) *)array;
+ (instancetype) arrayWithContentsOfFile: (NSString*)file;
#if OS_API_VERSION(GS_API_MACOSX, GS_API_LATEST)
+ (instancetype) arrayWithContentsOfURL: (NSURL*)aURL;
#endif
+ (instancetype) arrayWithObject: (id)anObject;
+ (instancetype) arrayWithObjects: (id)firstObject, ...;
+ (instancetype) arrayWithObjects: (const id[])objects count: (NSUInteger)count;
- (GS_GENERIC_CLASS(NSArray, ElementT) *) arrayByAddingObject:
(GS_GENERIC_TYPE(ElementT))anObject;
- (GS_GENERIC_CLASS(NSArray, ElementT) *) arrayByAddingObjectsFromArray:
(GS_GENERIC_CLASS(NSArray, ElementT)*)anotherArray;
- (BOOL) containsObject: (GS_GENERIC_TYPE(ElementT))anObject;
初始化操作
方法 | 作用 |
---|---|
+ (instancetype) array; | 无参数,初始化数组 |
+ (instancetype) arrayWithArray: (GS_GENERIC_CLASS(NSArray, ElementT) *)array; | 利用已知数组初始化数组 |
+ (instancetype) arrayWithContentsOfFile: (NSString*)file; | 利用文件进行初始化 |
+ (instancetype) arrayWithContentsOfURL: (NSURL*)aURL; | 利用URL路径进行初始化数组 |
+ (instancetype) arrayWithObject: (id)anObject; | 利用一个对象进行初始化数组 |
+ (instancetype) arrayWithObjects: (id)firstObject, …; | 多个对象进行初始化操作 |
+ (instancetype) arrayWithObjects: (const id[])objects count: (NSUInteger)count | 多个对象通过下标进行初始化操作 |
array初始化
+ (id) array
{
id o;
o = [self allocWithZone: NSDefaultMallocZone()];
o = [o initWithObjects: (id*)0 count: 0];
return AUTORELEASE(o);
}
实例化一个空的自动释放的数组:
- 初始化大小一个id类型的对象;
- 将o添加到自动释放池中,这样数组在使用完成时候,不会立即释放,而是等到合适的时候被ARC释放;
#define AUTORELEASE(object) [(object) autorelease]
arrayWithArray初始化
通过数组进行初始化的操作和array无参初始化,大致机理是相同的。
+ (id) arrayWithArray: (NSArray*)array
{
id o;
o = [self allocWithZone: NSDefaultMallocZone()];
o = [o initWithArray: array];
return AUTORELEASE(o);
}
通过一个数组便利另一个数组,最好的做法就是,逐一遍历逐一赋值,看看底层怎么实现的。
- (id) initWithArray: (NSArray*)anotherArray
{
NSUInteger count = [anotherArray count];
_contents = NSZoneMalloc([self zone], count * (sizeof(id) + sizeof(BOOL)));
_isGCObject = (BOOL*)&_contents[count];
_count = 0;
while (_count < count)
{
_contents[_count] = RETAIN([anotherArray objectAtIndex: _count]);
_isGCObject[_count] = [_contents[_count] isKindOfClass: gcClass];
_count++;
}
return self;
}
- 先获得赋值数组元素项个数;
- 动态的分配内存(初始化数组的大小,根据个数进行分配内存,不可变数组,分配的内存大小和anotherArray相同即可);
值的关注的是,分配内存的大小。
_contents = NSZoneMalloc([self zone], count * (sizeof(id) + sizeof(BOOL)));
思考:为什么会有sizeof(id) + sizeof(BOOL)
👤:可能是因为OC中传入的数组元素的类型是id类型(范性),而BOOL说明了可能用来判断这个元素项是否符合传入的id类型,使用BOOL类型作为标记
- 遍历操作;
while (_count < count)
{
_contents[_count] = RETAIN([anotherArray objectAtIndex: _count]);
_isGCObject[_count] = [_contents[_count] isKindOfClass: gcClass];
_count++;
}
- 返回结果;
注意: 使用RETAIN将不属于自己创建的对象,使生成数组持有这个元素项 这样每一个元素项,都是_contents持有的
#define RETAIN(object) [(object) retain]
在使用ARC后,就不需要手动写入retain,是因为这些操作系统帮助程序员做了优化
arrayWithContentsOfFile 和 arrayWithContentsOfURL 初始化
- arrayWithContentsOfFile
+ (id) arrayWithContentsOfFile: (NSString*)file
{
id o;
o = [self allocWithZone: NSDefaultMallocZone()];
o = [o initWithContentsOfFile: file];
return AUTORELEASE(o);
}
- arrayWithContentsOfURL
+ (id) arrayWithContentsOfURL: (NSURL*)aURL
{
id o;
o = [self allocWithZone: NSDefaultMallocZone()];
o = [o initWithContentsOfURL: aURL];
return AUTORELEASE(o);
}
对比File和URL两个方法,他们都将file和url转换成为了data数据,然后使用propertyList进行处理操作。
- (id) initWithContentsOfFile: (NSString*)file
{
NSString *myString;
myString = [[NSString allocWithZone: NSDefaultMallocZone()]
initWithContentsOfFile: file];
if (myString == nil)
{
DESTROY(self);
}
else
{
id result;
NS_DURING
{
result = [myString propertyList];
}
NS_HANDLER
{
result = nil;
}
NS_ENDHANDLER
RELEASE(myString);
if ([result isKindOfClass: NSArrayClass])
{
//self = [self initWithArray: result];
/* OSX appears to always return a mutable array rather than
* the class of the receiver.
*/
RELEASE(self);
self = RETAIN(result);
}
else
{
NSWarnMLog(@"Contents of file '%@' does not contain an array", file);
DESTROY(self);
}
}
return self;
}
- (id) initWithContentsOfURL: (NSURL*)aURL
{
NSString *myString;
myString = [[NSString allocWithZone: NSDefaultMallocZone()]
initWithContentsOfURL: aURL];
if (myString == nil)
{
DESTROY(self);
}
else
{
id result;
NS_DURING
{
result = [myString propertyList];
}
NS_HANDLER
{
result = nil;
}
NS_ENDHANDLER
RELEASE(myString);
if ([result isKindOfClass: NSArrayClass])
{
self = [self initWithArray: result];
}
else
{
NSWarnMLog(@"Contents of URL '%@' does not contain an array", aURL);
DESTROY(self);
}
}
return self;
}
注意: 当使用完成或者发生异常错误时候,底层会自动进行销毁(释放)操作。
根据元素向进行初始化
+ (id) arrayWithObject: (id)anObject
{
id o;
o = [self allocWithZone: NSDefaultMallocZone()];
o = [o initWithObjects: &anObject count: 1];
return AUTORELEASE(o);
}
+ (id) arrayWithObjects: firstObject, ...
{
id a = [self allocWithZone: NSDefaultMallocZone()];
GS_USEIDLIST(firstObject,
a = [a initWithObjects: __objects count: __count]);
return AUTORELEASE(a);
}
+ (id) arrayWithObjects: (const id[])objects count: (NSUInteger)count
{
return AUTORELEASE([[self allocWithZone: NSDefaultMallocZone()]
initWithObjects: objects count: count]);
}
这三个方法共同调用了initWithObjects:count:,来看一下具体做了什么操作。
- (id) initWithObjects: (const id[])objects count: (NSUInteger)count
{
_contents = NSZoneMalloc([self zone], count * (sizeof(id) + sizeof(BOOL)));
_isGCObject = (BOOL*)&_contents[count];
_count = 0;
while (_count < count)
{
_contents[_count] = RETAIN(objects[_count]);
if (_contents[_count] == nil)
{
DESTROY(self);
[NSException raise: NSInvalidArgumentException
format: @"Nil object to be added in array"];
}
else
{
_isGCObject[_count] = [objects[_count] isKindOfClass: gcClass];
}
_count++;
}
return self;
}
这段代码在上面就进行了操作,并且我们应该注意到两个参数的传入:
- 数组元素项;
- 数组元素的个数;
当数组元素传入后,根据传入的个数count进行遍历,逐一赋值给被初始化的数据就可以了。
总结
截止到此,分析了关于数组初始化的基本实现。值的我们关注的,主要是对于错误可能性的处理,对于ARC引用我们在NSArray赋值中看的淋淋尽致,当赋值时候,这个时候被初始化数组是对元素项没有持有的,使用RETAIN()使得元素项变得被数组持有。当使用完成时候,及时RELEASE()。当错误时候时候赋值nil,通知ARC来回收销毁等。
最主要,我们发现了初始化的数组都被添加到了自动释放池中,这是ARC操作的很好的表现,当使用完成时候,不会立即释放,而是当ARC选择合适的时候进行释放对象。
下一篇,我们来分析NSArray常用方法底层实现。