How to Pass in a Void Block to Objc_Setassociatedobject in Swift

How do I pass in a void block to objc_setAssociatedObject in swift

The problem is closures are not objects that conform to AnyObject so you can't store them like that from Swift.

You could look into wrapping the closures in a class with a single property. Something like:

class ClosureWrapper {
var closure: (() -> Void)?

init(_ closure: (() -> Void)?) {
self.closure = closure
}
}

var tapAction: (() -> Void)? {
get {
if let cl = objc_getAssociatedObject(self, "key") as? ClosureWrapper {
return cl.closure
}
return nil
}

set {
objc_setAssociatedObject(
self,
"key",
ClosureWrapper(newValue),
UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)
)
}
}

It's a little ugly and could use a nice typealias but it's one way to do it.

How to implement objc_setAssociatedObject with different block in Swift?

Just do as below:

class BlockWrapper<T> {
let block: T?
init (_ b: T?) { self.block = b }
}

public typealias YourBlock = (param: [String:String]) -> Bool
public typealias YourBlock2 = () -> Bool

extension UIButton {
@nonobjc static var iActionBlockKey = Int8(0);
public var actionBlock: YourBlock? {
set {
objc_setAssociatedObject(self, &UIButton.iActionBlockKey, BlockWrapper<YourBlock>(newValue), .OBJC_ASSOCIATION_RETAIN)
}
get {
let wrapper = objc_getAssociatedObject(self, &UIButton.iActionBlockKey) as? BlockWrapper<YourBlock>
return wrapper?.block
}
}
@nonobjc static var iActionBlockKey2 = Int8(0);
public var actionBlock2: YourBlock2? {
set {
objc_setAssociatedObject(self, &UIButton.iActionBlockKey2, BlockWrapper<YourBlock2>(newValue), .OBJC_ASSOCIATION_RETAIN)
}
get {
let wrapper = objc_getAssociatedObject(self, &UIButton.iActionBlockKey2) as? BlockWrapper<YourBlock2>
return wrapper?.block
}
}
}

How do I declare NULL/Void in swift when I don't need to use a completion block?

Apple's prerelease documentation says:

func presentViewController(_ viewControllerToPresent: UIViewController!,
animated flag: Bool,
completion completion: (() -> Void)!)

Parameters

viewControllerToPresent

The view controller to display over the current view controller’s content.

flag

Pass true to animate the presentation; otherwise, pass false.

completion

The block to execute after the presentation finishes. This block has no return value and takes no parameters. You may specify nil for this parameter.

Therefore you can specify nil for this parameter.

You can access this prerelease documentation from your iOS Dev Center

Using objc_setAssociatedObject with weak references

After trying it out, the answer is NO.

I ran the following code under the iOS 6 Simulator, but it would probably have the same behavior with previous iterations of the runtime:

NSObject *test1 = [NSObject new];

NSObject __weak *test2 = test1;

objc_setAssociatedObject(self, "test", test1, OBJC_ASSOCIATION_ASSIGN);

test1 = nil;

id test3 = objc_getAssociatedObject(self, "test");

In the end, test1 and test2 are nil, and test3 is the pointer previously stored into test1. Using test3 would result in trying to access an object that had already been dealloced.

Can I pass a block as a @selector with Objective-C?

Yes, but you'd have to use a category.

Something like:

@interface UIControl (DDBlockActions)

- (void) addEventHandler:(void(^)(void))handler
forControlEvents:(UIControlEvents)controlEvents;

@end

The implementation would be a bit trickier:

#import <objc/runtime.h>

@interface DDBlockActionWrapper : NSObject
@property (nonatomic, copy) void (^blockAction)(void);
- (void) invokeBlock:(id)sender;
@end

@implementation DDBlockActionWrapper
@synthesize blockAction;
- (void) dealloc {
[self setBlockAction:nil];
[super dealloc];
}

- (void) invokeBlock:(id)sender {
[self blockAction]();
}
@end

@implementation UIControl (DDBlockActions)

static const char * UIControlDDBlockActions = "unique";

- (void) addEventHandler:(void(^)(void))handler
forControlEvents:(UIControlEvents)controlEvents {

NSMutableArray * blockActions =
objc_getAssociatedObject(self, &UIControlDDBlockActions);

if (blockActions == nil) {
blockActions = [NSMutableArray array];
objc_setAssociatedObject(self, &UIControlDDBlockActions,
blockActions, OBJC_ASSOCIATION_RETAIN);
}

DDBlockActionWrapper * target = [[DDBlockActionWrapper alloc] init];
[target setBlockAction:handler];
[blockActions addObject:target];

[self addTarget:target action:@selector(invokeBlock:) forControlEvents:controlEvents];
[target release];

}

@end

Some explanation:

  1. We're using a custom "internal only" class called DDBlockActionWrapper. This is a simple class that has a block property (the block we want to get invoked), and a method that simply invokes that block.
  2. The UIControl category simply instantiates one of these wrappers, gives it the block to be invoked, and then tells itself to use that wrapper and its invokeBlock: method as the target and action (as normal).
  3. The UIControl category uses an associated object to store an array of DDBlockActionWrappers, because UIControl does not retain its targets. This array is to ensure that the blocks exist when they're supposed to be invoked.
  4. We have to ensure that the DDBlockActionWrappers get cleaned up when the object is destroyed, so we're doing a nasty hack of swizzling out -[UIControl dealloc] with a new one that removes the associated object, and then invokes the original dealloc code. Tricky, tricky. Actually, associated objects are cleaned up automatically during deallocation.

