GnuStep NSArray源码分析(一) -- 初始化

前言

前面几期博客中,我们看了苹果开发常用主流框架的源码,在其中我们也学会了一些设计理念和设计思路。从这篇博客开始我们开起了对于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);
}

实例化一个空的自动释放的数组:

  1. 初始化大小一个id类型的对象;
  2. 将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;
}
  1. 先获得赋值数组元素项个数;
  2. 动态的分配内存(初始化数组的大小,根据个数进行分配内存,不可变数组,分配的内存大小和anotherArray相同即可);

值的关注的是,分配内存的大小。

_contents = NSZoneMalloc([self zone], count * (sizeof(id) + sizeof(BOOL)));

思考:为什么会有sizeof(id) + sizeof(BOOL)
👤:可能是因为OC中传入的数组元素的类型是id类型(范性),而BOOL说明了可能用来判断这个元素项是否符合传入的id类型,使用BOOL类型作为标记


  1. 遍历操作;
  while (_count < count)
    {
      _contents[_count] = RETAIN([anotherArray objectAtIndex: _count]);
      _isGCObject[_count] = [_contents[_count] isKindOfClass: gcClass];
      _count++;
    }
  1. 返回结果;

注意: 使用RETAIN将不属于自己创建的对象,使生成数组持有这个元素项 这样每一个元素项,都是_contents持有的
#define RETAIN(object) [(object) retain]
在使用ARC后,就不需要手动写入retain,是因为这些操作系统帮助程序员做了优化

arrayWithContentsOfFile 和 arrayWithContentsOfURL 初始化
  1. arrayWithContentsOfFile
+ (id) arrayWithContentsOfFile: (NSString*)file
{
  id	o;

  o = [self allocWithZone: NSDefaultMallocZone()];
  o = [o initWithContentsOfFile: file];
  return AUTORELEASE(o);
}
  1. 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;
}

这段代码在上面就进行了操作,并且我们应该注意到两个参数的传入:

  1. 数组元素项;
  2. 数组元素的个数;

当数组元素传入后,根据传入的个数count进行遍历,逐一赋值给被初始化的数据就可以了。

总结

截止到此,分析了关于数组初始化的基本实现。值的我们关注的,主要是对于错误可能性的处理,对于ARC引用我们在NSArray赋值中看的淋淋尽致,当赋值时候,这个时候被初始化数组是对元素项没有持有的,使用RETAIN()使得元素项变得被数组持有。当使用完成时候,及时RELEASE()。当错误时候时候赋值nil,通知ARC来回收销毁等。
最主要,我们发现了初始化的数组都被添加到了自动释放池中,这是ARC操作的很好的表现,当使用完成时候,不会立即释放,而是当ARC选择合适的时候进行释放对象。

下一篇,我们来分析NSArray常用方法底层实现。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值