ios之Block研究


Block的好處,我總結了下主要有2點:1.用於回調特別方便,2.可以延長對象的作用區域。但是,Block的內存管理這個模塊一直不是很清楚,這個周末好好的看了下Block的原理,有些許心得。

為了性能,默認Block都是分配在stack上面的,所以它的作用區域就是當前函數。

#include <stdio.h>

int main()
{
    int i = 1024;
    void (^blk)(void) = ^ {
        printf("%d\n", i);
    };
    blk();
    return 0;
}

 在blk這個block里面是不能修改i的。Why?我們可以通過clang看看編譯器處理后的這塊代碼

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int i;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int i = __cself->i; // bound by copy

        printf("%d\n", i);
    }

static struct __main_block_desc_0 {
  unsigned long reserved;
  unsigned long Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main()
{
    int i = 1024;
    void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i);
    ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
    return 0;
}
struct __block_impl是Block的一個內部結構體,原型是
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

每個block都有個默認的構造函數

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) 所以只能讀取i,而不能修改i,當你試圖修改它時,編譯器就在預處理階段直接報錯。

只要在i前加__Block變量就可以在Block里面修改i值了,此時由值類型變為引用類型

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *i;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *i = __cself->i; // bound by copy

        printf("%d\n", (*i));
    }

static struct __main_block_desc_0 {
  unsigned long reserved;
  unsigned long Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main()
{
    static int i = 1024;
    void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &i);
    ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
    return 0;
}

上面的代碼塊是將int i的類型修改為__Block int i = 1024;后編譯器生成代碼塊,可以看到__main_block_impl_0中的 i類型已經改變為int *,所以我們可以修改它的值。

所以只要沒對Block進行copy操作,它一直存在stack里面。不管是否有__block修飾符

要想延長Block的作於域,我們可以對它進行copy操作,apple提供的接口是Block_Copy()方法

/* Copy, or bump refcount, of a block.  If really copying, call the copy helper if present. */
static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;

    //printf("_Block_copy_internal(%p, %x)\n", arg, flags);    
    if (!arg) return NULL;
    
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GC) {
        // GC refcounting is expensive so do most refcounting here.
        if (wantsOne && ((latching_incr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK) == 1)) {
            // Tell collector to hang on this - it will bump the GC refcount version
            _Block_setHasRefcount(aBlock, true);
        }
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }

    // Its a stack block.  Make a copy.
    if (!isGC) {
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return (void *)0;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 1;
        result->isa = _NSConcreteMallocBlock;
        if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
            //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
            (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        }
        return result;
    }
    else {
        // Under GC want allocation with refcount 1 so we ask for "true" if wantsOne
        // This allows the copy helper routines to make non-refcounted block copies under GC
        unsigned long int flags = aBlock->flags;
        bool hasCTOR = (flags & BLOCK_HAS_CTOR) != 0;
        struct Block_layout *result = _Block_allocator(aBlock->descriptor->size, wantsOne, hasCTOR);
        if (!result) return (void *)0;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        // if we copy a malloc block to a GC block then we need to clear NEEDS_FREE.
        flags &= ~(BLOCK_NEEDS_FREE|BLOCK_REFCOUNT_MASK);   // XXX not needed
        if (wantsOne)
            flags |= BLOCK_IS_GC | 1;
        else
            flags |= BLOCK_IS_GC;
        result->flags = flags;
        if (flags & BLOCK_HAS_COPY_DISPOSE) {
            //printf("calling block copy helper...\n");
            (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        }
        if (hasCTOR) {
            result->isa = _NSConcreteFinalizingBlock;
        }
        else {
            result->isa = _NSConcreteAutoBlock;
        }
        return result;
    }
}
View Code

通過觀察apple提供的block源碼,我們可以看到copy方法將block從statck拷貝到heap里面,所以它的作用區域延長了

待完成:1.block與oc的混合

    2.__block修飾符與oc的混合

總結

1.block默認都是分配在stack,當copy后,它移到heap里

2.block中的變量默認是不能修改的,只有添加__Block修飾符后才能修改

3.block中有oc對象時,會_Block_retain_object(object),直到block銷毀后才會_Block_release_object(object);

4.對block進行copy時

  • If you access an instance variable by reference, a strong reference is made to self;

  • If you access an instance variable by value, a strong reference is made to the variable.

它會將self進行copy,此時改對象的dealloc方法不會執行(因為它的引用計數歸0),解決此問題有2種方法:在block執行完成后面立即Block_Release(),或者將改變量聲明為__Block類型(Why?)

 

繼續補充block的oc的混合

//
//  main.m
//  block
//
//  Created by lijian on 13-8-9.
//  Copyright (c) 2013年 YOUKU. All rights reserved.
//

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
    
    NSMutableString *str = [NSMutableString stringWithFormat:@"lijian"];
    
    void (^blk)(void) = ^ {
        NSLog(@"%@", str);
    };
    blk();
    
    return 0;
}
View Code

編譯器生成代碼為

//
//  main.m
//  block
//
//  Created by lijian on 13-8-9.
//  Copyright (c) 2013年 lijian. All rights reserved.
//

#include <Foundation/Foundation.h>

int main(int, const char **);

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSMutableString *str;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableString *_str, int flags=0) : str(_str) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSMutableString *str = __cself->str; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl_main_m_1, str);
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __main_block_desc_0 {
  unsigned long reserved;
  unsigned long Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main (int argc, const char * argv[]) {
    
    NSMutableString *str = ((id (*)(id, SEL, NSString *, ...))(void *)objc_msgSend)(objc_getClass("NSMutableString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl_main_m_0);
    
    void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, str, 570425344);
    ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
    
    return 0;
}
View Code

blk的原型為

    void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, str, 570425344);

其中570425344 = BLOCK_HAS_COPY_DISPOSE | BLOCK_HAS_DESCRIPTOR;

通過構造函數,我們看到仍然是值傳遞,所以blk中是能不修改str的。

至於上面的__main_block_copy_0和__main_block_dispose_0 就是用於Block_Copy()和Block_Release();

當我將str的類型修改為__block NSMutableString時,生成如下代碼

int main(int, const char **);
struct __Block_byref_str_0 {
  void *__isa;
__Block_byref_str_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSMutableString *str;
};
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_str_0 *str; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_str_0 *_str, int flags=0) : str(_str->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_str_0 *str = __cself->str; // bound by ref

        (str->__forwarding->str) = ((id (*)(id, SEL, NSString *, ...))(void *)objc_msgSend)(objc_getClass("NSMutableString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl_main2_m_1);
        NSLog((NSString *)&__NSConstantStringImpl_main2_m_2, (str->__forwarding->str));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  unsigned long reserved;
  unsigned long Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main (int argc, const char * argv[]) {
    
    __block __Block_byref_str_0 str = {(void*)0,(__Block_byref_str_0 *)&str, 33554432, sizeof(__Block_byref_str_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((id (*)(id, SEL, NSString *, ...))(void *)objc_msgSend)(objc_getClass("NSMutableString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl_main2_m_0)};
    
    void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (struct __Block_byref_str_0 *)&str, 570425344);
    ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
    
    return 0;
}
View Code

可以看到str的類型實際上是__Block_byref_str_0,其中33554432 = BLOCK_HAS_COPY_DISPOSE = (1 << 25)

而blk的構造函數中傳遞的是__Block_byref_str_0類型的指針,所以我們能在blk中修改str

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM