Slowly getting into Swift but still struggling with the completion blocks. How would the following code look like in Swift?
[self.eventStore requestAccessToEntityType:type completion:^(BOOL granted, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self alertViewWithDataClass:((type == EKEntityTypeEvent) ? Calendars : Reminders) status:(granted) ? NSLocalizedString(#"GRANTED", #"") : NSLocalizedString(#"DENIED", #"")];
});
}];
self.eventStore.requestAccessToEntityType(type) {
(granted: Bool, err: NSError!) in
dispatch_async(dispatch_get_main_queue()) {
...
}
}
for an example of working code, I was experimenting with this exact API in swift :)
Your Objective-C 'completion blocks' in Swift (now called 'closures' in this context) will contain all of the same information:
parameter labels and types (at the start of the block in parentheses)
return type (preceded by '->')
the keyword 'in' separating the signature from the code
Note that the signature of the method specifies the type for the parameters, so all you really need to do there is supply names for them :) (type inference FTW!) Additionally, your block returns 'Void' so we don't need to include the return type here, either.
That would give us:
self.eventStore.requestAccessToEntityType(type) { (granted, err) in
dispatch_async(dispatch_get_main_queue()) {
...other stuff...
}
}
Related
I'm a little new to Swift and Objective-C (and by that I mean I started last week) so if this is a duplicate question I'm sorry in advance.
In short I seem to be having trouble with calling a method. One part of the code is in Objective-C and the other part is in Swift. I imported the Objective-C file in to the Swift file through a bridging header file if that helps.
Declaration of method (in Objective-C):
- (NSUInteger)fetchTeamsForEventKey:(NSString *)eventKey withCompletionBlock:(void (^)(NSArray *teams, NSInteger totalCount, NSError *error))completionBlock {
NSString *apiMethod = [NSString stringWithFormat:#"event/%#/teams", eventKey];
NSUInteger taskId = [[TBAKit sharedKit] callArrayMethod:apiMethod modelClass:[TBATeam class] andCompletionBlock:^(NSArray *modelObjects, NSInteger totalCount, NSError *error) {
completionBlock(modelObjects, totalCount, error);
}];
return taskId;
}
Implementation of method (Swift):
TBAKit.fetchTeamsForEventKey("2015cacg", withCompletionBlock: {(teams:NSArray, totalCount:NSInteger, error:NSError) in //The error appears in this line.
if error {
NSLog("Unable to fetch event - %#", error.localizedDescription)
return
}
teamList = teams
})
The source code for the declaration method is in this GitHub Repo: https://github.com/ZachOrr/TBAKit. It's in the file TBAKit/TBAKit+EventMethods.m
Edit: I also tried the code with each of the following:
-> UInt
and
-> Int
Edit 2: So the solution mentioned by t4nhpt did work as it was intended to, but in turn another problem presented itself. I had to replace
teamList = teams
with
self.teamList = teams
Now I'm getting the error: "cannot assign a value of type 'anyobject' to a value of type '[TBATeam]'" on the line
self.teamList = teams
My declaration of the variable is
var teamList = [TBATeam]()
Edit 3: I solved the problem specified in EDIT 2 by converting each object inside teams to TBATeam. Sorry for the stupid mistake.
Try this:
TBAKit.sharedKit().fetchTeamsForEventKey("2015cacg") { (teams, totalCount, error) -> Void in
// your code
}
I'm using a framework that uses a huge amount of block arguments to emit events. Due to licensing I can't release code samples, but I can still get my point across with similar code. Lets say you have the following code:
- (void)begin
{
[MyService doSomething:^{
NSLog(#"Done.");
}];
}
This is fine, but the methods I'm dealing with have 14 block arguments, most of which take several parameters so it makes it very hard to read and difficult to test without creating repetitive boiler-plate code.
Is there a way I can point a block to an instance's method, something like:
- (void)doSomethingDelegate
{
NSLog(#"Done.");
}
- (void)begin
{
[MyService doSomething:CALLBACK(self, #selector(doSomethingDelegate))];
}
Where CALLBACK is a macro or native construct. Is this possible?
No, but you can declare local blocks:
- (void)begin
{
void (^callback)() = ^{ NSLog(#"Done."); };
[MyService doSomething:callback];
}
You could extend that to:
- (void)callbackImpl {
NSLog(#"Done.");
}
- (void)begin {
void (^callback)() = ^() { [self callbackImpl]; };
[MyService doSomething:callback];
}
And you could imagine a macro of the sort:
#define CALLBACK(name) void (^name)() = ^{ [self name##Impl]; };
It gets a little messier when you have arguments to the block, and you need to consider if you want a strong or weak reference to self in those blocks...
Since I also have to worry about arguments that are in a different from this is the best solution I've found so far:
#define wrapper(args, method) ^args { [self method]; }
- (void)onUpdatingDeviceWithMessage:(NSString *)message progress:(float)progress
{
}
- (void)begin
{
[MyService ...
onUpdatingDevice:wrapper((NSString *message, float progress),
onUpdatingDeviceWithMessage:message progress:progress)
}
I am trying to use a library which is written in objective c in my swift application.
I tried to translate a snippet from the readme to swift code - But I get a type error I don't understand.
Obj.C code from readme:
[self.client logonWithUsername:self.username.text password:self.password.text responseCallback:^(NSDictionary *response, NSError *error) {
if (error) {
[self handleFailedAuth:error];
return;
}
[self handleSuccessfulAuth];
}];
My translation to swift:
var username = NSUserDefaults.standardUserDefaults().stringForKey("username")
var password = NSUserDefaults.standardUserDefaults().stringForKey("password")
client.logonWithUsername(username, password: password, responseCallback: {
(response: NSDictionary, error: NSError) in
if(error){
handleFailedAuth(error)
return;
}
handleSuccessfulAuth()
}
)
I Get [NSObject: AnyObject]! is not a subtype of NSDictionary on the line where the parameters of the closure are defined. How is that possible? I am using the same types as in the example.
Your Swift should probably read the following:
var username = NSUserDefaults.standardUserDefaults().stringForKey("username")
var password = NSUserDefaults.standardUserDefaults().stringForKey("password")
client.logonWithUsername(username, password: password, responseCallback: {
(response: NSDictionary?, error: NSError?) in
if(error){
handleFailedAuth(error!)
return;
}
handleSuccessfulAuth()
}
)
This is because Swift optionals in some ways replace the way you used to pass nil in objective-c. So because the NSDictionary might be nil and the NSError might be nil, you put a ?-mark after them, then conditionally unwrap w/ a !-mark inside the block when you need to access the value of that
You're explicitly specifying the block/closure parameter types in Swift, and the Swift compiler does not have enough information about the NSDictionary. This is because the Swift Dictionary is more strongly typed than the Objective-C NSDictionary.
The error message says (admittedly, pretty cryptically) that the exact type Swift is expecting is a Dictionary<NSObject, AnyObject>!.
There are a couple of ways to solve this. One is to be more explicit about your NSDictionary parameter in the Swift closure definition:-
client.logonWithUsername(username, password: password, responseCallback: {
(response: Dictionary<NSObject, AnyObject>!, error: NSError) in
// ... handler body
}
)
A somewhat easier way is to not try to tell Swift about the types at all and let the compiler infer what it needs:-
client.logonWithUsername(username, password: password, responseCallback: {
response, error in
// ... handler body
}
)
There is a method in the Google Maps SDK that uses callbacks like these, I guess it´s blocks? anyways I have never used these before. I just want to see if the method requestPanoramaNearCoordinate returns a valid Panorama near the coordinate i give it. How do i use the callback to see what the callback returns? If it returns a valid Panorama at the coordinate I want to return it - if not I´ll recursively call the method until a valid location is found
- (void) requestPanoramaNearCoordinate: (CLLocationCoordinate2D)coordinate
callback: (GMSPanoramaCallback)callback
Retrieves information about a panorama near the given coordinate. This
is an asynchronous request, callback will be called with the result.
CLLocationCoordinate2D ranLatLng = CLLocationCoordinate2DMake(ranLatitude, ranLongitude);
if ([panoService requestPanoramaNearCoordinate:ranLatLng callback:<#^(GMSPanorama *panorama, NSError *error)callback#>)
The panorama parameter contains the GMSPanorama object that the request found for you. It doesn't seem to be documented very clearly, but the usual pattern in Cocoa is that if nothing appropriate was found, the result will be nil, and the NSError object will give you a little information about why. As for re-making the request, that would look something like this I guess:
// Necessary if GMSPanoService is ill-behaved and keeps the Block
// around after calling it.
__weak GMSPanoramaService weak_panoService = panoService;
__block GMSPanoramaCallback cb;
cb = [^(GMSPanorama *panorama, NSError *error){
if( !panorama ){
// Do something with error if desired
CLLocationCoordinate2D newLatLng = /* Calc new coordinates somehow */;
[/*weak_*/panoService requestPanoramaNearCoordinate:newLatLng
callback:cb];
}
else {
// Got a panorama, do something with it
cb = nil; // Release Block
}
} copy];
[panoService requestPanoramaNearCoordinate:ranLatLng
callback:cb];
It's a block. You make the api call like this:
[aPanoramaService requestPanoramaNearCoordinate:aCoordiante callback:^(GMSPanorama *panorama, NSError *error) {
NSLog(#"the service returned a panorama=%# and an error=%#", panorama, error);
}];
One way to do the recursion is to build a method with a similar signature to the google method. An important point is to find a way for the recursion to stop, possibly because you get tired of searching.
- (void)findPanoramaAtLocation:(CLLocationCoordinate2D)location withCompletion:(void (^)(GMSPanorama *, NSError *))completion {
[self.panoramaService requestPanoramaNearCoordinate:location callback:^(GMSPanorama *panorama, NSError *error) {
if (error) {
completion(nil, error);
} else if (panorama) {
completion(panorama, nil);
} else {
if (/* should we continue searching? */) {
// compute a new location to try
double latitude = // compute new latitude
double longitude = // compute new longitude
CLLocationCoordinate2D newLocation = CLLocationCoordinate2DMake(latitude, longitude);
[self findPanoramaAtLocation:newLocation withCompletion:completion];
} else {
// no panorama and no error means we gave up
completion(nil, nil);
}
}
}];
}
This will run, calling the "callback" (it's a block) when either a panorama location is found, when an error occurs, or when your logic determines that it ought to stop searching.
My app is crashing in iOS 5 because I have some code that is calling UIKit instances from a secondary thread. You know you have this problem when you see the following error:
bool _WebTryThreadLock(bool), 0x811bf20: Multiple locks on web thread not allowed! Please file a bug. Crashing now…
So my question is what are some ways that I can find the code that is calling the UIKit instances from a secondary thread?
Here are some things I’ve tried already:
Commented out blocks that could be violating the rule
Added assert([NSThread isMainThread]) in places that might be processing in secondary thread
Added a symbolic breakpoint for _WebTryThreadLock
These things have helped me to find problem areas. However, in my final crash the _WebTryThreadLock breakpoint has no stack trace in any of the other threads. So, how I can find the code that causing the problem without a stack trace?
Thanks for your time!
Your assert() is probably the most valuable tool in this. I've been known to put a similar assertion at the beginning of every method in my Controller classes. If that doesn't find it, I add the assertion to my View classes. If that doesn't find it, I add it to any Model classes that I think are main-thread only.
To #craig's comment, the fact that it claims to be an internal bug might be accurate. But I think you're on the right path to closely examine your own code first.
I adapted the PSPDFUIKitMainThreadGuard.m to allow one to not have to worry about these things. Here: https://gist.github.com/k3zi/98ca835b15077d11dafc :
#import <objc/runtime.h>
#import <objc/message.h>
// Compile-time selector checks.
#define PROPERTY(propName) NSStringFromSelector(#selector(propName))
// A better assert. NSAssert is too runtime dependant, and assert() doesn't log.
// http://www.mikeash.com/pyblog/friday-qa-2013-05-03-proper-use-of-asserts.html
// Accepts both:
// - PSPDFAssert(x > 0);
// - PSPDFAssert(y > 3, #"Bad value for y");
#define PSPDFAssert(expression, ...) \
do { if(!(expression)) { \
NSLog(#"%#", [NSString stringWithFormat: #"Assertion failure: %s in %s on line %s:%d. %#", #expression, __PRETTY_FUNCTION__, __FILE__, __LINE__, [NSString stringWithFormat:#"" __VA_ARGS__]]); \
abort(); }} while(0)
///////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - Helper for Swizzling
BOOL PSPDFReplaceMethodWithBlock(Class c, SEL origSEL, SEL newSEL, id block) {
PSPDFAssert(c && origSEL && newSEL && block);
Method origMethod = class_getInstanceMethod(c, origSEL);
const char *encoding = method_getTypeEncoding(origMethod);
// Add the new method.
IMP impl = imp_implementationWithBlock(block);
if (!class_addMethod(c, newSEL, impl, encoding)) {
NSLog(#"Failed to add method: %# on %#", NSStringFromSelector(newSEL), c);
return NO;
}else {
// Ensure the new selector has the same parameters as the existing selector.
Method newMethod = class_getInstanceMethod(c, newSEL);
PSPDFAssert(strcmp(method_getTypeEncoding(origMethod), method_getTypeEncoding(newMethod)) == 0, #"Encoding must be the same.");
// If original doesn't implement the method we want to swizzle, create it.
if (class_addMethod(c, origSEL, method_getImplementation(newMethod), encoding)) {
class_replaceMethod(c, newSEL, method_getImplementation(origMethod), encoding);
}else {
method_exchangeImplementations(origMethod, newMethod);
}
}
return YES;
}
// This installs a small guard that checks for the most common threading-errors in UIKit.
// This won't really slow down performance but still only is compiled in DEBUG versions of PSPDFKit.
// #note No private API is used here.
__attribute__((constructor)) static void PSPDFUIKitMainThreadGuard(void) {
#autoreleasepool {
for (NSString *selStr in #[PROPERTY(setNeedsLayout), PROPERTY(setNeedsDisplay), PROPERTY(setNeedsDisplayInRect:)]) {
SEL selector = NSSelectorFromString(selStr);
SEL newSelector = NSSelectorFromString([NSString stringWithFormat:#"pspdf_%#", selStr]);
if ([selStr hasSuffix:#":"]) {
PSPDFReplaceMethodWithBlock(UIView.class, selector, newSelector, ^(__unsafe_unretained UIView *_self, CGRect r) {
if(!NSThread.isMainThread){
dispatch_async(dispatch_get_main_queue(), ^{
((void ( *)(id, SEL, CGRect))objc_msgSend)(_self, newSelector, r);
});
}else{
((void ( *)(id, SEL, CGRect))objc_msgSend)(_self, newSelector, r);
}
});
}else {
PSPDFReplaceMethodWithBlock(UIView.class, selector, newSelector, ^(__unsafe_unretained UIView *_self) {
if(!NSThread.isMainThread){
dispatch_async(dispatch_get_main_queue(), ^{
((void ( *)(id, SEL))objc_msgSend)(_self, newSelector);
});
}else
((void ( *)(id, SEL))objc_msgSend)(_self, newSelector);
});
}
}
}
}
It automatically kicks calls into the main thread and thus you wouldn't even have to do anything but plop the code in.
This problem comes because you want to access to UI from secondary thread somehow, it can from webview of whatever else. It is not permitted because UIKit is not thread safe and can be accessed only from MainThread.
The very first thing you can do is to change your thread call to [self performSelectorOnMainThread:#selector(myMethod) withObject:nil waitUntilDone:NO]; (look for documentation).
In case when you have no other choice you can use GCD(Grand Central Dispathc)...
This code (just add to project and compile this file without ARC) causes assertions on UIKit access outside the main thread: https://gist.github.com/steipete/5664345
I've just used it to pickup numerous UIKit/main thread issues in some code I've just picked up.