An array of iOS data structures

Posted by kenshejoe on Tue, 07 Jan 2020 01:59:19 +0100

The arrays often used in iOS development are NSArray and NSMutableArray, which are data structures provided by Foundation. Usually, NSArray and NSMutableArray are used in development to meet the development needs. With a learning attitude, Xiaobian is very interested in the implementation of the bottom layer of array data structure, so he has implemented an array

https://github.com/ZhaoBingDong/iOS-DataStructures.git

//
//  ArrayList.m
//  ArrayList
//
//  Created by dzb on 2018/7/19.
//  Copyright © 2018 private Bryant. All rights reserved
//

#import "ArrayList.h"

static NSInteger const defaultCapacity = 10;
typedef id _Nullable (*RFUNC)(id _Nonnull, SEL _Nonnull,...);
typedef void * AnyObject;

@interface ArrayList ()
{
@private
    AnyObject *_array;
    NSInteger _size;
    NSInteger _capacity;
}
@end

@implementation ArrayList

#pragma mark - init

- (instancetype)init
{
    self = [super init];
    if (self) {
        _size = 0;
        _capacity = defaultCapacity;
        _array = (AnyObject*)calloc(_capacity, sizeof(AnyObject));
    }
    return self;
}

+ (instancetype)array {
    return [[ArrayList alloc] initWithCapacity:defaultCapacity];
}

+ (instancetype)arrayWithCapacity:(NSUInteger)numItems {
    return [[ArrayList alloc] initWithCapacity:numItems];
}

- (instancetype)initWithCapacity:(NSUInteger)numItems {
    _capacity = numItems;
    _array = (AnyObject*)calloc(_capacity,sizeof(AnyObject));
    _size = 0;
    return self;
}

#pragma mark - add operation

- (void)addObject:(id)anObject {
    [self insertObject:anObject atIndex:_size];
}

- (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
    if (!anObject) {
        @throw [NSException exceptionWithName:@"add object null." reason:@"object must be not null ." userInfo:nil];
        return;
    }
    //Cross the border
    if ((index > _size)) {
        @throw [NSException exceptionWithName:@"Array is out of bounds" reason:@"out of bounds" userInfo:nil];
        return;
    }
    if (_size == _capacity) { ///Determine whether the original array is full. If it is full, increase the array length
        [self resize:2 * _capacity];
    }
    ///Swap index location
    if (self.count > 0 ) {
        for(NSInteger i = _size - 1 ; i >= index ; i--)
            _array[i + 1] = _array[i];
    }
    self->_array[index] = (__bridge_retained AnyObject)(anObject);
    _size++;
}

#pragma mark - delete operation
- (void)removeAllObjects {
    AnyObject *oldArray = _array;
    NSInteger i = _size - 1;
    while (i >= 0) {
        AnyObject *obj = oldArray[i];
        CFRelease(obj);
        i--;
    }
    if (oldArray != NULL) { free(oldArray); }
    _size = 0;
    _capacity = defaultCapacity;
    _array = (AnyObject*)calloc(_capacity, sizeof(AnyObject));

}

- (void)removeObjectAtIndex:(NSUInteger)index {
    ///Judge out of bounds
    if ((index > _size)) {
        @throw [NSException exceptionWithName:@"Array is out of bounds" reason:@"out of bounds" userInfo:nil];
        return;
    }
    AnyObject object =(_array[index]);
    CFRelease(object);
    for(NSInteger i = index + 1 ; i < _size ; i ++)
        _array[i - 1] = _array[i];
    _size--;
    _array[_size] = NULL;
    ///Reduce array space
    if (_size == _capacity * 0.25 && (_capacity*0.25 != 0)) {
        [self resize:_capacity/2];
    }
}

- (void)removeObject:(id)anObject {
    NSInteger index = [self indexOfObject:anObject];
    if (index == NSNotFound) return;
    [self removeObjectAtIndex:index];
}

- (void)removeLastObject {
    if ([self isEmpty]) return;
    [self removeObjectAtIndex:_size-1];
}

#pragma mark - modify operation

- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
    if (!anObject) {
        @throw [NSException exceptionWithName:@"add object null." reason:@"object must be not null ." userInfo:nil];
        return;
    }
    ///Judge out of bounds
    if ((index > _size)) {
        @throw [NSException exceptionWithName:@"Array is out of bounds" reason:@"out of bounds" userInfo:nil];
        return;
    }
    _array[index] = (__bridge AnyObject)(anObject);
}

#pragma mark - query operation

- (BOOL) isEmpty {
    return (self->_size == 0);
}

- (BOOL) isFull {
    return (self->_size == self->_capacity-1);
}

- (id)objectAtIndex:(NSUInteger)index {
    if ((index > _size)) {
        @throw [NSException exceptionWithName:@"Array is out of bounds" reason:@"out of bounds" userInfo:nil];
        return nil;
    }
    if ([self isEmpty]) { return nil; }
    AnyObject obj = _array[index];
    if (obj == NULL) return nil;
    return (__bridge id)(obj);
}

- (NSUInteger)indexOfObject:(id)anObject {
    for (int i = 0; i<_size; i++) {
        id obj = (__bridge id)(_array[i]);
        if ([anObject isEqual:obj]) return i;
    }
    return NSNotFound;
}

- (BOOL)containsObject:(id)anObject {
    for (int i = 0; i<_size; i++) {
        id obj = (__bridge id)(_array[i]);
        if ([anObject isEqual:obj]) return YES;
    }
    return NO;
}

- (id)firstObject {
    if ([self isEmpty]) return nil;
    return (__bridge id _Nullable)(_array[0]);
}

- (id)lastObject {
    if ([self isEmpty]) return nil;
    return (__bridge id _Nullable)(_array[_size-1]);
}

