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:
- 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. - 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 itsinvokeBlock:
method as the target and action (as normal). - The
UIControl
category uses an associated object to store an array ofDDBlockActionWrappers
, becauseUIControl
does not retain its targets. This array is to ensure that the blocks exist when they're supposed to be invoked. We have to ensure that theActually, associated objects are cleaned up automatically during deallocation.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 originaldealloc
code. Tricky, tricky.
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
functionobjc_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
How to Programmatically Check and Open an Existing App in iOS 8
Apple Healthkit - Background Updates Not Triggering
Setcollectionviewlayout' Animation Broken When Also Changing Collection View Frame
Swiftui Tabbedview Only Shows First Tab's Content
How to Get Date from String in Swift 3
Using Haneke to Cache Then Play Mp4 Files with Avplayer
Prevent Uitableview Scrolling Below a Certain Point
App Crashes When User Starts Typing in Uisearchbar
Firebase Get Child Id Swift iOS
How to Cache Images Only in Disk Using Kingfisher
Alpha for Background Color Not Working in iOS
Traverse View Controller Hierarchy in Swift
How Do View Types Like Text or Image Conform to the View Protocol in Swiftui
Embedding a Uitableview as a Container View Within a Uiviewcontroller
How to Make a Uiimage Scrollable Inside a Uiscrollview