[iOS] 七七八八的小姿势(2)

目录:
  1. NSProxy
  2. 字典集合对成员的引用方式
  3. class判断
  4. block变量捕获

1. NSProxy

@interface NSProxy  {
    Class   isa;
}

NSProxy遵守了 NSObject 协议,主要用于消息转发。NSProxy与NSObject一样是根类,都遵守协议,只是NSProxy是抽象类的根类。NSProxy的使用也非常简单,通常,你只需要实现两个方法:

- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
正常NSObject消息转发

设计的转发类继承自NSProxy而不是NSObject,是因为给NSProxy发送消息时只会在当前类中查找方法,一旦找不到就执行消息转发操作,相比NSObject少去了递归父类查找方法等流程,效率更高(它的父类并不是NSObject)

对于 NSProxy 的消息转发就没有这么复杂了,接收到 unknown selector 后,直接回调- (NSMethodSignature *)methodSignatureForSelector:- (void)forwardInvocation:,消息转发过程简单的很多。


※ 举个例子
// .h文件
#import 

NS_ASSUME_NONNULL_BEGIN

@interface ProxyTest : NSProxy

+ (id)proxyForObject:(id)obj;

@end

NS_ASSUME_NONNULL_END

// .m文件
#import "ProxyTest.h"

@interface ProxyTest() {
    id _object;
}

@end


@implementation ProxyTest

+ (id)proxyForObject:(id)obj {
    ProxyTest *instance = [ProxyTest alloc];
    instance->_object = obj;
    
    return instance;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [_object methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    if ([_object respondsToSelector:invocation.selector]) {
        NSString *selectorName = NSStringFromSelector(invocation.selector);
        
        NSLog(@"Before calling \"%@\".", selectorName);
        [invocation invokeWithTarget:_object];
        NSLog(@"After calling \"%@\".", selectorName);
    }
}

@end

// 使用
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    
NSURL *url = [ProxyTest proxyForObject:[NSURL URLWithString:@"https://www.google.com"]];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    dispatch_semaphore_signal(sem);
}];

[task resume];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

// 输出:
2019-12-07 09:24:54.114677+0800 Example1[47127:474396] Before calling "absoluteURL".
2019-12-07 09:24:54.114795+0800 Example1[47127:474396] After calling "absoluteURL".

这里当我们用ProxyTest的时候,ProxyTest是木有NSURL的方法的,所以会走消息转发methodSignatureForSelectorforwardInvocation,然后在forwardInvocation里其实就是把所有的操作都转发给了NSURL,只不过是加了一些log。其实这个感觉有点AOP嘿嘿~


※ 应用场景

我们其实是拿NSProxy做消息转发用的,也就是Flux中的Dispatcher~

(1)虚拟多重继承

这里并不是真的继承,只是实现了可以将消息转发给不同的类,类似用上面的ProxyTest可以做到:

ProxyTest *proxy = [ProxyTest proxyForObject:[NSURL URLWithString:@"https://www.google.com"]];
NSURL *absoluteStr = [proxy performSelector:@selector(absoluteURL)];
NSLog(@"absoluteStr : %@", absoluteStr);

proxy = [ProxyTest proxyForObject:@"lalala"];
NSInteger length = [proxy performSelector:@selector(length)];
NSLog(@"text length : %ld", length);

这个其实就是先转发给了NSURL,然后又转发给了NSString。

但现在其实实现的是每次只能转发给一个类,如果想做多个类,可以在ProxyTest里面维护一个array/set/hashtable,然后每次遍历里面的数组,将消息转发给每个对象。

(2)NSTimer避免循环引用

我们之前都是利用category实现的防止NSTimer持有self,这里也可以尝试一下用Proxy做哦~

强推这篇吖!https://www.jianshu.com/p/d4589134358a

image

大概就是让一个Proxy对象弱持有self,timer强持有Proxy~

(3)Lazy Initialization懒加载

这一点其实我并木有查到例子,但是我猜大概就是你只要创建一个NXProxy对象,然后当某个方法被调用的时候,再去创建一个开销比较大的对象(懒加载),这样如果这个大对象的方法不被调用,他就不会被创建。避免了你必须创建一个大对象,再去调用他的方法。


※ NSMethodSignature

NSMethodSignature对象记录着某个方法的返回值类型信息以及参数类型信息。主要是在消息转发的时候用的~

methodSignatureForSelector可以看对象的方法签名,instanceMethodSignatureForSelector可以看类的方法签名。

举个例子:

NSMethodSignature * sign1 = [self methodSignatureForSelector:@selector(testBlock)];
NSMethodSignature * sign2 = [self methodSignatureForSelector:@selector(testStringBlock)];
NSMethodSignature * sign3 = [UIView instanceMethodSignatureForSelector:@selector(setBackgroundColor:)];

- (void)testBlock {
    ……
}

- (void)testStringBlock {
   ……
}

然后我们打断点看下sign是神马~

sign1   NSMethodSignature * 0x600003964420  0x0000600003964420
sign2   NSMethodSignature * 0x600003964420  0x0000600003964420
sign3   NSMethodSignature * 0x600003977630  0x0000600003977630

Printing description of sign1:

    number of arguments = 2
    frame size = 224
    is special struct return? NO
    return value: -------- -------- -------- --------
        type encoding (v) 'v'
        flags {}
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
        memory {offset = 0, size = 0}
    argument 0: -------- -------- -------- --------
        type encoding (@) '@'
        flags {isObject}
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
        memory {offset = 0, size = 8}
    argument 1: -------- -------- -------- --------
        type encoding (:) ':'
        flags {}
        modifiers {}
        frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
        memory {offset = 0, size = 8}

Printing description of sign3:

    number of arguments = 3
    frame size = 224
    is special struct return? NO
    return value: -------- -------- -------- --------
        type encoding (v) 'v'
        flags {}
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
        memory {offset = 0, size = 0}
    argument 0: -------- -------- -------- --------
        type encoding (@) '@'
        flags {isObject}
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
        memory {offset = 0, size = 8}
    argument 1: -------- -------- -------- --------
        type encoding (:) ':'
        flags {}
        modifiers {}
        frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
        memory {offset = 0, size = 8}
    argument 2: -------- -------- -------- --------
        type encoding (@) '@'
        flags {isObject}
        modifiers {}
        frame {offset = 16, offset adjust = 0, size = 8, size adjust = 0}
        memory {offset = 0, size = 8}

只要返回值和参数类型都相同,那么不论方法名是否相同,都会返回同一个方法签名对象。(sign1和sign2)

testBlock虽然木有参数,为什么有两个argu呢?因为任何消息都会默认有两个隐参数,self和_cmd。

一个方法签名包含代表方法返回值的一个或多个字符,后面跟上隐式参数self以及_cmd的字符串编码,然后后面再跟上零个或多个明确的参数的字符编码。

type encoding又是神马呢?

NSMethodSignature对象是根据字符串创建的,这里的字符串代表了某个方法的返回值类型以及参数类型的字符串编码。你可以使用编译器命令:encode()来获取特定类型的字符串编码。

例如:NSString的实例方法containsString:的方法签名包含以下参数:

1.返回值:BOOL类型, @encode(BOOL) ——c

2.方法接收者(self):id类型,@encode(id)——@

3.方法名(_cmd):SEL,@encode(SEL)——:

4.方法参数:NSString类型,@encode(NSString *)——@

2. 字典集合对成员的引用方式

主要是NSCache, NSHashTable, NSMapTable, NSMutableDictionary之间的区别~

※ NSCache和NSMutableDictionary
  • NSMutableDictionary会对key进行copy,NSCache的Key只是对对象进行了Strong引用,而非拷贝,所以NSMutableDictionary的key要遵守NSCopy协议。
  • NSCache线程安全, NSMutableDictionary非线程安全。
  • NSCache有对象淘汰策略,通过在存值的时候指定cost,当总cost或者key-value个数达到limit时,会进行对象释放(非LRU算法)。这种淘汰策略在没收到内存警告是也会执行,从而降低内存峰值(minimizing its memory footprint)。
  • Retrieving something from an NSCache object returns an autoreleased result.

※ NSHashTable和NSSet