- (NSUInteger)count {
    return _size;
}

/**
 Expand array

 @param capacity New capacity
 */
- (void) resize:(NSInteger)capacity {

    _capacity = capacity;
    AnyObject *oldArray = _array;
    AnyObject *newArray = (AnyObject *)calloc(_capacity,sizeof(AnyObject));
    size_t size = sizeof(AnyObject) * self.count;
    memcpy(newArray,oldArray,size); ///Copy values from old arrays
    _array = newArray;
    if (oldArray != NULL) {
        free(oldArray);
        oldArray = NULL;
    }

}

/**
 Copy a new array so that the contents of the new array are the same as the original

 @return ArrayList
 */
- (ArrayList *)copyNewArray {
    AnyObject *oldArray = _array;
    ArrayList *newArray = [ArrayList arrayWithCapacity:_capacity];
    size_t size = sizeof(AnyObject) * _capacity;
    memcpy(newArray->_array,oldArray, size);
    return newArray;
}

- (void)dealloc
{
    if (_array != NULL) {
        NSInteger i = _size - 1;
        while (i >= 0) {
            AnyObject *obj = _array[i];
            if (obj != NULL)
                CFRelease(obj);
            i--;
        }
        free(_array);
    }
}

- (NSString *)description {
    NSMutableString *string = [NSMutableString stringWithFormat:@"\nArrayList %p : [ \n" ,self];
    for (int i = 0; i<_size; i++) {
        AnyObject obj = _array[i];
        [string appendFormat:@"%@",(__bridge id)obj];
        if (i<_size-1) {
            [string appendString:@" , \n"];
        }
    }
    [string appendString:@"\n]\n"];
    return string;
}

- (id)copyWithZone:(NSZone *)zone {
    return self;
}

- (id)mutableCopyWithZone:(NSZone *)zone {
    return self;
}

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len {

    NSInteger count;

    /* In a mutable subclass, the mutationsPtr should be set to point to a
     * value (unsigned long) which will be changed (incremented) whenever
     * the container is mutated (content added, removed, re-ordered).
     * This is cached in the caller at the start and compared at each
     * iteration.   If it changes during the iteration then
     * objc_enumerationMutation() will be called, throwing an exception.
     * The abstract base class implementation points to a fixed value
     * (the enumeration state pointer should exist and be unchanged for as
     * long as the enumeration process runs), which is fine for enumerating
     * an immutable array.
     */
    state->mutationsPtr = (unsigned long *)&state->mutationsPtr;
    count = MIN(len, [self count] - state->state);
    /* If a mutation has occurred then it's possible that we are being asked to
     * get objects from after the end of the array.  Don't pass negative values
     * to memcpy.
     */
    if (count > 0)
    {
        IMP imp = [self methodForSelector: @selector(objectAtIndex:)];
        int p = (int)state->state;
        int i;

        for (i = 0; i < count; i++, p++)
        {
            RFUNC funcPt =(RFUNC)imp;
            id objc = funcPt(self,@selector(objectAtIndex:),p);
            buffer[i] = objc;
        }
        state->state += count;
    }
    else
    {
        count = 0;
    }
    state->itemsPtr = buffer;
    return count;

}
@end
/**
 The performance of NSMutableArray is 2.42 times higher than that of ArrayList
 ArrayList 16.90701246261597ms
 NSMutableArray 6.979000568389893ms
 */
- (void) testArrayListAndNSArray {
    _timeArray = [NSMutableArray array];

    ///100000 comparisons between NSMutableArray and ArrayList
    int number = 100000;
    Person *p = [Person new];
    for (int i = 0; i<10; i++) {
        CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
        ArrayList <Person *> *array = [ArrayList arrayWithCapacity:number];
        for (int i = 0; i<number; i++) {
            [array addObject:p];
        }
        [array removeAllObjects];
        CFAbsoluteTime linkTime = (CFAbsoluteTimeGetCurrent() - startTime);
        CFTimeInterval duration = linkTime * 1000.0f;
//      NSLog(@"Linked in %f ms",duration);
        [self->_timeArray addObject:@(duration)];
        [NSThread sleepForTimeInterval:0.3f];
    }

    NSLog(@"ArrayList time is %@",[_timeArray valueForKeyPath:@"@avg.self"]);

    NSLog(@"********************************");

    [_timeArray removeAllObjects];

    for (int i = 0; i<10; i++) {
        CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
        NSMutableArray <Person *> *array = [NSMutableArray arrayWithCapacity:number];
        for (int i = 0; i<number; i++) {
            [array addObject:p];
        }
        [array removeAllObjects];
        CFAbsoluteTime linkTime = (CFAbsoluteTimeGetCurrent() - startTime);
        CFTimeInterval duration = linkTime * 1000.0f;
        [self->_timeArray addObject:@(duration)];
        [NSThread sleepForTimeInterval:0.3f];
    }

    NSLog(@"NSMutableArray time is %@",[_timeArray valueForKeyPath:@"@avg.self"]);



}

Through 100000 test cases of adding data, the time of ArrayList is nearly 3-5 times that of NSMutableArray, but it's all at the millisecond level. For example, NSMutableArray takes 6.97900056838993ms MS, while ArrayList takes 16.90701246261597ms ms. However, in daily development, very few people add 100000 pieces of data to the array at a time, and large quantities of data are stored in the database, Therefore, the gap between the two can be ignored in the use process. Xiaobian has also used several versions of ArrayList to iteratively prove the stability of some functional modules in its own project
Learning the data structure is important to understand the underlying implementation principle. It is also very convenient to use the data structure provided by the system in normal development

OK, I'm Brian. Welcome to the technology exchange group and iOS development exchange group

Topics: iOS github git Database