Finally, this code was typed in the browser and has not been compiled. There are probably some things wrong with it. Your mileage may vary.

Can I use Objective-C blocks as properties?

@property (nonatomic, copy) void (^simpleBlock)(void);
@property (nonatomic, copy) BOOL (^blockWithParamter)(NSString *input);

If you are going to be repeating the same block in several places use a type def

typedef void(^MyCompletionBlock)(BOOL success, NSError *error);
@property (nonatomic) MyCompletionBlock completion;

What is objc_setAssociatedObject() and in what cases should it be used?

From the reference documents on Objective-C Runtime Reference:

You use the Objective-C runtime
function objc_setAssociatedObject to
make an association between one object
and another. The function takes four
parameters: the source object, a key,
the value, and an association policy
constant. The key is a void pointer.

  • The key for each association must be unique. A typical pattern is to
    use a static variable.
  • The policy specifies whether the associated object is assigned,

    retained, or copied, and whether the

    association is be made atomically or

    non-atomically. This pattern is

    similar to that of the attributes of

    a declared property (see “Property

    Declaration Attributes”). You specify
    the policy for the relationship using
    a constant (see

    objc_AssociationPolicy and

    Associative Object Behaviors).

Establishing an association between an array and a string

static char overviewKey;

NSArray *array =

[[NSArray alloc] initWithObjects:@"One", @"Two", @"Three", nil];

// For the purposes of illustration, use initWithFormat: to ensure

// the string can be deallocated

NSString *overview =

[[NSString alloc] initWithFormat:@"%@", @"First three numbers"];

objc_setAssociatedObject (

array,

&overviewKey,

overview,

OBJC_ASSOCIATION_RETAIN

);

[overview release];

// (1) overview valid

[array release];

// (2) overview invalid

At point 1, the string overview is
still valid because the
OBJC_ASSOCIATION_RETAIN policy
specifies that the array retains the
associated object. When the array is
deallocated, however (at point 2),
overview is released and so in this
case also deallocated. If you try to,
for example, log the value of
overview, you generate a runtime
exception.

Is there a way to set associated objects in Swift?

Here is a simple but complete example derived from jckarter's answer.

It shows how to add a new property to an existing class. It does it by defining a computed property in an extension block. The computed property is stored as an associated object:

import ObjectiveC

// Declare a global var to produce a unique address as the assoc object handle
private var AssociatedObjectHandle: UInt8 = 0

extension MyClass {
var stringProperty:String {
get {
return objc_getAssociatedObject(self, &AssociatedObjectHandle) as! String
}
set {
objc_setAssociatedObject(self, &AssociatedObjectHandle, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}

EDIT:

If you need to support getting the value of an uninitialized property and to avoid getting the error unexpectedly found nil while unwrapping an Optional value, you can modify the getter like this:

    get {
return objc_getAssociatedObject(self, &AssociatedObjectHandle) as? String ?? ""
}

Adding a closure as target to a UIButton

Do Not Use This Answer, See Note Below

NOTE:
like @EthanHuang said
"This solution doesn't work if you have more than two instances. All actions will be overwrite by the last assignment."

Keep in mind this when you develop, i will post another solution soon.

If you want to add a closure as target to a UIButton, you must add a function to UIButton class by using extension

Swift 5

import UIKit    
extension UIButton {
private func actionHandler(action:(() -> Void)? = nil) {
struct __ { static var action :(() -> Void)? }
if action != nil { __.action = action }
else { __.action?() }
}
@objc private func triggerActionHandler() {
self.actionHandler()
}
func actionHandler(controlEvents control :UIControl.Event, ForAction action:@escaping () -> Void) {
self.actionHandler(action: action)
self.addTarget(self, action: #selector(triggerActionHandler), for: control)
}
}

Older

import UIKit

extension UIButton {
private func actionHandleBlock(action:(() -> Void)? = nil) {
struct __ {
static var action :(() -> Void)?
}
if action != nil {
__.action = action
} else {
__.action?()
}
}

@objc private func triggerActionHandleBlock() {
self.actionHandleBlock()
}

func actionHandle(controlEvents control :UIControlEvents, ForAction action:() -> Void) {
self.actionHandleBlock(action)
self.addTarget(self, action: "triggerActionHandleBlock", forControlEvents: control)
}
}

and the call:

 let button = UIButton()
button.actionHandle(controlEvents: .touchUpInside,
ForAction:{() -> Void in
print("Touch")
})

Avoid extra static variables for associated objects keys

According to this blog entry by Erica Sadun (whose credits go to Gwynne Raskind), there is.

objc_getAssociatedObject and objc_getAssociatedObject require a key to store the object. Such key is required to be a constant void pointer. So in the end we just need a fixed address that stays constant over time.

It turns out that the @selector implementation provides just about what we need, since it uses fixed addresses.

We can therefore just get rid of the key declaration and simply use our property's selector address.

So if you are associating at runtime a property like

@property (nonatomic, retain) id anAssociatedObject;

we can provide dynamic implementations for its getter/setter that look like

- (void)setAnAssociatedObject:(id)newAssociatedObject {
objc_setAssociatedObject(self, @selector(anAssociatedObject), newAssociatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)anAssociatedObject {
return objc_getAssociatedObject(self, @selector(anAssociatedObject));
}

Very neat and definitely cleaner than defining an extra static variable key for every associated object.

Is this safe?

Since this is implementation-dependent, a legitimate question is: will it easily break?
Quoting the blog entry

Apple would probably have to implement a completely new ABI for that to happen

If we take those words to be true, it's then reasonably safe.



Related Topics



Leave a reply



Submit