NSHashTable 是 NSSet 的通用版本,他们之间的区别是:

  • NSSet / NSMutableSet 持有成员的强引用,通过 hash 和 isEqual: 方法来检测成员的散列值和相等性。
  • NSHashTable 是可变的,没有不可变的对应版本。
  • NSHashTable 可以持有成员的弱引用。
  • NSHashTable 可以在加入成员时进行 copy 操作。NSPointerFunctionsCopyIn
  • NSHashTable 可以存储任意的指针,通过指针来进行相等性和散列检查。
  • NSHashTable 能保存对象弱引用, 对象释放后能从中移除用法。[NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
NSHashTableStrongMemory:和 NSPointerFunctionsStrongMemory相同,使用此选项为默认的行为,和NSSet的内存策略相同。

NSHashTableWeakMemory:和 NSPointerFunctionsWeakMemory相同,此选项使用weak存储对象,当对象被销毁的时候自动将其从集合中移除。

NSHashTableCopyIn :和 NSPointerFunctionsCopyIn 相同,此选项在对象被加入到集合之前copy它们。

NSHashTableObjectPointerPersonality:和 NSPointerFunctionsObjectPointerPersonality相同,此选项是直接使用指针进行isEqual:和 hash。

※ NSMapTable和NSMutableDictionary

NSMapTable 是 NSMutableDictionary 的增强版。它和NSHashTable有异曲同工的地方~

  • NSMapTable是可变的,没有一个不变的类与其对应。
  • NSMapTable 可以对其 key和 value弱引用,在这种情况下当key或者value被释放的时候,此entry会自动从NSMapTable中移除。
  • NSMapTable 在加入一个(key,value)的时候,可以对其value设置为copy。
  • NSMapTable可以包含任意指针,使用指针去做相等或者hashing检查。
NSMapTableStrongMemory:指定对应的key或者value为强引用。 

NSMapTableWeakMemory:指定对应的key或者value为弱引用。 

NSMapTableCopyIn:指定对应的key或者value在加入到集合中的时候为copy。 

NSMapTableObjectPointerPersonality:此选项是直接使用指针进行isEqual:和 hash 。

下面的NSMapTable例子中,NSMapTable对象在初始化的时候使用options去分别指定keys和values的行为,key不是copy的(强引用的),value为弱引用:

id delegate = ...;
NSMapTable *mapTable = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory
                                             valueOptions:NSMapTableWeakMemory];
[mapTable setObject:delegate forKey:@"foo"];

3. class判断

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

+ (BOOL)instancesRespondToSelector:(SEL)sel {
    if (!sel) return NO;
    return class_respondsToSelector(self, sel);
}

+ (BOOL)respondsToSelector:(SEL)sel {
    if (!sel) return NO;
    return class_respondsToSelector_inst(object_getClass(self), sel, self);
}

- (BOOL)respondsToSelector:(SEL)sel {
    if (!sel) return NO;
    return class_respondsToSelector_inst([self class], sel, self);
}

| + (Class)class 和- (Class)class 均返回类本身
| + (BOOL)isMemberOfClass: 是判断该类的元类是否和class相等
| - (BOOL)isMemberOfClass: 判断该类是和class相等
| + (BOOL)isKindOfClass: 循环遍历该类的元类的父类,判断是否和class相等
| - (BOOL)isKindOfClass: 循环遍历该类的父类,判断是否和class相等
| + (BOOL)instancesRespondToSelector 该类是否能响应实例方法
| + (BOOL)respondsToSelector: 该类是能否响应类方法
| - (BOOL)respondsToSelector: 该对象是否能响应实例方法

class关系

猜一下下面这我碰巧看到的题目输出是啥~

@interface Sark : NSObject
@end
@implementation Sark
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
        BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
        BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
        BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
        NSLog(@"%d %d %d %d", res1, res2, res3, res4);
    }
    return 0;
}

输出是1000~

因为其实NSObject class是一个类对象,而非实例变量,它会去看object_getClass(NSObject的class),得到的就是NSObject的metaClass,此时两者不相等,然后下一个循环会看NSObject的metaClass的superClass,也就是NSObject的class,此时对比就是相等的啦,所以res1是true。

而NSObject类的class是metaclass,是不等于[NSObject class]的,所以res2是false。

Sark class的class是Sark的metaclass,此时[Sark class]是不等于Sark的metaclass的,然后看Sark的metaclass的父类可能是NSObject的metaclass,这个时候和Sark class也是不想等的,然后在看NSObject的metaclass的superclass也就是NSObject class,仍旧是不等于Sark class,最后看NSObject class的superclass就是nil了,所以始终没有找到相等的,故而res3为false。

最后就是Sark class的class和Sark class是不一样的,所以res4是false。


有KVO的情况下是神马样子的呢?

KVOTest是一个有name属性的类即可。

KVOTest *test1 = [[KVOTest alloc] init];
[test1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
Class class1 = object_getClass(test1);
Class class2 = [test1 class];
NSLog(@"object_getClass: %@, class: %@", class1, class2);

输出:
object_getClass: NSKVONotifying_KVOTest, class: KVOTest

KVO其实改了class这个method返回的东西,所以用class的时候返回的还是原类,但是object_getClass是直接拿的isa,也就是NSKVONotifying_KVOTest~

4. block变量捕获

这个其实是一个遗留问题了,之前小哥哥问过我,但是一直没仔细看,所以先来看下木有__block修饰的捕获什么样子~

image
① auto 正常的变量
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        
        void (^block)(void) = ^() {
            int x = age + 1;
        };

        age = 20;
        block();
    }
    return 0;
}

