Deepika Masilamani
Deepika Masilamani

Reputation: 139

How to implement copyWithZone in Swift 2.0?

The following code is written in Swift 2.0 to create an array of dispatch_block_t

let a: dispatch_block_t = {
    self.pickImages()
}
let b: dispatch_block_t = {
    self.takePicture()
}
let c: dispatch_block_t = {
    self.pickVideos()
}
let d: dispatch_block_t = {
    self.shootVideo()
}
let e: dispatch_block_t = {
    self.recordAudio()
}
let f: dispatch_block_t = {
    self.closeView()
}

let block1 = Block(block: a)
let block2 = Block(block: b)
let block3 = Block(block: c)
let block4 = Block(block: d)
let block5 = Block(block: e)
let block6 = Block(block: f)

let actionsArray: NSArray = [block1, block2, block3, block4, block5, block6]

And the Block class is defined as follows,

class Block: NSObject, NSCopying {
    var block: dispatch_block_t

    init(block: dispatch_block_t){
        self.block = block
    }

    func copyWithZone(zone: NSZone) -> AnyObject {
        return self.block as! AnyObject
    }
}

I could create an array of dispatch_block_t with the above piece of code. But, I need to pass this array as a parameter to another function and I am facing an issue in passing this array.

I am calling this function on a button click event,

menuView = btSimplePopUP(itemImage: imgs as [AnyObject],
    andTitles: titles as [AnyObject],
    andActionArray:  actionsArray as [AnyObject],
    addToViewController: self) 

And I am getting an error when the following snippet is executed,

- (instancetype)initWithImage:(UIImage *)image title:(NSString *)title action:(dispatch_block_t)action {
if ((self = [super init])) {
    _title = [title copy];
    _imageView = [[UIImageView alloc]initWithImage:image];
    _action = [action copy];
}

return self;
}

And the error is,

Could not cast value of type '() -> ()' (0x15164018) to 'Swift.AnyObject' (0x101e500c).

I am getting this error in copyWithZone function of Block class.

The complete source code for btSimplePopUp can be viewed here, https://github.com/balram3429/btSimplePopUp/blob/master/btSimplePopUp/btSimplePopUP.m

Upvotes: 1

Views: 520

Answers (2)

JeremyP
JeremyP

Reputation: 86651

dispatch_block_t is a struct, not an object. In fact it's a C struct. I don't understand why you need to use a dispatch_block_t, why not just pass the closure around. If you have to pass it to a dispatch_ function wrap it in a dispatch_block_t at the call site.

To Swiftify this:

In Swift, dispatch_block_t is simply an alias for () -> () i.e. a void function/closure returning Void. So you can simply say

let a = { self.pickImages() }
// etc

let actionArray = [ a, b, c, ...]

However, pickImages is a function that has the same type (technically it is curried over self but don't worry about what that means) so you can forget about the a, b, c bit and just do this

let actionArray = [ self.pickImages, self.takePhoto, ... ]

And in Swift, you can use one of the "objects" in that array any time you have a parameter that takes a dispatch_block_t e.g.

dispatch_after(someTime, dispatch_get_main_queue(), actionArray[0])

Upvotes: 1

user3441734
user3441734

Reputation: 17544

You need to modify you Objc framework. your btSimplePopUP should works like ActionContainer from this sample. ActionObject is boxed version of Swift dispatch_block_t. How to unbox it in your code? See ActionContainer job function.

//
//  Actions.h
//  test
//


#ifndef Actions_h
#define Actions_h
#import <Foundation/Foundation.h>

@interface ActionObject : NSObject
@property (nonatomic, copy) dispatch_block_t action;
@end


@interface ActionContainer : NSObject
@property  NSArray * actions;
-(void)job;
@end

#endif /* Actions_h */

...

//
//  Actions.m
//  test
//


#import <Foundation/Foundation.h>
#import "Actions.h"


@implementation ActionObject

-(instancetype)initWithAction: (dispatch_block_t)someaction {
    if ((self = [super init])) {
        _action = [someaction copy];
    }
    return self;
}

@end

@implementation ActionContainer
-(void)job {
    if (_actions != nil) {
        NSUInteger count = _actions.count;
        NSUInteger i;
        for (i = 0; i < count; i++) {
            ActionObject *action = _actions[i];
            action.action();
        }

    }
}

@end

...

//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#import "Actions.h"

...

//
//  main.swift
//  test
//


import Foundation

let a = ActionObject()
a.action = {
    print("swift a action")
}
let b = ActionObject()
b.action = {
    print("swift b action")
}
let arr: NSArray = [a,b]

let actions = ActionContainer()
actions.actions = arr as [AnyObject]
actions.job()

produce

swift a action
swift b action
Program ended with exit code: 0

...

@implementation ActionContainer
-(void)job {
    if (_actions != nil) {
        NSUInteger count = _actions.count;
        NSUInteger i;
        for (i = 0; i < count; i++) {
            ActionObject *action = _actions[i];
            // myAction is 'unboxed' swift block
            // you can save it, run it
            // somwhere in you ObjectiveC code
            dispatch_block_t myAction = action.action;
            // run it
            myAction();
        }

    }
}

Upvotes: 0

Related Questions