这段如果打印block里面的x是11还是21呢??答案是11,这是为啥嘞?我们用xcrun将代码转为C++看一下~ xcrun clang -arch arm64 -rewrite-objc test.m

OC语言基于C/C++语言,实际上要先经过一层编译将OC代码转换为C++代码

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

            int x = age + 1;
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int age = 10;

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

        age = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

可以看到__main_block_impl_0的参数是age,然后__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age)将age传给了__main_block_impl_0的成员变量age,其实就是值的传递,所以block里面用的都是它自己的成员变量,而非外部的age,故而即使外部的age变了,里面的仍旧是10。

注意哦,如果不用block修饰的时候,是不允许在block里面对age再次赋值的。(我猜是因为没有意义,毕竟本来就是block里面的一个变量,即使修改了它也没有改到外边……

上面是基础变量,下面试一下OC对象是什么样子的~

例如:

#import 

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableString *age = [@"10" mutableCopy];
        void (^block)(void) = ^() {
            NSString *x = age;
        };

        age = [@"20" mutableCopy];
        block();
    }
    return 0;
}
  • 关于clang可以参考:https://www.jianshu.com/p/80240af0bac6

clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.2.sdk test.m编译后为:

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

            NSString *x = age;
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

static struct __main_block_desc_0 {
  size_t reserved;
  size_t 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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        NSMutableString *age = ((id (*)(id, SEL))(void *)objc_msgSend)((id)&__NSConstantStringImpl__var_folders_45_0__442k93wdgt5nxfz7s8v8m0000gn_T_test_c69637_mi_0, sel_registerName("mutableCopy"));
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, 570425344));

        age = ((id (*)(id, SEL))(void *)objc_msgSend)((id)&__NSConstantStringImpl__var_folders_45_0__442k93wdgt5nxfz7s8v8m0000gn_T_test_c69637_mi_1, sel_registerName("mutableCopy"));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

可以看到这里传入block的仍旧是age的值void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, 570425344));,所以即使外部改了age的指针,block内部仍旧是传入那一刻的age。

注意这里是因为age换了指针,如果是下面这种是会按照改变之后的来的,因为其实block捕获的是那个时间点的age指针:

NSMutableString *age = [@"10" mutableCopy];
void (^block)(void) = ^() {
    NSString *x = age;
    NSLog(@"x: %@", age);
};

[age setString:@"aaa"];
block();

输出:
2020-06-19 17:11:00.922187+0800 Example1[57189:1136755] x: aaa

② static

如果是static的变量,是可以直接在block内赋值的,不会error。并且其实block内的age是20而非10,这是为神马呢?

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static int age = 10;
        
        void (^block)(void) = ^() {
            age = 15;
        };

        age = 20;
        block();
    }
    return 0;
}

我们还是编译C++看一下:

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

            (*age) = 15;
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        static int age = 10;

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &age));

        age = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

可以看到这里__main_block_impl_0的成员变量是int *age;,也就是age传入的是&age,也就是age的地址,而非age的值,于是其实block内保存的也是age的地址,所以外部如果改了这个变量的值,block内也是跟着变的。

③ 全局变量
int age = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^() {
            age = 15;
        };

        age = 20;
        block();
    }
    return 0;
}

全局变量肯定是无论是block里还是外都可以正常去改它的,那么block有木有把它捕获进block呢?我们来看下代码还是:

int age = 10;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            age = 15;
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        age = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

可以看出来age并没有被当做参数放入block~ age本来就是全部变量,作用域是所有函数,生命周期是程序结束,所以哪里都能改,哪里都能输出他的值,所以没必要捕获进block的内部。

④ __block修饰

无论是对象还是变量,如果不是全局也不是static,想要在block里面赋值,都要加__block,否则会报红无法编译的~

那么__block做了什么呢~

#import 

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block NSMutableString *age = [@"10" mutableCopy];
        void (^block)(void) = ^() {
            NSString *x = age;
        };

        age = [@"20" mutableCopy];
        block();
    }
    return 0;
}

注意这个当加了__block以后,里面变了外部也会变,外部变了里面也会变,举个~

__block NSMutableString *age = [@"10" mutableCopy];
void (^block)(void) = ^() {
    NSLog(@"age enter:%@", age);
    age = [@"30" mutableCopy];
};

age = [@"20" mutableCopy];
block();
NSLog(@"age after:%@", age);

输出:
2019-12-01 21:50:56.307280+0800 Example1[40976:975793] age enter:20
2019-12-01 21:50:56.318338+0800 Example1[40976:975793] age after:30

具体是怎么实现的呢?我们clang看一下~ 先看下简单的int怎么做~

关于block的copy和捕获可以参考写的很好~ https://blog.csdn.net/wangyanchang21/article/details/79785308 以及 https://www.jianshu.com/p/d0a9eaebdd47

#import 

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int age = 10;
        void (^block)(void) = ^() {
            int x = age;
        };

        age = 20;
        block();
    }
    return 0;
}

// clang后:
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__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_age_0 *age = __cself->age; // bound by ref

            int x = (age->__forwarding->age);
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

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

static struct __main_block_desc_0 {
  size_t reserved;
  size_t 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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));

        (age.__forwarding->age) = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

主要是有个结构体:

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSMutableString *__strong age;
};

它里面的__forwarding指针指向的还是__Block_byref_age_0,所以你看到clang以后的代码很多是通过age.__forwarding->age来赋值的。

可以看到age的定义是酱紫的__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};,它的forwarding指向的是自己这个结构体。

block定义

并且传给block的时候是传入的age的地址:
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));

block里面使用的时候,也是用传入的age的forwarding指针来拿age的,所以里外其实用的是一个对象。


  • 为什么要用forwarding指针呢?

因为当block从栈上拷贝到堆上后,__block变量也会拷贝到堆上。这时就有两份__block变量,一份栈上的,一份堆上的。

block拷贝

如果__block修饰的变量是存放在栈上,这时forwarding指向的是它自己,这样没有问题。但是如果__block修饰的变量复制到堆上,它就会把栈上的forwarding指向堆上的变量,这样就能保证即使访问栈上的__block变量也能获取到堆上的变量值。

复制到heap以后的forwarding

其实就是统一指向了堆上的__Block_byref_age_0。


现在来看下string的形式clang以后的代码:

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSMutableString *__strong age;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__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_age_0 *age = __cself->age; // bound by ref

            NSString *x = (age->__forwarding->age);
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

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

static struct __main_block_desc_0 {
  size_t reserved;
  size_t 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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 33554432, sizeof(__Block_byref_age_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((id (*)(id, SEL))(void *)objc_msgSend)((id)&__NSConstantStringImpl__var_folders_45_0__442k93wdgt5nxfz7s8v8m0000gn_T_test_6f592c_mi_0, sel_registerName("mutableCopy"))};
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));

        (age.__forwarding->age) = ((id (*)(id, SEL))(void *)objc_msgSend)((id)&__NSConstantStringImpl__var_folders_45_0__442k93wdgt5nxfz7s8v8m0000gn_T_test_6f592c_mi_1, sel_registerName("mutableCopy"));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

可以参考一下https://www.jianshu.com/p/86fbe9dfb0f5

当block内部访问了对象类型的auto变量时:

  1. 如果block是在栈上,将不会对auto变量产生强引用(不管是 MRC 或者 ARC)。
  2. 如果block是在堆上,就说明block进行过copy操作,进行copy操作的block会自动调用block内部的__main_block_copy_0函数,__main_block_copy_0函数内部会根据auto变量的修饰符形成相应的强引用(retain)或者弱引用。
  3. 当block销毁时,block会自动调用内部的dispose函数,dispose函数会自动调用内部的__main_block_dispose_0释放引用的auto变量。

往回倒一下会发现其实 __block int也是有__main_block_copy_0和__main_block_dispose_0的,这是为神马呢?

其实也是因为age结构体在heap也会有一份,从stack复制到heap的时候就会执行copy啦,当heap被清掉就会执行dispose,道理是一样的。

__block修饰基本变量和修饰对象的区别是:
如果使用__block修饰的变量,block内部直接对其强引用;如果是对象类型的变量,会根据变量的修饰符__weak,__strong来决定是否强引用。

block对object的捕获不仅会捕获对象的指针,还会捕获对象的内存修饰符,如果是用weak修饰的对象,那么block在捕获的时候,在内部也会用weak的指针去指向它(这也就是为什么用weak可以解决block持有self的原因),如果是捕获的strong对象,那么内部捕获的时候也会用strong的临时变量来指向它。


  • 注意哦,如果在block内部给外部没有使用__block修饰的array类型的auto变量添加元素是OK的,因为其实相当于读取,没有改array的实际指针~

  • 另外其实我们打印出来的age是age结构体里面的成员变量age的地址,不是age结构体的哦

你可能感兴趣的:([iOS] 七七八八的小姿势(2))